diff --git a/.ai/instructions.md b/.ai/instructions.md index 994d517f75..3c24177827 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -293,6 +293,12 @@ This document provides essential context for AI models interacting with this pro * **Configuration Design:** Aim for simplicity with sensible defaults, while allowing for advanced customization. * **Embedded Systems Optimization:** ESPHome targets resource-constrained microcontrollers. Be mindful of flash size and RAM usage. + **Why Heap Allocation Matters:** + + ESP devices run for months with small heaps shared between Wi-Fi, BLE, LWIP, and application code. Over time, repeated allocations of different sizes fragment the heap. Failures happen when the largest contiguous block shrinks, even if total free heap is still large. We have seen field crashes caused by this. + + **Heap allocation after `setup()` should be avoided unless absolutely unavoidable.** Every allocation/deallocation cycle contributes to fragmentation. ESPHome treats runtime heap allocation as a long-term reliability bug, not a performance issue. Helpers that hide allocation (`std::string`, `std::to_string`, string-returning helpers) are being deprecated and replaced with buffer and view based APIs. + **STL Container Guidelines:** ESPHome runs on embedded systems with limited resources. Choose containers carefully: @@ -322,15 +328,15 @@ This document provides essential context for AI models interacting with this pro std::array buffer; ``` - 2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for fixed-size stack allocation with `push_back()` interface. + 2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for compile-time fixed size with `push_back()` interface (no dynamic allocation). ```cpp // Bad - generates STL realloc code (_M_realloc_insert) std::vector services; services.reserve(5); // Still includes reallocation machinery - // Good - compile-time fixed size, stack allocated, no reallocation machinery - StaticVector services; // Allocates all MAX_SERVICES on stack - services.push_back(record1); // Tracks count but all slots allocated + // Good - compile-time fixed size, no dynamic allocation + StaticVector services; + services.push_back(record1); ``` Use `cg.add_define("MAX_SERVICES", count)` to set the size from Python configuration. Like `std::array` but with vector-like API (`push_back()`, `size()`) and no STL reallocation code. @@ -372,22 +378,21 @@ This document provides essential context for AI models interacting with this pro ``` Linear search on small datasets (1-16 elements) is often faster than hashing/tree overhead, but this depends on lookup frequency and access patterns. For frequent lookups in hot code paths, the O(1) vs O(n) complexity difference may still matter even for small datasets. `std::vector` with simple structs is usually fine—it's the heavy containers (`map`, `set`, `unordered_map`) that should be avoided for small datasets unless profiling shows otherwise. - 5. **Detection:** Look for these patterns in compiler output: + 5. **Avoid `std::deque`:** It allocates in 512-byte blocks regardless of element size, guaranteeing at least 512 bytes of RAM usage immediately. This is a major source of crashes on memory-constrained devices. + + 6. **Detection:** Look for these patterns in compiler output: - Large code sections with STL symbols (vector, map, set) - `alloc`, `realloc`, `dealloc` in symbol names - `_M_realloc_insert`, `_M_default_append` (vector reallocation) - Red-black tree code (`rb_tree`, `_Rb_tree`) - Hash table infrastructure (`unordered_map`, `hash`) - **When to optimize:** + **Prioritize optimization effort for:** - Core components (API, network, logger) - Widely-used components (mdns, wifi, ble) - Components causing flash size complaints - **When not to optimize:** - - Single-use niche components - - Code where readability matters more than bytes - - Already using appropriate containers + Note: Avoiding heap allocation after `setup()` is always required regardless of component type. The prioritization above is about the effort spent on container optimization (e.g., migrating from `std::vector` to `StaticVector`). * **State Management:** Use `CORE.data` for component state that needs to persist during configuration generation. Avoid module-level mutable globals. diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 18379de92e..9661c2ca02 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -5969e705693278d984c5292e998df0cbaf34f7e1f04dfc7f7b7ad7168527bfa7 +d272a88e8ca28ae9340a9a03295a566432a52cb696501908f57764475bf7ca65 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 41dd02458e..d1ef3bd822 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -27,6 +27,7 @@ - [ ] RP2040 - [ ] BK72xx - [ ] RTL87xx +- [ ] LN882x - [ ] nRF52840 ## Example entry for `config.yaml`: diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index c4ac3d1a9e..75586fd854 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv # yamllint disable-line rule:line-length diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index 39164fc2ea..8e96297cc0 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -26,7 +26,7 @@ jobs: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} diff --git a/.github/workflows/ci-api-proto.yml b/.github/workflows/ci-api-proto.yml index a0c6568345..4c4bbf9981 100644 --- a/.github/workflows/ci-api-proto.yml +++ b/.github/workflows/ci-api-proto.yml @@ -62,7 +62,7 @@ jobs: run: git diff - if: failure() name: Archive artifacts - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: generated-proto-files path: | diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index bf7fa0c262..84d79cda17 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -49,7 +49,7 @@ jobs: with: python-version: "3.11" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Set TAG run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03eadb5f0a..434aa388f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv # yamllint disable-line rule:line-length @@ -152,12 +152,12 @@ jobs: . venv/bin/activate pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ - name: Upload coverage to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Save Python virtual environment cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -193,7 +193,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Restore components graph cache - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -223,7 +223,7 @@ jobs: echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT - name: Save components graph cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -245,7 +245,7 @@ jobs: python-version: "3.13" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -334,14 +334,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} @@ -413,14 +413,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -502,14 +502,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -735,7 +735,7 @@ jobs: - name: Restore cached memory analysis id: cache-memory-analysis if: steps.check-script.outputs.skip != 'true' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -759,7 +759,7 @@ jobs: - name: Cache platformio if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} @@ -800,7 +800,7 @@ jobs: - name: Save memory analysis to cache if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success' - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -821,7 +821,7 @@ jobs: fi - name: Upload memory analysis JSON - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: memory-analysis-target path: memory-analysis-target.json @@ -847,7 +847,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} @@ -885,7 +885,7 @@ jobs: --platform "$platform" - name: Upload memory analysis JSON - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: memory-analysis-pr path: memory-analysis-pr.json @@ -915,13 +915,13 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Download target analysis JSON - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: memory-analysis-target path: ./memory-analysis continue-on-error: true - name: Download PR analysis JSON - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: memory-analysis-pr path: ./memory-analysis diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 481ad0ec34..399fb13aa5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 + uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 + uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51aa1f885e..b41b118504 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -99,7 +99,7 @@ jobs: python-version: "3.11" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to docker hub uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 @@ -138,7 +138,7 @@ jobs: # version: ${{ needs.init.outputs.tag }} - name: Upload digests - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: digests-${{ matrix.platform.arch }} path: /tmp/digests @@ -171,14 +171,14 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download digests - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: digests-* path: /tmp/digests merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to docker hub if: matrix.registry == 'dockerhub' @@ -221,7 +221,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} @@ -256,7 +256,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} @@ -287,7 +287,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 2c3219e38e..8c830d99c7 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -41,7 +41,7 @@ jobs: python script/run-in-env.py pre-commit run --all-files - name: Commit changes - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 + uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot diff --git a/.gitignore b/.gitignore index 390d1ab45b..da568d9b83 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,10 @@ venv-*/ # mypy .mypy_cache/ +# nix +/default.nix +/shell.nix + .pioenvs .piolibdeps .pio diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49b87866f1..3295cf070a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.14.8 + rev: v0.14.11 hooks: # Run the linter. - id: ruff diff --git a/CODEOWNERS b/CODEOWNERS index af926d2d61..8a37aeb29f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -42,6 +42,7 @@ esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/apds9306/* @aodrenah esphome/components/api/* @esphome/core +esphome/components/aqi/* @freekode @jasstrong @ximex esphome/components/as5600/* @ammmze esphome/components/as5600/sensor/* @ammmze esphome/components/as7341/* @mrgnr @@ -90,6 +91,7 @@ esphome/components/bmp3xx_spi/* @latonita esphome/components/bmp581/* @kahrendt esphome/components/bp1658cj/* @Cossid esphome/components/bp5758d/* @Cossid +esphome/components/bthome_mithermometer/* @nagyrobi esphome/components/button/* @esphome/core esphome/components/bytebuffer/* @clydebarrow esphome/components/camera/* @bdraco @DT-art1 @@ -133,7 +135,7 @@ esphome/components/display_menu_base/* @numo68 esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/ds2484/* @mrk-its -esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/dsmr/* @glmnet @PolarGoose @zuidwijk esphome/components/duty_time/* @dudanov esphome/components/ee895/* @Stock-M esphome/components/ektf2232/touchscreen/* @jesserockz @@ -215,6 +217,7 @@ esphome/components/hlk_fm22x/* @OnFreund esphome/components/hlw8032/* @rici4kubicek esphome/components/hm3301/* @freekode esphome/components/hmac_md5/* @dwmw2 +esphome/components/hmac_sha256/* @dwmw2 esphome/components/homeassistant/* @esphome/core @OttoWinter esphome/components/homeassistant/number/* @landonr esphome/components/homeassistant/switch/* @Links2004 @@ -246,11 +249,13 @@ esphome/components/ina260/* @mreditor97 esphome/components/ina2xx_base/* @latonita esphome/components/ina2xx_i2c/* @latonita esphome/components/ina2xx_spi/* @latonita +esphome/components/infrared/* @kbx81 esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkplate/* @jesserockz @JosipKuci esphome/components/integration/* @OttoWinter esphome/components/internal_temperature/* @Mat931 esphome/components/interval/* @esphome/core +esphome/components/ir_rf_proxy/* @kbx81 esphome/components/jsn_sr04t/* @Mafus1 esphome/components/json/* @esphome/core esphome/components/kamstrup_kmp/* @cfeenstra1024 @@ -392,6 +397,7 @@ esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet +esphome/components/rd03d/* @jasstrong esphome/components/resampler/speaker/* @kahrendt esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz @@ -517,6 +523,7 @@ esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/uart/button/* @ssieb +esphome/components/uart/event/* @eoasmxd esphome/components/uart/packet_transport/* @clydebarrow esphome/components/udp/* @clydebarrow esphome/components/ufire_ec/* @pvizeli @@ -535,6 +542,7 @@ esphome/components/version/* @esphome/core esphome/components/voice_assistant/* @jesserockz @kahrendt esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/watchdog/* @oarcher +esphome/components/water_heater/* @dhoeben esphome/components/waveshare_epaper/* @clydebarrow esphome/components/web_server/ota/* @esphome/core esphome/components/web_server_base/* @esphome/core @@ -570,5 +578,6 @@ esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68 esphome/components/xxtea/* @clydebarrow esphome/components/zephyr/* @tomaszduda23 esphome/components/zhlt01/* @cfeenstra1024 +esphome/components/zigbee/* @tomaszduda23 esphome/components/zio_ultrasonic/* @kahrendt esphome/components/zwave_proxy/* @kbx81 diff --git a/Doxyfile b/Doxyfile index a23e21dd0d..e98eac6aa5 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.12.6 +PROJECT_NUMBER = 2026.1.0b1 # 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 diff --git a/MANIFEST.in b/MANIFEST.in index 45d5e86672..ed65edc656 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include LICENSE include README.md include requirements.txt +recursive-include esphome *.yaml recursive-include esphome *.cpp *.h *.tcc *.c recursive-include esphome *.py.script recursive-include esphome LICENSE.txt diff --git a/esphome/__main__.py b/esphome/__main__.py index 55fbbc6c8a..3849a585ca 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -62,6 +62,9 @@ from esphome.util import ( _LOGGER = logging.getLogger(__name__) +# Maximum buffer size for serial log reading to prevent unbounded memory growth +SERIAL_BUFFER_MAX_SIZE = 65536 + # Special non-component keys that appear in configs _NON_COMPONENT_KEYS = frozenset( { @@ -431,25 +434,37 @@ def run_miniterm(config: ConfigType, port: str, args) -> int: while tries < 5: try: with ser: + buffer = b"" + ser.timeout = 0.1 # 100ms timeout for non-blocking reads while True: try: - raw = ser.readline() + # Read all available data and timestamp it + chunk = ser.read(ser.in_waiting or 1) + if not chunk: + continue + time_ = datetime.now() + milliseconds = time_.microsecond // 1000 + time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{milliseconds:03}]" + + # Add to buffer and process complete lines + # Limit buffer size to prevent unbounded memory growth + # if device sends data without newlines + buffer += chunk + if len(buffer) > SERIAL_BUFFER_MAX_SIZE: + buffer = buffer[-SERIAL_BUFFER_MAX_SIZE:] + while b"\n" in buffer: + raw_line, buffer = buffer.split(b"\n", 1) + line = raw_line.replace(b"\r", b"").decode( + "utf8", "backslashreplace" + ) + safe_print(parser.parse_line(line, time_str)) + + backtrace_state = platformio_api.process_stacktrace( + config, line, backtrace_state=backtrace_state + ) except serial.SerialException: _LOGGER.error("Serial port closed!") return 0 - line = ( - raw.replace(b"\r", b"") - .replace(b"\n", b"") - .decode("utf8", "backslashreplace") - ) - time_ = datetime.now() - nanoseconds = time_.microsecond // 1000 - time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]" - safe_print(parser.parse_line(line, time_str)) - - backtrace_state = platformio_api.process_stacktrace( - config, line, backtrace_state=backtrace_state - ) except serial.SerialException: tries += 1 time.sleep(1) @@ -518,10 +533,49 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int: rc = platformio_api.run_compile(config, CORE.verbose) if rc != 0: return rc + + # Check if firmware was rebuilt and emit build_info + create manifest + _check_and_emit_build_info() + idedata = platformio_api.get_idedata(config) return 0 if idedata is not None else 1 +def _check_and_emit_build_info() -> None: + """Check if firmware was rebuilt and emit build_info.""" + import json + + firmware_path = CORE.firmware_bin + build_info_json_path = CORE.relative_build_path("build_info.json") + + # Check if both files exist + if not firmware_path.exists() or not build_info_json_path.exists(): + return + + # Check if firmware is newer than build_info (indicating a relink occurred) + if firmware_path.stat().st_mtime <= build_info_json_path.stat().st_mtime: + return + + # Read build_info from JSON + try: + with open(build_info_json_path, encoding="utf-8") as f: + build_info = json.load(f) + except (OSError, json.JSONDecodeError) as e: + _LOGGER.debug("Failed to read build_info: %s", e) + return + + config_hash = build_info.get("config_hash") + build_time_str = build_info.get("build_time_str") + + if config_hash is None or build_time_str is None: + return + + # Emit build_info with human-readable time + _LOGGER.info( + "Build Info: config_hash=0x%08x build_time_str=%s", config_hash, build_time_str + ) + + def upload_using_esptool( config: ConfigType, port: str, file: str, speed: int ) -> str | int: @@ -750,7 +804,13 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None: exit_code = compile_program(args, config) if exit_code != 0: return exit_code - _LOGGER.info("Successfully compiled program.") + if CORE.is_host: + from esphome.platformio_api import get_idedata + + program_path = str(get_idedata(config).firmware_elf_path) + _LOGGER.info("Successfully compiled program to path '%s'", program_path) + else: + _LOGGER.info("Successfully compiled program.") return 0 @@ -800,10 +860,8 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None: if CORE.is_host: from esphome.platformio_api import get_idedata - idedata = get_idedata(config) - if idedata is None: - return 1 - program_path = idedata.raw["prog_path"] + program_path = str(get_idedata(config).firmware_elf_path) + _LOGGER.info("Running program from path '%s'", program_path) return run_external_process(program_path) # Get devices, resolving special identifiers like OTA @@ -974,6 +1032,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int: idedata.objdump_path, idedata.readelf_path, external_components, + idedata=idedata, ) analyzer.analyze() diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index 9632a68913..9c935c78fa 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -22,6 +22,7 @@ from .helpers import ( map_section_name, parse_symbol_line, ) +from .toolchain import find_tool, run_tool if TYPE_CHECKING: from esphome.platformio_api import IDEData @@ -53,6 +54,9 @@ _NAMESPACE_STD = "std::" # Type alias for symbol information: (symbol_name, size, component) SymbolInfoType = tuple[str, int, str] +# RAM sections - symbols in these sections consume RAM +RAM_SECTIONS = frozenset([".data", ".bss"]) + @dataclass class MemorySection: @@ -60,7 +64,20 @@ class MemorySection: name: str symbols: list[SymbolInfoType] = field(default_factory=list) - total_size: int = 0 + total_size: int = 0 # Actual section size from ELF headers + symbol_size: int = 0 # Sum of symbol sizes (may be less than total_size) + + +@dataclass +class SDKSymbol: + """Represents a symbol from an SDK library that's not in the ELF symbol table.""" + + name: str + size: int + library: str # Name of the .a file (e.g., "libpp.a") + section: str # ".bss" or ".data" + is_local: bool # True if static/local symbol (lowercase in nm output) + demangled: str = "" # Demangled name (populated after analysis) @dataclass @@ -118,6 +135,10 @@ class MemoryAnalyzer: self.objdump_path = objdump_path or "objdump" self.readelf_path = readelf_path or "readelf" self.external_components = external_components or set() + self._idedata = idedata + + # Derive nm path from objdump path using shared toolchain utility + self.nm_path = find_tool("nm", self.objdump_path) self.sections: dict[str, MemorySection] = {} self.components: dict[str, ComponentMemory] = defaultdict( @@ -128,15 +149,25 @@ class MemoryAnalyzer: self._esphome_core_symbols: list[ tuple[str, str, int] ] = [] # Track core symbols - self._component_symbols: dict[str, list[tuple[str, str, int]]] = defaultdict( + # Track symbols for all components: (symbol_name, demangled, size, section) + self._component_symbols: dict[str, list[tuple[str, str, int, str]]] = ( + defaultdict(list) + ) + # Track RAM symbols separately for detailed analysis: (symbol_name, demangled, size, section) + self._ram_symbols: dict[str, list[tuple[str, str, int, str]]] = defaultdict( list - ) # Track symbols for all components + ) + # Track ELF symbol names for SDK cross-reference + self._elf_symbol_names: set[str] = set() + # SDK symbols not in ELF (static/local symbols from closed-source libs) + self._sdk_symbols: list[SDKSymbol] = [] def analyze(self) -> dict[str, ComponentMemory]: """Analyze the ELF file and return component memory usage.""" self._parse_sections() self._parse_symbols() self._categorize_symbols() + self._analyze_sdk_libraries() return dict(self.components) def _parse_sections(self) -> None: @@ -190,6 +221,8 @@ class MemoryAnalyzer: continue self.sections[section].symbols.append((name, size, "")) + self.sections[section].symbol_size += size + self._elf_symbol_names.add(name) seen_addresses.add(address) def _categorize_symbols(self) -> None: @@ -233,8 +266,13 @@ class MemoryAnalyzer: if size > 0: demangled = self._demangle_symbol(symbol_name) self._component_symbols[component].append( - (symbol_name, demangled, size) + (symbol_name, demangled, size, section_name) ) + # Track RAM symbols separately for detailed RAM analysis + if section_name in RAM_SECTIONS: + self._ram_symbols[component].append( + (symbol_name, demangled, size, section_name) + ) def _identify_component(self, symbol_name: str) -> str: """Identify which component a symbol belongs to.""" @@ -328,6 +366,247 @@ class MemoryAnalyzer: return "Other Core" + def get_unattributed_ram(self) -> tuple[int, int, int]: + """Get unattributed RAM sizes (SDK/framework overhead). + + Returns: + Tuple of (unattributed_bss, unattributed_data, total_unattributed) + These are bytes in RAM sections that have no corresponding symbols. + """ + bss_section = self.sections.get(".bss") + data_section = self.sections.get(".data") + + unattributed_bss = 0 + unattributed_data = 0 + + if bss_section: + unattributed_bss = max(0, bss_section.total_size - bss_section.symbol_size) + if data_section: + unattributed_data = max( + 0, data_section.total_size - data_section.symbol_size + ) + + return unattributed_bss, unattributed_data, unattributed_bss + unattributed_data + + def _find_sdk_library_dirs(self) -> list[Path]: + """Find SDK library directories based on platform. + + Returns: + List of paths to SDK library directories containing .a files. + """ + sdk_dirs: list[Path] = [] + + if self._idedata is None: + return sdk_dirs + + # Get the CC path to determine the framework location + cc_path = getattr(self._idedata, "cc_path", None) + if not cc_path: + return sdk_dirs + + cc_path = Path(cc_path) + + # For ESP8266 Arduino framework + # CC is like: ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc + # SDK libs are in: ~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib/ + if "xtensa-lx106" in str(cc_path): + platformio_dir = cc_path.parent.parent.parent + esp8266_sdk = ( + platformio_dir + / "framework-arduinoespressif8266" + / "tools" + / "sdk" + / "lib" + ) + if esp8266_sdk.exists(): + sdk_dirs.append(esp8266_sdk) + # Also check for NONOSDK subdirectories (closed-source libs) + sdk_dirs.extend( + subdir + for subdir in esp8266_sdk.iterdir() + if subdir.is_dir() and subdir.name.startswith("NONOSDK") + ) + + # For ESP32 IDF framework + # CC is like: ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-gcc + # or: ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-gcc + elif "xtensa-esp" in str(cc_path) or "riscv32-esp" in str(cc_path): + # Detect ESP32 variant from CC path or defines + variant = self._detect_esp32_variant() + if variant: + platformio_dir = cc_path.parent.parent.parent + espidf_dir = platformio_dir / "framework-espidf" / "components" + if espidf_dir.exists(): + # Find all directories named after the variant that contain .a files + # This handles various ESP-IDF library layouts: + # - components/*/lib// + # - components/*// + # - components/*/lib/lib// + # - components/*/*/lib_*// + sdk_dirs.extend( + variant_dir + for variant_dir in espidf_dir.rglob(variant) + if variant_dir.is_dir() and any(variant_dir.glob("*.a")) + ) + + return sdk_dirs + + def _detect_esp32_variant(self) -> str | None: + """Detect ESP32 variant from idedata defines. + + Returns: + Variant string like 'esp32', 'esp32s2', 'esp32c3', etc. or None. + """ + if self._idedata is None: + return None + + defines = getattr(self._idedata, "defines", []) + if not defines: + return None + + # ESPHome always adds USE_ESP32_VARIANT_xxx defines + variant_prefix = "USE_ESP32_VARIANT_" + for define in defines: + if define.startswith(variant_prefix): + # Extract variant name and convert to lowercase + # USE_ESP32_VARIANT_ESP32 -> esp32 + # USE_ESP32_VARIANT_ESP32S3 -> esp32s3 + return define[len(variant_prefix) :].lower() + + return None + + def _parse_sdk_library( + self, lib_path: Path + ) -> tuple[list[tuple[str, int, str, bool]], set[str]]: + """Parse a single SDK library for symbols. + + Args: + lib_path: Path to the .a library file + + Returns: + Tuple of: + - List of BSS/DATA symbols: (symbol_name, size, section, is_local) + - Set of global BSS/DATA symbol names (for checking if RAM is linked) + """ + ram_symbols: list[tuple[str, int, str, bool]] = [] + global_ram_symbols: set[str] = set() + + result = run_tool([self.nm_path, "--size-sort", str(lib_path)], timeout=10) + if result is None: + return ram_symbols, global_ram_symbols + + for line in result.stdout.splitlines(): + parts = line.split() + if len(parts) < 3: + continue + + try: + size = int(parts[0], 16) + sym_type = parts[1] + name = parts[2] + + # Only collect BSS (b/B) and DATA (d/D) for RAM analysis + if sym_type in ("b", "B"): + section = ".bss" + is_local = sym_type == "b" + ram_symbols.append((name, size, section, is_local)) + # Track global RAM symbols (B/D) for linking check + if sym_type == "B": + global_ram_symbols.add(name) + elif sym_type in ("d", "D"): + section = ".data" + is_local = sym_type == "d" + ram_symbols.append((name, size, section, is_local)) + if sym_type == "D": + global_ram_symbols.add(name) + except (ValueError, IndexError): + continue + + return ram_symbols, global_ram_symbols + + def _analyze_sdk_libraries(self) -> None: + """Analyze SDK libraries to find symbols not in the ELF. + + This finds static/local symbols from closed-source SDK libraries + that consume RAM but don't appear in the final ELF symbol table. + Only includes symbols from libraries that have RAM actually linked + (at least one global BSS/DATA symbol in the ELF). + """ + sdk_dirs = self._find_sdk_library_dirs() + if not sdk_dirs: + _LOGGER.debug("No SDK library directories found") + return + + _LOGGER.debug("Analyzing SDK libraries in %d directories", len(sdk_dirs)) + + # Track seen symbols to avoid duplicates from multiple SDK versions + seen_symbols: set[str] = set() + + for sdk_dir in sdk_dirs: + for lib_path in sorted(sdk_dir.glob("*.a")): + lib_name = lib_path.name + ram_symbols, global_ram_symbols = self._parse_sdk_library(lib_path) + + # Check if this library's RAM is actually linked by seeing if any + # of its global BSS/DATA symbols appear in the ELF + if not global_ram_symbols & self._elf_symbol_names: + # No RAM from this library is in the ELF - skip it + continue + + for name, size, section, is_local in ram_symbols: + # Skip if already in ELF or already seen from another lib + if name in self._elf_symbol_names or name in seen_symbols: + continue + + # Only track symbols with non-zero size + if size > 0: + self._sdk_symbols.append( + SDKSymbol( + name=name, + size=size, + library=lib_name, + section=section, + is_local=is_local, + ) + ) + seen_symbols.add(name) + + # Demangle SDK symbols for better readability + if self._sdk_symbols: + sdk_names = [sym.name for sym in self._sdk_symbols] + demangled_map = batch_demangle(sdk_names, objdump_path=self.objdump_path) + for sym in self._sdk_symbols: + sym.demangled = demangled_map.get(sym.name, sym.name) + + # Sort by size descending for reporting + self._sdk_symbols.sort(key=lambda s: s.size, reverse=True) + + total_sdk_ram = sum(s.size for s in self._sdk_symbols) + _LOGGER.debug( + "Found %d SDK symbols not in ELF, totaling %d bytes", + len(self._sdk_symbols), + total_sdk_ram, + ) + + def get_sdk_ram_symbols(self) -> list[SDKSymbol]: + """Get SDK symbols that consume RAM but aren't in the ELF symbol table. + + Returns: + List of SDKSymbol objects sorted by size descending. + """ + return self._sdk_symbols + + def get_sdk_ram_by_library(self) -> dict[str, list[SDKSymbol]]: + """Get SDK RAM symbols grouped by library. + + Returns: + Dictionary mapping library name to list of symbols. + """ + by_lib: dict[str, list[SDKSymbol]] = defaultdict(list) + for sym in self._sdk_symbols: + by_lib[sym.library].append(sym) + return dict(by_lib) + if __name__ == "__main__": from .cli import main diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index 44ade221f8..a77e17afce 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -1,16 +1,24 @@ """CLI interface for memory analysis with report generation.""" +from __future__ import annotations + from collections import defaultdict +from collections.abc import Callable import sys +from typing import TYPE_CHECKING from . import ( _COMPONENT_API, _COMPONENT_CORE, _COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL, + RAM_SECTIONS, MemoryAnalyzer, ) +if TYPE_CHECKING: + from . import ComponentMemory + class MemoryAnalyzerCLI(MemoryAnalyzer): """Memory analyzer with CLI-specific report generation.""" @@ -19,6 +27,8 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): SYMBOL_SIZE_THRESHOLD: int = ( 100 # Show symbols larger than this in detailed analysis ) + # Lower threshold for RAM symbols (RAM is more constrained) + RAM_SYMBOL_SIZE_THRESHOLD: int = 24 # Column width constants COL_COMPONENT: int = 29 @@ -83,6 +93,60 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): COL_CORE_PERCENT, ) + def _add_section_header(self, lines: list[str], title: str) -> None: + """Add a section header with title centered between separator lines.""" + lines.append("") + lines.append("=" * self.TABLE_WIDTH) + lines.append(title.center(self.TABLE_WIDTH)) + lines.append("=" * self.TABLE_WIDTH) + lines.append("") + + def _add_top_consumers( + self, + lines: list[str], + title: str, + components: list[tuple[str, ComponentMemory]], + get_size: Callable[[ComponentMemory], int], + total: int, + memory_type: str, + limit: int = 25, + ) -> None: + """Add a formatted list of top memory consumers to the report. + + Args: + lines: List of report lines to append the output to. + title: Section title to print before the list. + components: Sequence of (name, ComponentMemory) tuples to analyze. + get_size: Callable that takes a ComponentMemory and returns the + size in bytes to use for ranking and display. + total: Total size in bytes for computing percentage usage. + memory_type: Label for the memory region (e.g., "flash" or "RAM"). + limit: Maximum number of components to include in the list. + """ + lines.append("") + lines.append(f"{title}:") + for i, (name, mem) in enumerate(components[:limit]): + size = get_size(mem) + if size > 0: + percentage = (size / total * 100) if total > 0 else 0 + lines.append( + f"{i + 1}. {name} ({size:,} B) - {percentage:.1f}% of analyzed {memory_type}" + ) + + def _format_symbol_with_section( + self, demangled: str, size: int, section: str | None = None + ) -> str: + """Format a symbol entry, optionally adding a RAM section label. + + If section is one of the RAM sections (.data or .bss), a label like + " [data]" or " [bss]" is appended. For non-RAM sections or when + section is None, no section label is added. + """ + section_label = "" + if section in RAM_SECTIONS: + section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss] + return f"{demangled} ({size:,} B){section_label}" + def generate_report(self, detailed: bool = False) -> str: """Generate a formatted memory report.""" components = sorted( @@ -123,43 +187,70 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): f"{total_flash:>{self.COL_TOTAL_FLASH - 2},} B | {total_ram:>{self.COL_TOTAL_RAM - 2},} B" ) - # Top consumers - lines.append("") - lines.append("Top Flash Consumers:") - for i, (name, mem) in enumerate(components[:25]): - if mem.flash_total > 0: - percentage = ( - (mem.flash_total / total_flash * 100) if total_flash > 0 else 0 - ) - lines.append( - f"{i + 1}. {name} ({mem.flash_total:,} B) - {percentage:.1f}% of analyzed flash" - ) - - lines.append("") - lines.append("Top RAM Consumers:") - ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True) - for i, (name, mem) in enumerate(ram_components[:25]): - if mem.ram_total > 0: - percentage = (mem.ram_total / total_ram * 100) if total_ram > 0 else 0 - lines.append( - f"{i + 1}. {name} ({mem.ram_total:,} B) - {percentage:.1f}% of analyzed RAM" - ) - - lines.append("") - lines.append( - "Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included." + # Show unattributed RAM (SDK/framework overhead) + unattributed_bss, unattributed_data, unattributed_total = ( + self.get_unattributed_ram() + ) + if unattributed_total > 0: + lines.append("") + lines.append( + f"Unattributed RAM: {unattributed_total:,} B (SDK/framework overhead)" + ) + if unattributed_bss > 0 and unattributed_data > 0: + lines.append( + f" .bss: {unattributed_bss:,} B | .data: {unattributed_data:,} B" + ) + + # Show SDK symbol breakdown if available + sdk_by_lib = self.get_sdk_ram_by_library() + if sdk_by_lib: + lines.append("") + lines.append("SDK library breakdown (static symbols not in ELF):") + # Sort libraries by total size + lib_totals = [ + (lib, sum(s.size for s in syms), syms) + for lib, syms in sdk_by_lib.items() + ] + lib_totals.sort(key=lambda x: x[1], reverse=True) + + for lib_name, lib_total, syms in lib_totals: + if lib_total == 0: + continue + lines.append(f" {lib_name}: {lib_total:,} B") + # Show top symbols from this library + for sym in sorted(syms, key=lambda s: s.size, reverse=True)[:3]: + section_label = sym.section.lstrip(".") + # Use demangled name (falls back to original if not demangled) + display_name = sym.demangled or sym.name + if len(display_name) > 50: + display_name = f"{display_name[:47]}..." + lines.append( + f" {sym.size:>6,} B [{section_label}] {display_name}" + ) + + # Top consumers + self._add_top_consumers( + lines, + "Top Flash Consumers", + components, + lambda m: m.flash_total, + total_flash, + "flash", + ) + + ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True) + self._add_top_consumers( + lines, + "Top RAM Consumers", + ram_components, + lambda m: m.ram_total, + total_ram, + "RAM", ) - lines.append("=" * self.TABLE_WIDTH) # Add ESPHome core detailed analysis if there are core symbols if self._esphome_core_symbols: - lines.append("") - lines.append("=" * self.TABLE_WIDTH) - lines.append( - f"{_COMPONENT_CORE} Detailed Analysis".center(self.TABLE_WIDTH) - ) - lines.append("=" * self.TABLE_WIDTH) - lines.append("") + self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis") # Group core symbols by subcategory core_subcategories: dict[str, list[tuple[str, str, int]]] = defaultdict( @@ -211,7 +302,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):" ) for i, (symbol, demangled, size) in enumerate(large_core_symbols): - lines.append(f"{i + 1}. {demangled} ({size:,} B)") + # Core symbols only track (symbol, demangled, size) without section info, + # so we don't show section labels here + lines.append( + f"{i + 1}. {self._format_symbol_with_section(demangled, size)}" + ) lines.append("=" * self.TABLE_WIDTH) @@ -267,11 +362,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): for comp_name, comp_mem in components_to_analyze: if not (comp_symbols := self._component_symbols.get(comp_name, [])): continue - lines.append("") - lines.append("=" * self.TABLE_WIDTH) - lines.append(f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH)) - lines.append("=" * self.TABLE_WIDTH) - lines.append("") + self._add_section_header(lines, f"{comp_name} Detailed Analysis") # Sort symbols by size sorted_symbols = sorted(comp_symbols, key=lambda x: x[2], reverse=True) @@ -282,19 +373,69 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): # Show all symbols above threshold for better visibility large_symbols = [ - (sym, dem, size) - for sym, dem, size in sorted_symbols + (sym, dem, size, sec) + for sym, dem, size, sec in sorted_symbols if size > self.SYMBOL_SIZE_THRESHOLD ] lines.append( f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):" ) - for i, (symbol, demangled, size) in enumerate(large_symbols): - lines.append(f"{i + 1}. {demangled} ({size:,} B)") + for i, (symbol, demangled, size, section) in enumerate(large_symbols): + lines.append( + f"{i + 1}. {self._format_symbol_with_section(demangled, size, section)}" + ) lines.append("=" * self.TABLE_WIDTH) + # Detailed RAM analysis by component (at end, before RAM strings analysis) + self._add_section_header(lines, "RAM Symbol Analysis by Component") + + # Show top 15 RAM consumers with their large symbols + for name, mem in ram_components[:15]: + if mem.ram_total == 0: + continue + ram_syms = self._ram_symbols.get(name, []) + if not ram_syms: + continue + + # Sort by size descending + sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True) + large_ram_syms = [ + s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD + ] + + lines.append(f"{name} ({mem.ram_total:,} B total RAM):") + + # Show breakdown by section type + data_size = sum(s[2] for s in ram_syms if s[3] == ".data") + bss_size = sum(s[2] for s in ram_syms if s[3] == ".bss") + lines.append(f" .data (initialized): {data_size:,} B") + lines.append(f" .bss (uninitialized): {bss_size:,} B") + + if large_ram_syms: + lines.append( + f" Symbols > {self.RAM_SYMBOL_SIZE_THRESHOLD} B ({len(large_ram_syms)}):" + ) + for symbol, demangled, size, section in large_ram_syms[:10]: + # Format section label consistently by stripping leading dot + section_label = section.lstrip(".") if section else "" + # Add ellipsis if name is truncated + demangled_display = ( + f"{demangled[:70]}..." if len(demangled) > 70 else demangled + ) + lines.append( + f" {size:>6,} B [{section_label}] {demangled_display}" + ) + if len(large_ram_syms) > 10: + lines.append(f" ... and {len(large_ram_syms) - 10} more") + lines.append("") + + lines.append( + "Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included." + ) + lines.append("=" * self.TABLE_WIDTH) + return "\n".join(lines) def dump_uncategorized_symbols(self, output_file: str | None = None) -> None: diff --git a/esphome/analyze_memory/const.py b/esphome/analyze_memory/const.py index 78af82059f..9933bd77fd 100644 --- a/esphome/analyze_memory/const.py +++ b/esphome/analyze_memory/const.py @@ -7,11 +7,13 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::") # Section mapping for ELF file sections # Maps standard section names to their various platform-specific variants +# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram) +# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise SECTION_MAPPING = { ".text": frozenset([".text", ".iram"]), ".rodata": frozenset([".rodata"]), + ".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss" ".data": frozenset([".data", ".dram"]), - ".bss": frozenset([".bss"]), } # Section to ComponentMemory attribute mapping @@ -88,6 +90,77 @@ SYMBOL_PATTERNS = { "sys_mbox_new", "sys_arch_mbox_tryfetch", ], + # LibreTiny/Beken BK7231 radio calibration + "bk_radio_cal": [ + "bk7011_", + "calibration_main", + "gcali_", + "rwnx_cal", + ], + # LibreTiny/Beken WiFi MAC layer + "bk_wifi_mac": [ + "rxu_", # RX upper layer + "txu_", # TX upper layer + "txl_", # TX lower layer + "rxl_", # RX lower layer + "scanu_", # Scan unit + "mm_hw_", # MAC management hardware + "mm_bcn", # MAC management beacon + "mm_tim", # MAC management TIM + "mm_check", # MAC management checks + "sm_connect", # Station management + "me_beacon", # Management entity beacon + "me_build", # Management entity build + "hapd_", # Host AP daemon + "chan_pre_", # Channel management + "handle_probe_", # Probe handling + ], + # LibreTiny/Beken system control + "bk_system": [ + "sctrl_", # System control + "icu_ctrl", # Interrupt control unit + "gdma_ctrl", # DMA control + "mpb_ctrl", # MPB control + "uf2_", # UF2 OTA + "bkreg_", # Beken registers + ], + # LibreTiny/Beken BLE stack + "bk_ble": [ + "gapc_", # GAP client + "gattc_", # GATT client + "attc_", # ATT client + "attmdb_", # ATT database + "atts_", # ATT server + "l2cc_", # L2CAP + "prf_env", # Profile environment + ], + # LibreTiny/Beken scheduler + "bk_scheduler": [ + "sch_plan_", # Scheduler plan + "sch_prog_", # Scheduler program + "sch_arb_", # Scheduler arbiter + ], + # LibreTiny/Beken DMA descriptors + "bk_dma": [ + "rx_payload_desc", + "rx_dma_hdrdesc", + "tx_hw_desc", + "host_event_data", + "host_cmd_data", + ], + # ARM EABI compiler runtime (LibreTiny uses ARM Cortex-M) + "arm_runtime": [ + "__aeabi_", + "__adddf3", + "__subdf3", + "__muldf3", + "__divdf3", + "__addsf3", + "__subsf3", + "__mulsf3", + "__divsf3", + "__gnu_unwind", + ], "xtensa": ["xt_", "_xt_", "xPortEnterCriticalTimeout"], "heap": ["heap_", "multi_heap"], "spi_flash": ["spi_flash"], @@ -782,7 +855,22 @@ SYMBOL_PATTERNS = { "math_internal": ["__mdiff", "__lshift", "__mprec_tens", "quorem"], "character_class": ["__chclass"], "camellia": ["camellia_", "camellia_feistel"], - "crypto_tables": ["FSb", "FSb2", "FSb3", "FSb4"], + "crypto_tables": [ + "FSb", + "FSb2", + "FSb3", + "FSb4", + "Te0", # AES encryption table + "Td0", # AES decryption table + "crc32_table", # CRC32 lookup table + "crc_tab", # CRC lookup table + ], + "crypto_hash": [ + "SHA1Transform", # SHA1 hash function + "MD5Transform", # MD5 hash function + "SHA256", + "SHA512", + ], "event_buffer": ["g_eb_list_desc", "eb_space"], "base_node": ["base_node_", "base_node_add_handler"], "file_descriptor": ["s_fd_table"], diff --git a/esphome/analyze_memory/toolchain.py b/esphome/analyze_memory/toolchain.py index e766252412..23d85e9700 100644 --- a/esphome/analyze_memory/toolchain.py +++ b/esphome/analyze_memory/toolchain.py @@ -5,6 +5,10 @@ from __future__ import annotations import logging from pathlib import Path import subprocess +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence _LOGGER = logging.getLogger(__name__) @@ -55,3 +59,35 @@ def find_tool( _LOGGER.warning("Could not find %s tool", tool_name) return None + + +def run_tool( + cmd: Sequence[str], + timeout: int = 30, +) -> subprocess.CompletedProcess[str] | None: + """Run a toolchain command and return the result. + + Args: + cmd: Command and arguments to run + timeout: Timeout in seconds + + Returns: + CompletedProcess on success, None on failure + """ + try: + return subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=timeout, + check=False, + ) + except subprocess.TimeoutExpired: + _LOGGER.warning("Command timed out: %s", " ".join(cmd)) + return None + except FileNotFoundError: + _LOGGER.warning("Command not found: %s", cmd[0]) + return None + except OSError as e: + _LOGGER.warning("Failed to run command %s: %s", cmd[0], e) + return None diff --git a/esphome/components/a01nyub/a01nyub.cpp b/esphome/components/a01nyub/a01nyub.cpp index d0bc89a0c9..210c3557b3 100644 --- a/esphome/components/a01nyub/a01nyub.cpp +++ b/esphome/components/a01nyub/a01nyub.cpp @@ -30,7 +30,9 @@ void A01nyubComponent::check_buffer_() { ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters); this->publish_state(meters); } else { - ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + char hex_buf[format_hex_pretty_size(4)]; + ESP_LOGW(TAG, "Invalid data read from sensor: %s", + format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size())); } } else { ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); diff --git a/esphome/components/a02yyuw/a02yyuw.cpp b/esphome/components/a02yyuw/a02yyuw.cpp index ee378c3283..a2aad0cef1 100644 --- a/esphome/components/a02yyuw/a02yyuw.cpp +++ b/esphome/components/a02yyuw/a02yyuw.cpp @@ -29,7 +29,9 @@ void A02yyuwComponent::check_buffer_() { ESP_LOGV(TAG, "Distance from sensor: %f mm", distance); this->publish_state(distance); } else { - ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + char hex_buf[format_hex_pretty_size(4)]; + ESP_LOGW(TAG, "Invalid data read from sensor: %s", + format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size())); } } else { ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); diff --git a/esphome/components/absolute_humidity/absolute_humidity.cpp b/esphome/components/absolute_humidity/absolute_humidity.cpp index 74d675b80b..b13fcd519a 100644 --- a/esphome/components/absolute_humidity/absolute_humidity.cpp +++ b/esphome/components/absolute_humidity/absolute_humidity.cpp @@ -90,13 +90,16 @@ void AbsoluteHumidityComponent::loop() { this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!")); return; } - ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es); // Calculate absolute humidity const float absolute_humidity = vapor_density(es, hr, temperature_k); + ESP_LOGD(TAG, + "Saturation vapor pressure %f kPa\n" + "Publishing absolute humidity %f g/m³", + es, absolute_humidity); + // Publish absolute humidity - ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity); this->status_clear_warning(); this->publish_state(absolute_humidity); } diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index e6f7a1214a..1e850a18fe 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "ac_dimmer.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -9,12 +7,12 @@ #ifdef USE_ESP8266 #include #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include + +#ifdef USE_ESP32 +#include "hw_timer_esp_idf.h" #endif -namespace esphome { -namespace ac_dimmer { +namespace esphome::ac_dimmer { static const char *const TAG = "ac_dimmer"; @@ -27,7 +25,14 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no /// However other factors like gate driver propagation time /// are also considered and a really low value is not important /// See also: https://github.com/esphome/issues/issues/1632 -static const uint32_t GATE_ENABLE_TIME = 50; +static constexpr uint32_t GATE_ENABLE_TIME = 50; + +#ifdef USE_ESP32 +/// Timer frequency in Hz (1 MHz = 1µs resolution) +static constexpr uint32_t TIMER_FREQUENCY_HZ = 1000000; +/// Timer interrupt interval in microseconds +static constexpr uint64_t TIMER_INTERVAL_US = 50; +#endif /// Function called from timer interrupt /// Input is current time in microseconds (micros()) @@ -154,7 +159,7 @@ void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) { #ifdef USE_ESP32 // ESP32 implementation, uses basically the same code but needs to wrap // timer_interrupt() function to auto-reschedule -static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static HWTimer *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } #endif @@ -194,15 +199,15 @@ void AcDimmer::setup() { setTimer1Callback(&timer_interrupt); #endif #ifdef USE_ESP32 - // timer frequency of 1mhz - dimmer_timer = timerBegin(1000000); - timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr); + dimmer_timer = timer_begin(TIMER_FREQUENCY_HZ); + timer_attach_interrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr); // For ESP32, we can't use dynamic interval calculation because the timerX functions // are not callable from ISR (placed in flash storage). // Here we just use an interrupt firing every 50 µs. - timerAlarm(dimmer_timer, 50, true, 0); + timer_alarm(dimmer_timer, TIMER_INTERVAL_US, true, 0); #endif } + void AcDimmer::write_state(float state) { state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation auto new_value = static_cast(roundf(state * 65535)); @@ -210,14 +215,15 @@ void AcDimmer::write_state(float state) { this->store_.init_cycle = this->init_with_half_cycle_; this->store_.value = new_value; } + void AcDimmer::dump_config() { - ESP_LOGCONFIG(TAG, "AcDimmer:"); - LOG_PIN(" Output Pin: ", this->gate_pin_); - LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_); ESP_LOGCONFIG(TAG, + "AcDimmer:\n" " Min Power: %.1f%%\n" " Init with half cycle: %s", this->store_.min_power / 10.0f, YESNO(this->init_with_half_cycle_)); + LOG_PIN(" Output Pin: ", this->gate_pin_); + LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_); if (method_ == DIM_METHOD_LEADING_PULSE) { ESP_LOGCONFIG(TAG, " Method: leading pulse"); } else if (method_ == DIM_METHOD_LEADING) { @@ -230,7 +236,4 @@ void AcDimmer::dump_config() { ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2); } -} // namespace ac_dimmer -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::ac_dimmer diff --git a/esphome/components/ac_dimmer/ac_dimmer.h b/esphome/components/ac_dimmer/ac_dimmer.h index fd1bbc28db..ca2a19210a 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.h +++ b/esphome/components/ac_dimmer/ac_dimmer.h @@ -1,13 +1,10 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/components/output/float_output.h" -namespace esphome { -namespace ac_dimmer { +namespace esphome::ac_dimmer { enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING }; @@ -64,7 +61,4 @@ class AcDimmer : public output::FloatOutput, public Component { DimMethod method_; }; -} // namespace ac_dimmer -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::ac_dimmer diff --git a/esphome/components/ac_dimmer/hw_timer_esp_idf.cpp b/esphome/components/ac_dimmer/hw_timer_esp_idf.cpp new file mode 100644 index 0000000000..543b476085 --- /dev/null +++ b/esphome/components/ac_dimmer/hw_timer_esp_idf.cpp @@ -0,0 +1,152 @@ +#ifdef USE_ESP32 + +#include "hw_timer_esp_idf.h" + +#include "freertos/FreeRTOS.h" +#include "esphome/core/log.h" + +#include "driver/gptimer.h" +#include "esp_clk_tree.h" +#include "soc/clk_tree_defs.h" + +static const char *const TAG = "hw_timer_esp_idf"; + +namespace esphome::ac_dimmer { + +// GPTimer divider constraints from ESP-IDF documentation +static constexpr uint32_t GPTIMER_DIVIDER_MIN = 2; +static constexpr uint32_t GPTIMER_DIVIDER_MAX = 65536; + +using voidFuncPtr = void (*)(); +using voidFuncPtrArg = void (*)(void *); + +struct InterruptConfigT { + voidFuncPtr fn{nullptr}; + void *arg{nullptr}; +}; + +struct HWTimer { + gptimer_handle_t timer_handle{nullptr}; + InterruptConfigT interrupt_handle{}; + bool timer_started{false}; +}; + +HWTimer *timer_begin(uint32_t frequency) { + esp_err_t err = ESP_OK; + uint32_t counter_src_hz = 0; + uint32_t divider = 0; + soc_module_clk_t clk; + for (auto clk_candidate : SOC_GPTIMER_CLKS) { + clk = clk_candidate; + esp_clk_tree_src_get_freq_hz(clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz); + divider = counter_src_hz / frequency; + if ((divider >= GPTIMER_DIVIDER_MIN) && (divider <= GPTIMER_DIVIDER_MAX)) { + break; + } else { + divider = 0; + } + } + + if (divider == 0) { + ESP_LOGE(TAG, "Resolution not possible; aborting"); + return nullptr; + } + + gptimer_config_t config = { + .clk_src = static_cast(clk), + .direction = GPTIMER_COUNT_UP, + .resolution_hz = frequency, + .flags = {.intr_shared = true}, + }; + + HWTimer *timer = new HWTimer(); + + err = gptimer_new_timer(&config, &timer->timer_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "GPTimer creation failed; error %d", err); + delete timer; + return nullptr; + } + + err = gptimer_enable(timer->timer_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "GPTimer enable failed; error %d", err); + gptimer_del_timer(timer->timer_handle); + delete timer; + return nullptr; + } + + err = gptimer_start(timer->timer_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "GPTimer start failed; error %d", err); + gptimer_disable(timer->timer_handle); + gptimer_del_timer(timer->timer_handle); + delete timer; + return nullptr; + } + + timer->timer_started = true; + return timer; +} + +bool IRAM_ATTR timer_fn_wrapper(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *args) { + auto *isr = static_cast(args); + if (isr->fn) { + if (isr->arg) { + reinterpret_cast(isr->fn)(isr->arg); + } else { + isr->fn(); + } + } + // Return false to indicate that no higher-priority task was woken and no context switch is requested. + return false; +} + +static void timer_attach_interrupt_functional_arg(HWTimer *timer, void (*user_func)(void *), void *arg) { + if (timer == nullptr) { + ESP_LOGE(TAG, "Timer handle is nullptr"); + return; + } + gptimer_event_callbacks_t cbs = { + .on_alarm = timer_fn_wrapper, + }; + + timer->interrupt_handle.fn = reinterpret_cast(user_func); + timer->interrupt_handle.arg = arg; + + if (timer->timer_started) { + gptimer_stop(timer->timer_handle); + } + gptimer_disable(timer->timer_handle); + esp_err_t err = gptimer_register_event_callbacks(timer->timer_handle, &cbs, &timer->interrupt_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Timer Attach Interrupt failed; error %d", err); + } + gptimer_enable(timer->timer_handle); + if (timer->timer_started) { + gptimer_start(timer->timer_handle); + } +} + +void timer_attach_interrupt(HWTimer *timer, voidFuncPtr user_func) { + timer_attach_interrupt_functional_arg(timer, reinterpret_cast(user_func), nullptr); +} + +void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count) { + if (timer == nullptr) { + ESP_LOGE(TAG, "Timer handle is nullptr"); + return; + } + gptimer_alarm_config_t alarm_cfg = { + .alarm_count = alarm_value, + .reload_count = reload_count, + .flags = {.auto_reload_on_alarm = autoreload}, + }; + esp_err_t err = gptimer_set_alarm_action(timer->timer_handle, &alarm_cfg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Timer Alarm Write failed; error %d", err); + } +} + +} // namespace esphome::ac_dimmer +#endif diff --git a/esphome/components/ac_dimmer/hw_timer_esp_idf.h b/esphome/components/ac_dimmer/hw_timer_esp_idf.h new file mode 100644 index 0000000000..1b2401ebda --- /dev/null +++ b/esphome/components/ac_dimmer/hw_timer_esp_idf.h @@ -0,0 +1,17 @@ +#pragma once +#ifdef USE_ESP32 + +#include "driver/gptimer_types.h" + +namespace esphome::ac_dimmer { + +struct HWTimer; + +HWTimer *timer_begin(uint32_t frequency); + +void timer_attach_interrupt(HWTimer *timer, void (*user_func)()); +void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count); + +} // namespace esphome::ac_dimmer + +#endif diff --git a/esphome/components/ac_dimmer/output.py b/esphome/components/ac_dimmer/output.py index 5e24779510..efc24b65e7 100644 --- a/esphome/components/ac_dimmer/output.py +++ b/esphome/components/ac_dimmer/output.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import output import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER +from esphome.core import CORE CODEOWNERS = ["@glmnet"] @@ -31,11 +32,16 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_with_arduino, ) async def to_code(config): + if CORE.is_esp8266: + # ac_dimmer uses setTimer1Callback which requires the waveform generator + from esphome.components.esp8266.const import require_waveform + + require_waveform() + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index 120cb1c926..ea1263db5f 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -121,23 +121,21 @@ void ADCSensor::setup() { void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, - " Channel: %d\n" - " Unit: %s\n" - " Attenuation: %s\n" - " Samples: %i\n" - " Sampling mode: %s", - this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), - this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_, - LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); - ESP_LOGCONFIG( TAG, + " Channel: %d\n" + " Unit: %s\n" + " Attenuation: %s\n" + " Samples: %i\n" + " Sampling mode: %s\n" " Setup Status:\n" " Handle Init: %s\n" " Config: %s\n" " Calibration: %s\n" " Overall Init: %s", + this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), + this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_, + LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)), this->setup_flags_.handle_init_complete ? "OK" : "FAILED", this->setup_flags_.config_complete ? "OK" : "FAILED", this->setup_flags_.calibration_complete ? "OK" : "FAILED", this->setup_flags_.init_complete ? "OK" : "FAILED"); diff --git a/esphome/components/addressable_light/addressable_light_display.h b/esphome/components/addressable_light/addressable_light_display.h index f47389fd05..53f8604b7d 100644 --- a/esphome/components/addressable_light/addressable_light_display.h +++ b/esphome/components/addressable_light/addressable_light_display.h @@ -25,11 +25,13 @@ class AddressableLightDisplay : public display::DisplayBuffer { if (enabled_ && !enabled) { // enabled -> disabled // - Tell the parent light to refresh, effectively wiping the display. Also // restores the previous effect (if any). - light_state_->make_call().set_effect(this->last_effect_).perform(); + if (this->last_effect_index_.has_value()) { + light_state_->make_call().set_effect(*this->last_effect_index_).perform(); + } } else if (!enabled_ && enabled) { // disabled -> enabled - // - Save the current effect. - this->last_effect_ = light_state_->get_effect_name(); + // - Save the current effect index. + this->last_effect_index_ = light_state_->get_current_effect_index(); // - Disable any current effect. light_state_->make_call().set_effect(0).perform(); } @@ -56,7 +58,7 @@ class AddressableLightDisplay : public display::DisplayBuffer { int32_t width_; int32_t height_; std::vector addressable_light_buffer_; - optional last_effect_; + optional last_effect_index_; optional> pixel_mapper_f_; }; } // namespace addressable_light diff --git a/esphome/components/ade7880/ade7880.cpp b/esphome/components/ade7880/ade7880.cpp index fd560e0676..f6a15190cd 100644 --- a/esphome/components/ade7880/ade7880.cpp +++ b/esphome/components/ade7880/ade7880.cpp @@ -162,11 +162,13 @@ void ADE7880::update() { } void ADE7880::dump_config() { - ESP_LOGCONFIG(TAG, "ADE7880:"); + ESP_LOGCONFIG(TAG, + "ADE7880:\n" + " Frequency: %.0f Hz", + this->frequency_); LOG_PIN(" IRQ0 Pin: ", this->irq0_pin_); LOG_PIN(" IRQ1 Pin: ", this->irq1_pin_); LOG_PIN(" RESET Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_); if (this->channel_a_ != nullptr) { ESP_LOGCONFIG(TAG, " Phase A:"); diff --git a/esphome/components/ads1115/sensor/ads1115_sensor.cpp b/esphome/components/ads1115/sensor/ads1115_sensor.cpp index 6de95f1d12..fac6b60d0a 100644 --- a/esphome/components/ads1115/sensor/ads1115_sensor.cpp +++ b/esphome/components/ads1115/sensor/ads1115_sensor.cpp @@ -21,10 +21,12 @@ void ADS1115Sensor::update() { void ADS1115Sensor::dump_config() { LOG_SENSOR(" ", "ADS1115 Sensor", this); - ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_); - ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_); - ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_); - ESP_LOGCONFIG(TAG, " Sample rate: %u", this->samplerate_); + ESP_LOGCONFIG(TAG, + " Multiplexer: %u\n" + " Gain: %u\n" + " Resolution: %u\n" + " Sample rate: %u", + this->multiplexer_, this->gain_, this->resolution_, this->samplerate_); } } // namespace ads1115 diff --git a/esphome/components/ads1118/sensor/ads1118_sensor.cpp b/esphome/components/ads1118/sensor/ads1118_sensor.cpp index c3ce3bdc9c..7193c3c880 100644 --- a/esphome/components/ads1118/sensor/ads1118_sensor.cpp +++ b/esphome/components/ads1118/sensor/ads1118_sensor.cpp @@ -9,8 +9,10 @@ static const char *const TAG = "ads1118.sensor"; void ADS1118Sensor::dump_config() { LOG_SENSOR(" ", "ADS1118 Sensor", this); - ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_); - ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_); + ESP_LOGCONFIG(TAG, + " Multiplexer: %u\n" + " Gain: %u", + this->multiplexer_, this->gain_); } float ADS1118Sensor::sample() { diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index a36d614df5..58faf923f5 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -20,7 +20,8 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic sn |= ((uint32_t) it.data[2] << 16); sn |= ((uint32_t) it.data[3] << 24); - ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str_to(addr_buf)); return true; } } diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.cpp b/esphome/components/airthings_wave_base/airthings_wave_base.cpp index 16789ff454..e4c7d2a81d 100644 --- a/esphome/components/airthings_wave_base/airthings_wave_base.cpp +++ b/esphome/components/airthings_wave_base/airthings_wave_base.cpp @@ -1,4 +1,5 @@ #include "airthings_wave_base.h" +#include "esphome/components/esp32_ble/ble_uuid.h" // All information related to reading battery information came from the sensors.airthings_wave // project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave) @@ -93,8 +94,10 @@ void AirthingsWaveBase::update() { bool AirthingsWaveBase::request_read_values_() { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->sensors_data_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->sensors_data_characteristic_uuid_.to_str(char_buf)); return false; } @@ -117,17 +120,20 @@ bool AirthingsWaveBase::request_battery_() { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_); if (chr == nullptr) { + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s", - this->service_uuid_.to_string().c_str(), - this->access_control_point_characteristic_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->access_control_point_characteristic_uuid_.to_str(char_buf)); return false; } auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_, CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID); if (descr == nullptr) { - ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->access_control_point_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->access_control_point_characteristic_uuid_.to_str(char_buf)); return false; } diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index c29e02c8ef..89c0908a74 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -8,8 +8,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { static const char *const TAG = "alarm_control_panel"; @@ -35,26 +34,12 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)), LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); this->current_state_ = state; + // Single state callback - triggers check get_state() for specific states this->state_callback_.call(); #if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_alarm_control_panel_update(this); #endif - if (state == ACP_STATE_TRIGGERED) { - this->triggered_callback_.call(); - } else if (state == ACP_STATE_ARMING) { - this->arming_callback_.call(); - } else if (state == ACP_STATE_PENDING) { - this->pending_callback_.call(); - } else if (state == ACP_STATE_ARMED_HOME) { - this->armed_home_callback_.call(); - } else if (state == ACP_STATE_ARMED_NIGHT) { - this->armed_night_callback_.call(); - } else if (state == ACP_STATE_ARMED_AWAY) { - this->armed_away_callback_.call(); - } else if (state == ACP_STATE_DISARMED) { - this->disarmed_callback_.call(); - } - + // Cleared fires when leaving TRIGGERED state if (prev_state == ACP_STATE_TRIGGERED) { this->cleared_callback_.call(); } @@ -69,34 +54,6 @@ void AlarmControlPanel::add_on_state_callback(std::function &&callback) this->state_callback_.add(std::move(callback)); } -void AlarmControlPanel::add_on_triggered_callback(std::function &&callback) { - this->triggered_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_arming_callback(std::function &&callback) { - this->arming_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_armed_home_callback(std::function &&callback) { - this->armed_home_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_armed_night_callback(std::function &&callback) { - this->armed_night_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_armed_away_callback(std::function &&callback) { - this->armed_away_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_pending_callback(std::function &&callback) { - this->pending_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_disarmed_callback(std::function &&callback) { - this->disarmed_callback_.add(std::move(callback)); -} - void AlarmControlPanel::add_on_cleared_callback(std::function &&callback) { this->cleared_callback_.add(std::move(callback)); } @@ -157,5 +114,4 @@ void AlarmControlPanel::disarm(optional code) { call.perform(); } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index 85c2b2148e..340f15bcd6 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "alarm_control_panel_call.h" #include "alarm_control_panel_state.h" @@ -9,8 +7,7 @@ #include "esphome/core/entity_base.h" #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { enum AlarmControlPanelFeature : uint8_t { // Matches Home Assistant values @@ -35,54 +32,13 @@ class AlarmControlPanel : public EntityBase { */ void publish_state(AlarmControlPanelState state); - /** Add a callback for when the state of the alarm_control_panel changes + /** Add a callback for when the state of the alarm_control_panel changes. + * Triggers can check get_state() to determine the new state. * * @param callback The callback function */ void add_on_state_callback(std::function &&callback); - /** Add a callback for when the state of the alarm_control_panel chanes to triggered - * - * @param callback The callback function - */ - void add_on_triggered_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel chanes to arming - * - * @param callback The callback function - */ - void add_on_arming_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to pending - * - * @param callback The callback function - */ - void add_on_pending_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to armed_home - * - * @param callback The callback function - */ - void add_on_armed_home_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to armed_night - * - * @param callback The callback function - */ - void add_on_armed_night_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to armed_away - * - * @param callback The callback function - */ - void add_on_armed_away_callback(std::function &&callback); - - /** Add a callback for when the state of the alarm_control_panel changes to disarmed - * - * @param callback The callback function - */ - void add_on_disarmed_callback(std::function &&callback); - /** Add a callback for when the state of the alarm_control_panel clears from triggered * * @param callback The callback function @@ -172,29 +128,14 @@ class AlarmControlPanel : public EntityBase { uint32_t last_update_; // the call control function virtual void control(const AlarmControlPanelCall &call) = 0; - // state callback - CallbackManager state_callback_{}; - // trigger callback - CallbackManager triggered_callback_{}; - // arming callback - CallbackManager arming_callback_{}; - // pending callback - CallbackManager pending_callback_{}; - // armed_home callback - CallbackManager armed_home_callback_{}; - // armed_night callback - CallbackManager armed_night_callback_{}; - // armed_away callback - CallbackManager armed_away_callback_{}; - // disarmed callback - CallbackManager disarmed_callback_{}; - // clear callback - CallbackManager cleared_callback_{}; + // state callback - triggers check get_state() for specific state + LazyCallbackManager state_callback_{}; + // clear callback - fires when leaving TRIGGERED state + LazyCallbackManager cleared_callback_{}; // chime callback - CallbackManager chime_callback_{}; + LazyCallbackManager chime_callback_{}; // ready callback - CallbackManager ready_callback_{}; + LazyCallbackManager ready_callback_{}; }; -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp index 7bb9b9989c..5e98d58368 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { static const char *const TAG = "alarm_control_panel"; @@ -99,5 +98,4 @@ void AlarmControlPanelCall::perform() { } } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.h b/esphome/components/alarm_control_panel/alarm_control_panel_call.h index 034e3142da..cff00900dd 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.h @@ -6,8 +6,7 @@ #include "esphome/core/helpers.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { class AlarmControlPanel; @@ -36,5 +35,4 @@ class AlarmControlPanelCall { void validate_(); }; -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp index abe6f51995..862c620497 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp @@ -1,7 +1,6 @@ #include "alarm_control_panel_state.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) { switch (state) { @@ -30,5 +29,4 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat } } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_state.h b/esphome/components/alarm_control_panel/alarm_control_panel_state.h index ad16222dc0..dd0b91f064 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_state.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel_state.h @@ -3,8 +3,7 @@ #include #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { enum AlarmControlPanelState : uint8_t { ACP_STATE_DISARMED = 0, @@ -25,5 +24,4 @@ enum AlarmControlPanelState : uint8_t { */ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state); -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index db2ef78158..ce5ceadb47 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -3,9 +3,9 @@ #include "esphome/core/automation.h" #include "alarm_control_panel.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { +/// Trigger on any state change class StateTrigger : public Trigger<> { public: explicit StateTrigger(AlarmControlPanel *alarm_control_panel) { @@ -13,55 +13,30 @@ class StateTrigger : public Trigger<> { } }; -class TriggeredTrigger : public Trigger<> { +/// Template trigger that fires when entering a specific state +template class StateEnterTrigger : public Trigger<> { public: - explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); }); + explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) { + alarm_control_panel->add_on_state_callback([this]() { + if (this->alarm_control_panel_->get_state() == State) + this->trigger(); + }); } + + protected: + AlarmControlPanel *alarm_control_panel_; }; -class ArmingTrigger : public Trigger<> { - public: - explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); }); - } -}; - -class PendingTrigger : public Trigger<> { - public: - explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); }); - } -}; - -class ArmedHomeTrigger : public Trigger<> { - public: - explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); }); - } -}; - -class ArmedNightTrigger : public Trigger<> { - public: - explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); }); - } -}; - -class ArmedAwayTrigger : public Trigger<> { - public: - explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); }); - } -}; - -class DisarmedTrigger : public Trigger<> { - public: - explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); }); - } -}; +// Type aliases for state-specific triggers +using TriggeredTrigger = StateEnterTrigger; +using ArmingTrigger = StateEnterTrigger; +using PendingTrigger = StateEnterTrigger; +using ArmedHomeTrigger = StateEnterTrigger; +using ArmedNightTrigger = StateEnterTrigger; +using ArmedAwayTrigger = StateEnterTrigger; +using DisarmedTrigger = StateEnterTrigger; +/// Trigger when leaving TRIGGERED state (alarm cleared) class ClearedTrigger : public Trigger<> { public: explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { @@ -69,6 +44,7 @@ class ClearedTrigger : public Trigger<> { } }; +/// Trigger on chime event (zone opened while disarmed) class ChimeTrigger : public Trigger<> { public: explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) { @@ -76,6 +52,7 @@ class ChimeTrigger : public Trigger<> { } }; +/// Trigger on ready state change class ReadyTrigger : public Trigger<> { public: explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) { @@ -187,5 +164,4 @@ template class AlarmControlPanelCondition : public Condition(0xa9), 0x7b, static_cast(0xb8), static_cast(0x85), 0x0, - 0x1a, 0x28, static_cast(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast(0xd1), - static_cast(0xff), static_cast(0x9c), static_cast(0x85)}); +static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = espbt::ESPBTUUID::from_raw( + {0xa9, 0x7b, 0xb8, 0x85, 0x00, 0x1a, 0x28, 0xaa, 0x2a, 0x43, 0x6e, 0x03, 0xd1, 0xff, 0x9c, 0x85}); static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13; static const size_t GENI_RESPONSE_TYPE_LENGTH = 8; diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 2693224a97..5054488089 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -67,8 +67,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { - ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); - ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); + ESP_LOGW(TAG, + "[%s] No control service found at device, not an Anova..?\n" + "[%s] Note, this component does not currently support Anova Nano.", + this->get_name().c_str(), this->get_name().c_str()); break; } this->char_handle_ = chr->handle; diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 88618acef4..9bff9f5635 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -4,6 +4,7 @@ import logging from esphome import automation from esphome.automation import Condition import esphome.codegen as cg +from esphome.components.logger import request_log_listener from esphome.config_helpers import get_logger_level import esphome.config_validation as cv from esphome.const import ( @@ -226,32 +227,6 @@ def _encryption_schema(config): return ENCRYPTION_SCHEMA(config) -def _validate_api_config(config: ConfigType) -> ConfigType: - """Validate API configuration with mutual exclusivity check and deprecation warning.""" - # Check if both password and encryption are configured - has_password = CONF_PASSWORD in config and config[CONF_PASSWORD] - has_encryption = CONF_ENCRYPTION in config - - if has_password and has_encryption: - raise cv.Invalid( - "The 'password' and 'encryption' options are mutually exclusive. " - "The API client only supports one authentication method at a time. " - "Please remove one of them. " - "Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. " - "We strongly recommend using 'encryption' instead for better security." - ) - - # Warn about password deprecation - if has_password: - _LOGGER.warning( - "API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. " - "Please migrate to the 'encryption' configuration. " - "See https://esphome.io/components/api/#configuration-variables" - ) - - return config - - def _consume_api_sockets(config: ConfigType) -> ConfigType: """Register socket needs for API component.""" from esphome.components import socket @@ -268,7 +243,17 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(APIServer), cv.Optional(CONF_PORT, default=6053): cv.port, - cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, + # Removed in 2026.1.0 - kept to provide helpful error message + cv.Optional(CONF_PASSWORD): cv.invalid( + "The 'password' option has been removed in ESPHome 2026.1.0.\n" + "Password authentication was deprecated in May 2022.\n" + "Please migrate to encryption for secure API communication:\n\n" + "api:\n" + " encryption:\n" + " key: !secret api_encryption_key\n\n" + "Generate a key with: openssl rand -base64 32\n" + "Or visit https://esphome.io/components/api/#configuration-variables" + ), cv.Optional( CONF_REBOOT_TIMEOUT, default="15min" ): cv.positive_time_period_milliseconds, @@ -330,7 +315,6 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.rename_key(CONF_SERVICES, CONF_ACTIONS), - _validate_api_config, _consume_api_sockets, ) @@ -343,10 +327,10 @@ async def to_code(config: ConfigType) -> None: # Track controller registration for StaticVector sizing CORE.register_controller() + # Request a log listener slot for API log streaming + request_log_listener() + cg.add(var.set_port(config[CONF_PORT])) - if config[CONF_PASSWORD]: - cg.add_define("USE_API_PASSWORD") - cg.add(var.set_password(config[CONF_PASSWORD])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) if CONF_LISTEN_BACKLOG in config: diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 50af5061c0..597da25883 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -7,10 +7,7 @@ service APIConnection { option (needs_setup_connection) = false; option (needs_authentication) = false; } - rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) { - option (needs_setup_connection) = false; - option (needs_authentication) = false; - } + // REMOVED in ESPHome 2026.1.0: rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) rpc disconnect (DisconnectRequest) returns (DisconnectResponse) { option (needs_setup_connection) = false; option (needs_authentication) = false; @@ -69,6 +66,8 @@ service APIConnection { rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {} rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {} + + rpc infrared_rf_transmit_raw_timings(InfraredRFTransmitRawTimingsRequest) returns (void) {} } @@ -82,14 +81,13 @@ service APIConnection { // * VarInt denoting the type of message. // * The message object encoded as a ProtoBuf message -// The connection is established in 4 steps: +// The connection is established in 2 steps: // * First, the client connects to the server and sends a "Hello Request" identifying itself -// * The server responds with a "Hello Response" and selects the protocol version -// * After receiving this message, the client attempts to authenticate itself using -// the password and a "Connect Request" -// * The server responds with a "Connect Response" and notifies of invalid password. +// * The server responds with a "Hello Response" and the connection is authenticated // If anything in this initial process fails, the connection must immediately closed // by both sides and _no_ disconnection message is to be sent. +// Note: Password authentication via AuthenticationRequest/AuthenticationResponse (message IDs 3, 4) +// was removed in ESPHome 2026.1.0. Those message IDs are reserved and should not be reused. // Message sent at the beginning of each connection // Can only be sent by the client and only at the beginning of the connection @@ -102,7 +100,7 @@ message HelloRequest { // For example "Home Assistant" // Not strictly necessary to send but nice for debugging // purposes. - string client_info = 1 [(pointer_to_buffer) = true]; + string client_info = 1; uint32 api_version_major = 2; uint32 api_version_minor = 3; } @@ -130,25 +128,23 @@ message HelloResponse { string name = 4; } -// Message sent at the beginning of each connection to authenticate the client -// Can only be sent by the client and only at the beginning of the connection +// DEPRECATED in ESPHome 2026.1.0 - Password authentication is no longer supported. +// These messages are kept for protocol documentation but are not processed by the server. +// Use noise encryption instead: https://esphome.io/components/api/#configuration-variables message AuthenticationRequest { option (id) = 3; option (source) = SOURCE_CLIENT; option (no_delay) = true; - option (ifdef) = "USE_API_PASSWORD"; + option deprecated = true; - // The password to log in with - string password = 1 [(pointer_to_buffer) = true]; + string password = 1; } -// Confirmation of successful connection. After this the connection is available for all traffic. -// Can only be sent by the server and only at the beginning of the connection message AuthenticationResponse { option (id) = 4; option (source) = SOURCE_SERVER; option (no_delay) = true; - option (ifdef) = "USE_API_PASSWORD"; + option deprecated = true; bool invalid_password = 1; } @@ -205,7 +201,9 @@ message DeviceInfoResponse { option (id) = 10; option (source) = SOURCE_SERVER; - bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"]; + // Deprecated in ESPHome 2026.1.0, but kept for backward compatibility + // with older ESPHome versions that still send this field. + bool uses_password = 1 [deprecated = true]; // The name of the node, given by "App.set_name()" string name = 2; @@ -579,7 +577,7 @@ message LightCommandRequest { bool has_flash_length = 16; uint32 flash_length = 17; bool has_effect = 18; - string effect = 19 [(pointer_to_buffer) = true]; + string effect = 19; uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"]; } @@ -767,7 +765,7 @@ message SubscribeHomeassistantServicesRequest { message HomeassistantServiceMap { string key = 1; - string value = 2 [(no_zero_copy) = true]; + string value = 2; } message HomeassistantActionRequest { @@ -783,7 +781,7 @@ message HomeassistantActionRequest { bool is_event = 5; uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"]; bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; - string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; + string response_template = 8 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; } // Message sent by Home Assistant to ESPHome with service call response data @@ -796,7 +794,7 @@ message HomeassistantActionResponse { uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest bool success = 2; // Whether the service call succeeded string error_message = 3; // Error message if success = false - bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; + bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"]; } // ==================== IMPORT HOME ASSISTANT STATES ==================== @@ -841,7 +839,7 @@ message GetTimeResponse { option (no_delay) = true; fixed32 epoch_seconds = 1; - string timezone = 2 [(pointer_to_buffer) = true]; + string timezone = 2; } // ==================== USER-DEFINES SERVICES ==================== @@ -1101,6 +1099,85 @@ message ClimateCommandRequest { uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"]; } +// ==================== WATER_HEATER ==================== +enum WaterHeaterMode { + WATER_HEATER_MODE_OFF = 0; + WATER_HEATER_MODE_ECO = 1; + WATER_HEATER_MODE_ELECTRIC = 2; + WATER_HEATER_MODE_PERFORMANCE = 3; + WATER_HEATER_MODE_HIGH_DEMAND = 4; + WATER_HEATER_MODE_HEAT_PUMP = 5; + WATER_HEATER_MODE_GAS = 6; +} + +message ListEntitiesWaterHeaterResponse { + option (id) = 132; + option (base_class) = "InfoResponseProtoMessage"; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_WATER_HEATER"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"]; + bool disabled_by_default = 5; + EntityCategory entity_category = 6; + uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"]; + float min_temperature = 8; + float max_temperature = 9; + float target_temperature_step = 10; + repeated WaterHeaterMode supported_modes = 11 [(container_pointer_no_template) = "water_heater::WaterHeaterModeMask"]; + // Bitmask of WaterHeaterFeature flags + uint32 supported_features = 12; +} + +message WaterHeaterStateResponse { + option (id) = 133; + option (base_class) = "StateResponseProtoMessage"; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_WATER_HEATER"; + option (no_delay) = true; + + fixed32 key = 1; + float current_temperature = 2; + float target_temperature = 3; + WaterHeaterMode mode = 4; + uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"]; + // Bitmask of current state flags (bit 0 = away, bit 1 = on) + uint32 state = 6; + float target_temperature_low = 7; + float target_temperature_high = 8; +} + +// Bitmask for WaterHeaterCommandRequest.has_fields +enum WaterHeaterCommandHasField { + WATER_HEATER_COMMAND_HAS_NONE = 0; + WATER_HEATER_COMMAND_HAS_MODE = 1; + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2; + WATER_HEATER_COMMAND_HAS_STATE = 4; + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8; + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16; +} + +message WaterHeaterCommandRequest { + option (id) = 134; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_WATER_HEATER"; + option (no_delay) = true; + option (base_class) = "CommandProtoMessage"; + + fixed32 key = 1; + // Bitmask of which fields are set (see WaterHeaterCommandHasField) + uint32 has_fields = 2; + WaterHeaterMode mode = 3; + float target_temperature = 4; + uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"]; + // State flags bitmask (bit 0 = away, bit 1 = on) + uint32 state = 6; + float target_temperature_low = 7; + float target_temperature_high = 8; +} + // ==================== NUMBER ==================== enum NumberMode { NUMBER_MODE_AUTO = 0; @@ -1195,7 +1272,7 @@ message SelectCommandRequest { option (base_class) = "CommandProtoMessage"; fixed32 key = 1; - string state = 2 [(pointer_to_buffer) = true]; + string state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -1213,7 +1290,7 @@ message ListEntitiesSirenResponse { string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; bool disabled_by_default = 6; - repeated string tones = 7; + repeated string tones = 7 [(container_pointer_no_template) = "FixedVector"]; bool supports_duration = 8; bool supports_volume = 9; EntityCategory entity_category = 10; @@ -1613,7 +1690,7 @@ message BluetoothGATTWriteRequest { uint32 handle = 2; bool response = 3; - bytes data = 4 [(pointer_to_buffer) = true]; + bytes data = 4; } message BluetoothGATTReadDescriptorRequest { @@ -1633,7 +1710,7 @@ message BluetoothGATTWriteDescriptorRequest { uint64 address = 1; uint32 handle = 2; - bytes data = 3 [(pointer_to_buffer) = true]; + bytes data = 3; } message BluetoothGATTNotifyRequest { @@ -1858,7 +1935,7 @@ message VoiceAssistantAudio { option (source) = SOURCE_BOTH; option (ifdef) = "USE_VOICE_ASSISTANT"; - bytes data = 1; + bytes data = 1 [(pointer_to_buffer) = true]; bool end = 2; } @@ -2346,7 +2423,7 @@ message ZWaveProxyFrame { option (ifdef) = "USE_ZWAVE_PROXY"; option (no_delay) = true; - bytes data = 1 [(pointer_to_buffer) = true]; + bytes data = 1; } enum ZWaveProxyRequestType { @@ -2360,5 +2437,51 @@ message ZWaveProxyRequest { option (ifdef) = "USE_ZWAVE_PROXY"; ZWaveProxyRequestType type = 1; - bytes data = 2 [(pointer_to_buffer) = true]; + bytes data = 2; +} + +// ==================== INFRARED ==================== +// Note: Feature and capability flag enums are defined in +// esphome/components/infrared/infrared.h + +// Listing of infrared instances +message ListEntitiesInfraredResponse { + option (id) = 135; + option (base_class) = "InfoResponseProtoMessage"; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_INFRARED"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"]; + bool disabled_by_default = 5; + EntityCategory entity_category = 6; + uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"]; + uint32 capabilities = 8; // Bitfield of InfraredCapabilityFlags +} + +// Command to transmit infrared/RF data using raw timings +message InfraredRFTransmitRawTimingsRequest { + option (id) = 136; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_IR_RF"; + + uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"]; + fixed32 key = 2; // Key identifying the transmitter instance + uint32 carrier_frequency = 3; // Carrier frequency in Hz + uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.) + repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off) +} + +// Event message for received infrared/RF data +message InfraredRFReceiveEvent { + option (id) = 137; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_IR_RF"; + option (no_delay) = true; + + uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"]; + fixed32 key = 2; // Key identifying the receiver instance + repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5186e5afda..0804985cc5 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #ifdef USE_ESP8266 #include @@ -42,6 +43,12 @@ #ifdef USE_ZWAVE_PROXY #include "esphome/components/zwave_proxy/zwave_proxy.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif +#ifdef USE_INFRARED +#include "esphome/components/infrared/infrared.h" +#endif namespace esphome::api { @@ -93,21 +100,18 @@ static const int CAMERA_STOP_STREAM = 5000; return; #endif // USE_DEVICES -APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) - : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { +APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) : parent_(parent) { #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) auto &noise_ctx = parent->get_noise_ctx(); if (noise_ctx.has_psk()) { - this->helper_ = - std::unique_ptr{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)}; + this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), noise_ctx)}; } else { - this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; } #elif defined(USE_API_PLAINTEXT) - this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; #elif defined(USE_API_NOISE) - this->helper_ = std::unique_ptr{ - new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)}; + this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())}; #else #error "No frame helper defined" #endif @@ -128,11 +132,13 @@ void APIConnection::start() { this->fatal_error_with_log_(LOG_STR("Helper init failed"), err); return; } - this->client_info_.peername = helper_->getpeername(); - this->client_info_.name = this->client_info_.peername; + // Initialize client name with peername (IP address) until Hello message provides actual name + const char *peername = this->helper_->get_client_peername(); + this->helper_->set_client_name(peername, strlen(peername)); } APIConnection::~APIConnection() { + this->destroy_active_iterator_(); #ifdef USE_BLUETOOTH_PROXY if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) { bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); @@ -145,6 +151,32 @@ APIConnection::~APIConnection() { #endif } +void APIConnection::destroy_active_iterator_() { + switch (this->active_iterator_) { + case ActiveIterator::LIST_ENTITIES: + this->iterator_storage_.list_entities.~ListEntitiesIterator(); + break; + case ActiveIterator::INITIAL_STATE: + this->iterator_storage_.initial_state.~InitialStateIterator(); + break; + case ActiveIterator::NONE: + break; + } + this->active_iterator_ = ActiveIterator::NONE; +} + +void APIConnection::begin_iterator_(ActiveIterator type) { + this->destroy_active_iterator_(); + this->active_iterator_ = type; + if (type == ActiveIterator::LIST_ENTITIES) { + new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this); + this->iterator_storage_.list_entities.begin(); + } else { + new (&this->iterator_storage_.initial_state) InitialStateIterator(this); + this->iterator_storage_.initial_state.begin(); + } +} + void APIConnection::loop() { if (this->flags_.next_close) { // requested a disconnect @@ -187,31 +219,42 @@ void APIConnection::loop() { this->process_batch_(); } - if (!this->list_entities_iterator_.completed()) { - this->process_iterator_batch_(this->list_entities_iterator_); - } else if (!this->initial_state_iterator_.completed()) { - this->process_iterator_batch_(this->initial_state_iterator_); - - // If we've completed initial states, process any remaining and clear the flag - if (this->initial_state_iterator_.completed()) { - // Process any remaining batched messages immediately - if (!this->deferred_batch_.empty()) { - this->process_batch_(); + switch (this->active_iterator_) { + case ActiveIterator::LIST_ENTITIES: + if (this->iterator_storage_.list_entities.completed()) { + this->destroy_active_iterator_(); + if (this->flags_.state_subscription) { + this->begin_iterator_(ActiveIterator::INITIAL_STATE); + } + } else { + this->process_iterator_batch_(this->iterator_storage_.list_entities); } - // Now that everything is sent, enable immediate sending for future state changes - this->flags_.should_try_send_immediately = true; - // Release excess memory from buffers that grew during initial sync - this->deferred_batch_.release_buffer(); - this->helper_->release_buffers(); - } + break; + case ActiveIterator::INITIAL_STATE: + if (this->iterator_storage_.initial_state.completed()) { + this->destroy_active_iterator_(); + // Process any remaining batched messages immediately + if (!this->deferred_batch_.empty()) { + this->process_batch_(); + } + // Now that everything is sent, enable immediate sending for future state changes + this->flags_.should_try_send_immediately = true; + // Release excess memory from buffers that grew during initial sync + this->deferred_batch_.release_buffer(); + this->helper_->release_buffers(); + } else { + this->process_iterator_batch_(this->iterator_storage_.initial_state); + } + break; + case ActiveIterator::NONE: + break; } if (this->flags_.sent_ping) { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { on_fatal_error(); - ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(), - this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting")); } } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) { // Only send ping if we're not disconnecting @@ -222,46 +265,29 @@ void APIConnection::loop() { // If we can't send the ping request directly (tx_buffer full), // schedule it at the front of the batch so it will be sent with priority ESP_LOGW(TAG, "Buffer full, ping queued"); - this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE, - PingRequest::ESTIMATED_SIZE); + this->schedule_message_front_(nullptr, PingRequest::MESSAGE_TYPE, PingRequest::ESTIMATED_SIZE); this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings } } -#ifdef USE_CAMERA - if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) { - uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available()); - bool done = this->image_reader_->available() == to_send; - - CameraImageResponse msg; - msg.key = camera::Camera::instance()->get_object_id_hash(); - msg.set_data(this->image_reader_->peek_data_buffer(), to_send); - msg.done = done; -#ifdef USE_DEVICES - msg.device_id = camera::Camera::instance()->get_device_id(); -#endif - - if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) { - this->image_reader_->consume_data(to_send); - if (done) { - this->image_reader_->return_image(); - } - } - } -#endif - #ifdef USE_API_HOMEASSISTANT_STATES if (state_subs_at_ >= 0) { this->process_state_subscriptions_(); } #endif + +#ifdef USE_CAMERA + // Process camera last - state updates are higher priority + // (missing a frame is fine, missing a state update is not) + this->try_send_camera_image_(); +#endif } bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) { // remote initiated disconnect_client // don't close yet, we still need to send the disconnect response // close will happen on next loop - ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected")); this->flags_.next_close = true; DisconnectResponse resp; return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE); @@ -278,7 +304,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess #ifdef HAS_PROTO_MESSAGE_DUMP // If in log-only mode, just log and return if (conn->flags_.log_only_mode) { - conn->log_send_message_(msg.message_name(), msg.dump()); + DumpBuffer dump_buf; + conn->log_send_message_(msg.message_name(), msg.dump_to(dump_buf)); return 1; // Return non-zero to indicate "success" for logging } #endif @@ -334,8 +361,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess #ifdef USE_BINARY_SENSOR bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { - return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state, - BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(binary_sensor, BinarySensorStateResponse::MESSAGE_TYPE, + BinarySensorStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -352,7 +379,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne bool is_single) { auto *binary_sensor = static_cast(entity); ListEntitiesBinarySensorResponse msg; - msg.set_device_class(binary_sensor->get_device_class_ref()); + msg.device_class = binary_sensor->get_device_class_ref(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -361,8 +388,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne #ifdef USE_COVER bool APIConnection::send_cover_state(cover::Cover *cover) { - return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE, - CoverStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(cover, CoverStateResponse::MESSAGE_TYPE, CoverStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -384,7 +410,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c msg.supports_position = traits.get_supports_position(); msg.supports_tilt = traits.get_supports_tilt(); msg.supports_stop = traits.get_supports_stop(); - msg.set_device_class(cover->get_device_class_ref()); + msg.device_class = cover->get_device_class_ref(); return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -402,8 +428,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { #ifdef USE_FAN bool APIConnection::send_fan_state(fan::Fan *fan) { - return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE, - FanStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(fan, FanStateResponse::MESSAGE_TYPE, FanStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -419,7 +444,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co if (traits.supports_direction()) msg.direction = static_cast(fan->direction); if (traits.supports_preset_modes() && fan->has_preset_mode()) - msg.set_preset_mode(StringRef(fan->get_preset_mode())); + msg.preset_mode = fan->get_preset_mode(); return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -447,15 +472,14 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (msg.has_direction) call.set_direction(static_cast(msg.direction)); if (msg.has_preset_mode) - call.set_preset_mode(msg.preset_mode); + call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size()); call.perform(); } #endif #ifdef USE_LIGHT bool APIConnection::send_light_state(light::LightState *light) { - return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE, - LightStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(light, LightStateResponse::MESSAGE_TYPE, LightStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -475,7 +499,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection * resp.cold_white = values.get_cold_white(); resp.warm_white = values.get_warm_white(); if (light->supports_effects()) { - resp.set_effect(light->get_effect_name_ref()); + resp.effect = light->get_effect_name(); } return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -498,7 +522,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c effects_list.init(light_effects.size() + 1); effects_list.push_back("None"); for (auto *effect : light_effects) { - effects_list.push_back(effect->get_name()); + // c_str() is safe as effect names are null-terminated strings from codegen + effects_list.push_back(effect->get_name().c_str()); } } msg.effects = &effects_list; @@ -533,15 +558,14 @@ void APIConnection::light_command(const LightCommandRequest &msg) { if (msg.has_flash_length) call.set_flash_length(msg.flash_length); if (msg.has_effect) - call.set_effect(reinterpret_cast(msg.effect), msg.effect_len); + call.set_effect(msg.effect.c_str(), msg.effect.size()); call.perform(); } #endif #ifdef USE_SENSOR bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { - return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE, - SensorStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(sensor, SensorStateResponse::MESSAGE_TYPE, SensorStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -557,10 +581,10 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * bool is_single) { auto *sensor = static_cast(entity); ListEntitiesSensorResponse msg; - msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref()); + msg.unit_of_measurement = sensor->get_unit_of_measurement_ref(); msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); - msg.set_device_class(sensor->get_device_class_ref()); + msg.device_class = sensor->get_device_class_ref(); msg.state_class = static_cast(sensor->get_state_class()); return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -569,8 +593,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * #ifdef USE_SWITCH bool APIConnection::send_switch_state(switch_::Switch *a_switch) { - return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE, - SwitchStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(a_switch, SwitchStateResponse::MESSAGE_TYPE, SwitchStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -587,7 +610,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * auto *a_switch = static_cast(entity); ListEntitiesSwitchResponse msg; msg.assumed_state = a_switch->assumed_state(); - msg.set_device_class(a_switch->get_device_class_ref()); + msg.device_class = a_switch->get_device_class_ref(); return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -604,15 +627,15 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) { #ifdef USE_TEXT_SENSOR bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { - return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state, - TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(text_sensor, TextSensorStateResponse::MESSAGE_TYPE, + TextSensorStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { auto *text_sensor = static_cast(entity); TextSensorStateResponse resp; - resp.set_state(StringRef(text_sensor->state)); + resp.state = StringRef(text_sensor->state); resp.missing_state = !text_sensor->has_state(); return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); @@ -621,7 +644,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect bool is_single) { auto *text_sensor = static_cast(entity); ListEntitiesTextSensorResponse msg; - msg.set_device_class(text_sensor->get_device_class_ref()); + msg.device_class = text_sensor->get_device_class_ref(); return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -629,8 +652,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect #ifdef USE_CLIMATE bool APIConnection::send_climate_state(climate::Climate *climate) { - return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE, - ClimateStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(climate, ClimateStateResponse::MESSAGE_TYPE, ClimateStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -651,13 +673,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) { - resp.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode())); + resp.custom_fan_mode = climate->get_custom_fan_mode(); } if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); } if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) { - resp.set_custom_preset(StringRef(climate->get_custom_preset())); + resp.custom_preset = climate->get_custom_preset(); } if (traits.get_supports_swing_modes()) resp.swing_mode = static_cast(climate->swing_mode); @@ -712,11 +734,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) - call.set_fan_mode(msg.custom_fan_mode); + call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size()); if (msg.has_preset) call.set_preset(static_cast(msg.preset)); if (msg.has_custom_preset) - call.set_preset(msg.custom_preset); + call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size()); if (msg.has_swing_mode) call.set_swing_mode(static_cast(msg.swing_mode)); call.perform(); @@ -725,8 +747,7 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { #ifdef USE_NUMBER bool APIConnection::send_number_state(number::Number *number) { - return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE, - NumberStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(number, NumberStateResponse::MESSAGE_TYPE, NumberStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -742,9 +763,9 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * bool is_single) { auto *number = static_cast(entity); ListEntitiesNumberResponse msg; - msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref()); + msg.unit_of_measurement = number->traits.get_unit_of_measurement_ref(); msg.mode = static_cast(number->traits.get_mode()); - msg.set_device_class(number->traits.get_device_class_ref()); + msg.device_class = number->traits.get_device_class_ref(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); msg.step = number->traits.get_step(); @@ -760,8 +781,7 @@ void APIConnection::number_command(const NumberCommandRequest &msg) { #ifdef USE_DATETIME_DATE bool APIConnection::send_date_state(datetime::DateEntity *date) { - return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE, - DateStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(date, DateStateResponse::MESSAGE_TYPE, DateStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -789,8 +809,7 @@ void APIConnection::date_command(const DateCommandRequest &msg) { #ifdef USE_DATETIME_TIME bool APIConnection::send_time_state(datetime::TimeEntity *time) { - return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE, - TimeStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(time, TimeStateResponse::MESSAGE_TYPE, TimeStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -818,8 +837,8 @@ void APIConnection::time_command(const TimeCommandRequest &msg) { #ifdef USE_DATETIME_DATETIME bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { - return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state, - DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(datetime, DateTimeStateResponse::MESSAGE_TYPE, + DateTimeStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -849,15 +868,14 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { #ifdef USE_TEXT bool APIConnection::send_text_state(text::Text *text) { - return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE, - TextStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(text, TextStateResponse::MESSAGE_TYPE, TextStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { auto *text = static_cast(entity); TextStateResponse resp; - resp.set_state(StringRef(text->state)); + resp.state = StringRef(text->state); resp.missing_state = !text->has_state(); return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -869,7 +887,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co msg.mode = static_cast(text->traits.get_mode()); msg.min_length = text->traits.get_min_length(); msg.max_length = text->traits.get_max_length(); - msg.set_pattern(text->traits.get_pattern_ref()); + msg.pattern = text->traits.get_pattern_ref(); return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -882,15 +900,14 @@ void APIConnection::text_command(const TextCommandRequest &msg) { #ifdef USE_SELECT bool APIConnection::send_select_state(select::Select *select) { - return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE, - SelectStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(select, SelectStateResponse::MESSAGE_TYPE, SelectStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { auto *select = static_cast(entity); SelectStateResponse resp; - resp.set_state(StringRef(select->current_option())); + resp.state = select->current_option(); resp.missing_state = !select->has_state(); return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -905,7 +922,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection * } void APIConnection::select_command(const SelectCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) - call.set_option(reinterpret_cast(msg.state), msg.state_len); + call.set_option(msg.state.c_str(), msg.state.size()); call.perform(); } #endif @@ -915,7 +932,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection * bool is_single) { auto *button = static_cast(entity); ListEntitiesButtonResponse msg; - msg.set_device_class(button->get_device_class_ref()); + msg.device_class = button->get_device_class_ref(); return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -927,8 +944,7 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg #ifdef USE_LOCK bool APIConnection::send_lock_state(lock::Lock *a_lock) { - return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE, - LockStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(a_lock, LockStateResponse::MESSAGE_TYPE, LockStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -968,8 +984,7 @@ void APIConnection::lock_command(const LockCommandRequest &msg) { #ifdef USE_VALVE bool APIConnection::send_valve_state(valve::Valve *valve) { - return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE, - ValveStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(valve, ValveStateResponse::MESSAGE_TYPE, ValveStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -984,7 +999,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c auto *valve = static_cast(entity); ListEntitiesValveResponse msg; auto traits = valve->get_traits(); - msg.set_device_class(valve->get_device_class_ref()); + msg.device_class = valve->get_device_class_ref(); msg.assumed_state = traits.get_is_assumed_state(); msg.supports_position = traits.get_supports_position(); msg.supports_stop = traits.get_supports_stop(); @@ -1003,8 +1018,8 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) { #ifdef USE_MEDIA_PLAYER bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { - return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state, - MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(media_player, MediaPlayerStateResponse::MESSAGE_TYPE, + MediaPlayerStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1029,7 +1044,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec for (auto &supported_format : traits.get_supported_formats()) { msg.supported_formats.emplace_back(); auto &media_format = msg.supported_formats.back(); - media_format.set_format(StringRef(supported_format.format)); + media_format.format = StringRef(supported_format.format); media_format.sample_rate = supported_format.sample_rate; media_format.num_channels = supported_format.num_channels; media_format.purpose = static_cast(supported_format.purpose); @@ -1057,6 +1072,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { #endif #ifdef USE_CAMERA +void APIConnection::try_send_camera_image_() { + if (!this->image_reader_) + return; + + // Send as many chunks as possible without blocking + while (this->image_reader_->available()) { + if (!this->helper_->can_write_without_blocking()) + return; + + uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available()); + bool done = this->image_reader_->available() == to_send; + + CameraImageResponse msg; + msg.key = camera::Camera::instance()->get_object_id_hash(); + msg.set_data(this->image_reader_->peek_data_buffer(), to_send); + msg.done = done; +#ifdef USE_DEVICES + msg.device_id = camera::Camera::instance()->get_device_id(); +#endif + + if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) { + return; // Send failed, try again later + } + this->image_reader_->consume_data(to_send); + if (done) { + this->image_reader_->return_image(); + return; + } + } +} void APIConnection::set_camera_state(std::shared_ptr image) { if (!this->flags_.state_subscription) return; @@ -1064,8 +1109,11 @@ void APIConnection::set_camera_state(std::shared_ptr image) return; if (this->image_reader_->available()) return; - if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) + if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) { this->image_reader_->set_image(std::move(image)); + // Try to send immediately to reduce latency + this->try_send_camera_image_(); + } } uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1094,9 +1142,8 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { if (homeassistant::global_homeassistant_time != nullptr) { homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds); #ifdef USE_TIME_TIMEZONE - if (value.timezone_len > 0) { - homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast(value.timezone), - value.timezone_len); + if (!value.timezone.empty()) { + homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size()); } #endif } @@ -1207,8 +1254,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA for (auto &wake_word : config.available_wake_words) { resp.available_wake_words.emplace_back(); auto &resp_wake_word = resp.available_wake_words.back(); - resp_wake_word.set_id(StringRef(wake_word.id)); - resp_wake_word.set_wake_word(StringRef(wake_word.wake_word)); + resp_wake_word.id = StringRef(wake_word.id); + resp_wake_word.wake_word = StringRef(wake_word.wake_word); for (const auto &lang : wake_word.trained_languages) { resp_wake_word.trained_languages.push_back(lang); } @@ -1223,8 +1270,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA resp.available_wake_words.emplace_back(); auto &resp_wake_word = resp.available_wake_words.back(); - resp_wake_word.set_id(StringRef(wake_word.id)); - resp_wake_word.set_wake_word(StringRef(wake_word.wake_word)); + resp_wake_word.id = StringRef(wake_word.id); + resp_wake_word.wake_word = StringRef(wake_word.wake_word); for (const auto &lang : wake_word.trained_languages) { resp_wake_word.trained_languages.push_back(lang); } @@ -1254,8 +1301,7 @@ void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) { #ifdef USE_ALARM_CONTROL_PANEL bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { - return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, - AlarmControlPanelStateResponse::MESSAGE_TYPE, + return this->send_message_smart_(a_alarm_control_panel, AlarmControlPanelStateResponse::MESSAGE_TYPE, AlarmControlPanelStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, @@ -1306,15 +1352,68 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe } #endif -#ifdef USE_EVENT -void APIConnection::send_event(event::Event *event, const char *event_type) { - this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, - EventResponse::ESTIMATED_SIZE); +#ifdef USE_WATER_HEATER +bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) { + return this->send_message_smart_(water_heater, WaterHeaterStateResponse::MESSAGE_TYPE, + WaterHeaterStateResponse::ESTIMATED_SIZE); } -uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, +uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single) { + auto *wh = static_cast(entity); + WaterHeaterStateResponse resp; + resp.mode = static_cast(wh->get_mode()); + resp.current_temperature = wh->get_current_temperature(); + resp.target_temperature = wh->get_target_temperature(); + resp.target_temperature_low = wh->get_target_temperature_low(); + resp.target_temperature_high = wh->get_target_temperature_high(); + resp.state = wh->get_state(); + resp.key = wh->get_object_id_hash(); + + return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); +} +uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single) { + auto *wh = static_cast(entity); + ListEntitiesWaterHeaterResponse msg; + auto traits = wh->get_traits(); + msg.min_temperature = traits.get_min_temperature(); + msg.max_temperature = traits.get_max_temperature(); + msg.target_temperature_step = traits.get_target_temperature_step(); + msg.supported_modes = &traits.get_supported_modes(); + msg.supported_features = traits.get_feature_flags(); + return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size, + is_single); +} + +void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) { + ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater) + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE) + call.set_mode(static_cast(msg.mode)); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE) + call.set_target_temperature(msg.target_temperature); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW) + call.set_target_temperature_low(msg.target_temperature_low); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH) + call.set_target_temperature_high(msg.target_temperature_high); + if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE) { + call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0); + call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0); + } + call.perform(); +} +#endif + +#ifdef USE_EVENT +// Event is a special case - unlike other entities with simple state fields, +// events store their state in a member accessed via obj->get_last_event_type() +void APIConnection::send_event(event::Event *event) { + this->send_message_smart_(event, EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE, + event->get_last_event_type_index()); +} +uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn, uint32_t remaining_size, bool is_single) { EventResponse resp; - resp.set_event_type(StringRef(event_type)); + resp.event_type = event_type; return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1322,17 +1421,45 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c bool is_single) { auto *event = static_cast(entity); ListEntitiesEventResponse msg; - msg.set_device_class(event->get_device_class_ref()); + msg.device_class = event->get_device_class_ref(); msg.event_types = &event->get_event_types(); return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } #endif +#ifdef USE_IR_RF +void APIConnection::infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) { + // TODO: When RF is implemented, add a field to the message to distinguish IR vs RF + // and dispatch to the appropriate entity type based on that field. +#ifdef USE_INFRARED + ENTITY_COMMAND_MAKE_CALL(infrared::Infrared, infrared, infrared) + call.set_carrier_frequency(msg.carrier_frequency); + call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_); + call.set_repeat_count(msg.repeat_count); + call.perform(); +#endif +} + +void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) { + this->send_message(msg, InfraredRFReceiveEvent::MESSAGE_TYPE); +} +#endif + +#ifdef USE_INFRARED +uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single) { + auto *infrared = static_cast(entity); + ListEntitiesInfraredResponse msg; + msg.capabilities = infrared->get_capability_flags(); + return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size, + is_single); +} +#endif + #ifdef USE_UPDATE bool APIConnection::send_update_state(update::UpdateEntity *update) { - return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE, - UpdateStateResponse::ESTIMATED_SIZE); + return this->send_message_smart_(update, UpdateStateResponse::MESSAGE_TYPE, UpdateStateResponse::ESTIMATED_SIZE); } uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) { @@ -1345,11 +1472,11 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection resp.has_progress = true; resp.progress = update->update_info.progress; } - resp.set_current_version(StringRef(update->update_info.current_version)); - resp.set_latest_version(StringRef(update->update_info.latest_version)); - resp.set_title(StringRef(update->update_info.title)); - resp.set_release_summary(StringRef(update->update_info.summary)); - resp.set_release_url(StringRef(update->update_info.release_url)); + resp.current_version = StringRef(update->update_info.current_version); + resp.latest_version = StringRef(update->update_info.latest_version); + resp.title = StringRef(update->update_info.title); + resp.release_summary = StringRef(update->update_info.summary); + resp.release_url = StringRef(update->update_info.release_url); } return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1357,7 +1484,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection * bool is_single) { auto *update = static_cast(entity); ListEntitiesUpdateResponse msg; - msg.set_device_class(update->get_device_class_ref()); + msg.device_class = update->get_device_class_ref(); return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); } @@ -1395,9 +1522,10 @@ void APIConnection::complete_authentication_() { } this->flags_.connection_state = static_cast(ConnectionState::AUTHENTICATED); - ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected")); #ifdef USE_API_CLIENT_CONNECTED_TRIGGER - this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername); + this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()), + std::string(this->helper_->get_client_peername())); #endif #ifdef USE_HOMEASSISTANT_TIME if (homeassistant::global_homeassistant_time != nullptr) { @@ -1412,41 +1540,25 @@ void APIConnection::complete_authentication_() { } bool APIConnection::send_hello_response(const HelloRequest &msg) { - this->client_info_.name.assign(reinterpret_cast(msg.client_info), msg.client_info_len); - this->client_info_.peername = this->helper_->getpeername(); + // Copy client name with truncation if needed (set_client_name handles truncation) + this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size()); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; - ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(), - this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_); + ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(), + this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 13; + resp.api_version_minor = 14; // Send only the version string - the client only logs this for debugging and doesn't use it otherwise - resp.set_server_info(ESPHOME_VERSION_REF); - resp.set_name(StringRef(App.get_name())); + resp.server_info = ESPHOME_VERSION_REF; + resp.name = StringRef(App.get_name()); -#ifdef USE_API_PASSWORD - // Password required - wait for authentication - this->flags_.connection_state = static_cast(ConnectionState::CONNECTED); -#else - // No password configured - auto-authenticate + // Auto-authenticate - password auth was removed in ESPHome 2026.1.0 this->complete_authentication_(); -#endif return this->send_message(resp, HelloResponse::MESSAGE_TYPE); } -#ifdef USE_API_PASSWORD -bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) { - AuthenticationResponse resp; - // bool invalid_password = 1; - resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len); - if (!resp.invalid_password) { - this->complete_authentication_(); - } - return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE); -} -#endif // USE_API_PASSWORD bool APIConnection::send_ping_response(const PingRequest &msg) { PingResponse resp; @@ -1455,24 +1567,24 @@ bool APIConnection::send_ping_response(const PingRequest &msg) { bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { DeviceInfoResponse resp{}; -#ifdef USE_API_PASSWORD - resp.uses_password = true; -#endif - resp.set_name(StringRef(App.get_name())); - resp.set_friendly_name(StringRef(App.get_friendly_name())); + resp.name = StringRef(App.get_name()); + resp.friendly_name = StringRef(App.get_friendly_name()); #ifdef USE_AREAS - resp.set_suggested_area(StringRef(App.get_area())); + resp.suggested_area = StringRef(App.get_area()); #endif // Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes) char mac_address[18]; uint8_t mac[6]; get_mac_address_raw(mac); format_mac_addr_upper(mac, mac_address); - resp.set_mac_address(StringRef(mac_address)); + resp.mac_address = StringRef(mac_address); - resp.set_esphome_version(ESPHOME_VERSION_REF); + resp.esphome_version = ESPHOME_VERSION_REF; - resp.set_compilation_time(App.get_compilation_time_ref()); + // Stack buffer for build time string + char build_time_str[Application::BUILD_TIME_STR_SIZE]; + App.get_build_time_string(build_time_str); + resp.compilation_time = StringRef(build_time_str); // Manufacturer string - define once, handle ESP8266 PROGMEM separately #if defined(USE_ESP8266) || defined(USE_ESP32) @@ -1496,10 +1608,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER; char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)]; memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM)); - resp.set_manufacturer(StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1)); + resp.manufacturer = StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1); #else static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER); - resp.set_manufacturer(MANUFACTURER); + resp.manufacturer = MANUFACTURER; #endif #undef ESPHOME_MANUFACTURER @@ -1507,10 +1619,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD; char model_buf[sizeof(MODEL_PROGMEM)]; memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM)); - resp.set_model(StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1)); + resp.model = StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1); #else static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD); - resp.set_model(MODEL); + resp.model = MODEL; #endif #ifdef USE_DEEP_SLEEP resp.has_deep_sleep = deep_sleep::global_has_deep_sleep; @@ -1523,13 +1635,13 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)]; memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM)); memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM)); - resp.set_project_name(StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1)); - resp.set_project_version(StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1)); + resp.project_name = StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1); + resp.project_version = StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1); #else static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME); static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION); - resp.set_project_name(PROJECT_NAME); - resp.set_project_version(PROJECT_VERSION); + resp.project_name = PROJECT_NAME; + resp.project_version = PROJECT_VERSION; #endif #endif #ifdef USE_WEBSERVER @@ -1540,7 +1652,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { // Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes) char bluetooth_mac[18]; bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac); - resp.set_bluetooth_mac_address(StringRef(bluetooth_mac)); + resp.bluetooth_mac_address = StringRef(bluetooth_mac); #endif #ifdef USE_VOICE_ASSISTANT resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags(); @@ -1559,7 +1671,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { break; auto &device_info = resp.devices[device_index++]; device_info.device_id = device->get_device_id(); - device_info.set_name(StringRef(device->get_name())); + device_info.name = StringRef(device->get_name()); device_info.area_id = device->get_area_id(); } #endif @@ -1570,7 +1682,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { break; auto &area_info = resp.areas[area_index++]; area_info.area_id = area->get_area_id(); - area_info.set_name(StringRef(area->get_name())); + area_info.name = StringRef(area->get_name()); } #endif @@ -1579,15 +1691,38 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { #ifdef USE_API_HOMEASSISTANT_STATES void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { - for (auto &it : this->parent_->get_state_subs()) { - // Compare entity_id and attribute with message fields - bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0); - bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) || - (it.attribute == nullptr && msg.attribute.empty()); + // Skip if entity_id is empty (invalid message) + if (msg.entity_id.empty()) { + return; + } - if (entity_match && attribute_match) { - it.callback(msg.state); + for (auto &it : this->parent_->get_state_subs()) { + // Compare entity_id: check length matches and content matches + size_t entity_id_len = strlen(it.entity_id); + if (entity_id_len != msg.entity_id.size() || + memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) { + continue; } + + // Compare attribute: either both have matching attribute, or both have none + size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0; + if (sub_attr_len != msg.attribute.size() || + (sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) { + continue; + } + + // Create null-terminated state for callback (parse_number needs null-termination) + // HA state max length is 255, so 256 byte buffer covers all cases + char state_buf[256]; + size_t copy_len = msg.state.size(); + if (copy_len >= sizeof(state_buf)) { + copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator + } + if (copy_len > 0) { + memcpy(state_buf, msg.state.c_str(), copy_len); + } + state_buf[copy_len] = '\0'; + it.callback(StringRef(state_buf, copy_len)); } } #endif @@ -1622,20 +1757,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) { // the action list. This ensures async actions (delays, waits) complete first. } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES -void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) { +void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) { ExecuteServiceResponse resp; resp.call_id = call_id; resp.success = success; - resp.set_error_message(StringRef(error_message)); + resp.error_message = error_message; this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON -void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, +void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len) { ExecuteServiceResponse resp; resp.call_id = call_id; resp.success = success; - resp.set_error_message(StringRef(error_message)); + resp.error_message = error_message; resp.response_data = response_data; resp.response_data_len = response_data_len; this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE); @@ -1663,13 +1798,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption resp.success = false; psk_t psk{}; - if (msg.key.empty()) { + if (msg.key_len == 0) { if (this->parent_->clear_noise_psk(true)) { resp.success = true; } else { ESP_LOGW(TAG, "Failed to clear encryption key"); } - } else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) { + } else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); } else if (!this->parent_->save_noise_psk(psk, true)) { ESP_LOGW(TAG, "Failed to save encryption key"); @@ -1704,10 +1839,30 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { return false; } bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { - if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse + const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE); + + if (!this->try_to_clear_buffer(!is_log_message)) { return false; } + // Toggle Nagle's algorithm based on message type to prevent log messages from + // filling the TCP send buffer and crowding out important state updates. + // + // This honors the `no_delay` proto option - SubscribeLogsResponse is the only + // message with `option (no_delay) = false;` in api.proto, indicating it should + // allow Nagle coalescing. This option existed since 2019 but was never implemented. + // + // - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce + // into fewer, larger packets. They flush naturally via TCP delayed ACK timer + // (~200ms), buffer filling, or when a state update triggers a flush. + // + // - All other messages (state updates, responses): Disable Nagle (NODELAY=true) + // for immediate delivery. These are time-sensitive and should not be delayed. + // + // This must be done proactively BEFORE the buffer fills up - checking buffer + // state here would be too late since we'd already be in a degraded state. + this->helper_->set_nodelay(!is_log_message); + APIError err = this->helper_->write_protobuf_packet(message_type, buffer); if (err == APIError::WOULD_BLOCK) return false; @@ -1718,45 +1873,40 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { // Do not set last_traffic_ on send return true; } -#ifdef USE_API_PASSWORD -void APIConnection::on_unauthenticated_access() { - this->on_fatal_error(); - ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); -} -#endif void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); + this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup")); } void APIConnection::on_fatal_error() { this->helper_->close(); this->flags_.remove = true; } -void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, - uint8_t estimated_size) { +void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, + uint8_t aux_data_index) { // Check if we already have a message of this type for this entity // This provides deduplication per entity/message_type combination // O(n) but optimized for RAM and not performance. - for (auto &item : items) { - if (item.entity == entity && item.message_type == message_type) { - // Replace with new creator - item.creator = creator; - return; + // Skip deduplication for events - they are edge-triggered, every occurrence matters +#ifdef USE_EVENT + if (message_type != EventResponse::MESSAGE_TYPE) +#endif + { + for (const auto &item : items) { + if (item.entity == entity && item.message_type == message_type) + return; // Already queued } } - - // No existing item found, add new one - items.emplace_back(entity, creator, message_type, estimated_size); + // No existing item found (or event), add new one + items.push_back({entity, message_type, estimated_size, aux_data_index}); } -void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, - uint8_t estimated_size) { +void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { // Add high priority message and swap to front // This avoids expensive vector::insert which shifts all elements // Note: We only ever have one high-priority message at a time (ping OR disconnect) // If we're disconnecting, pings are blocked, so this simple swap is sufficient - items.emplace_back(entity, creator, message_type, estimated_size); + items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED}); if (items.size() > 1) { // Swap the new high-priority item to the front std::swap(items.front(), items.back()); @@ -1772,9 +1922,9 @@ bool APIConnection::schedule_batch_() { } void APIConnection::process_batch_() { - // Ensure PacketInfo remains trivially destructible for our placement new approach - static_assert(std::is_trivially_destructible::value, - "PacketInfo must remain trivially destructible with this placement-new approach"); + // Ensure MessageInfo remains trivially destructible for our placement new approach + static_assert(std::is_trivially_destructible::value, + "MessageInfo must remain trivially destructible with this placement-new approach"); if (this->deferred_batch_.empty()) { this->flags_.batch_scheduled = false; @@ -1795,31 +1945,29 @@ void APIConnection::process_batch_() { if (num_items == 1) { const auto &item = this->deferred_batch_[0]; - // Let the creator calculate size and encode if it fits - uint16_t payload_size = - item.creator(item.entity, this, std::numeric_limits::max(), true, item.message_type); + // Let dispatch_message_ calculate size and encode if it fits + uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits::max(), true); if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) { #ifdef HAS_PROTO_MESSAGE_DUMP - // Log messages after send attempt for VV debugging - // It's safe to use the buffer for logging at this point regardless of send result + // Log message after send attempt for VV debugging this->log_batch_item_(item); #endif this->clear_batch_(); } else if (payload_size == 0) { - // Message too large + // Message too large to fit in available space ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type); this->clear_batch_(); } return; } - size_t packets_to_process = std::min(num_items, MAX_PACKETS_PER_BATCH); + size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH); - // Stack-allocated array for packet info - alignas(PacketInfo) char packet_info_storage[MAX_PACKETS_PER_BATCH * sizeof(PacketInfo)]; - PacketInfo *packet_info = reinterpret_cast(packet_info_storage); - size_t packet_count = 0; + // Stack-allocated array for message info + alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)]; + MessageInfo *message_info = reinterpret_cast(message_info_storage); + size_t message_count = 0; // Cache these values to avoid repeated virtual calls const uint8_t header_padding = this->helper_->frame_header_padding(); @@ -1850,11 +1998,11 @@ void APIConnection::process_batch_() { uint32_t current_offset = 0; // Process items and encode directly to buffer (up to our limit) - for (size_t i = 0; i < packets_to_process; i++) { + for (size_t i = 0; i < messages_to_process; i++) { const auto &item = this->deferred_batch_[i]; - // Try to encode message - // The creator will calculate overhead to determine if the message fits - uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); + // Try to encode message via dispatch + // The dispatch function calculates overhead to determine if the message fits + uint16_t payload_size = this->dispatch_message_(item, remaining_size, false); if (payload_size == 0) { // Message won't fit, stop processing @@ -1864,11 +2012,11 @@ void APIConnection::process_batch_() { // Message was encoded successfully // payload_size is header_padding + actual payload size + footer_size uint16_t proto_payload_size = payload_size - header_padding - footer_size; - // Use placement new to construct PacketInfo in pre-allocated stack array - // This avoids default-constructing all MAX_PACKETS_PER_BATCH elements - // Explicit destruction is not needed because PacketInfo is trivially destructible, + // Use placement new to construct MessageInfo in pre-allocated stack array + // This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements + // Explicit destruction is not needed because MessageInfo is trivially destructible, // as ensured by the static_assert in its definition. - new (&packet_info[packet_count++]) PacketInfo(item.message_type, current_offset, proto_payload_size); + new (&message_info[message_count++]) MessageInfo(item.message_type, current_offset, proto_payload_size); // Update tracking variables items_processed++; @@ -1892,9 +2040,9 @@ void APIConnection::process_batch_() { shared_buf.resize(shared_buf.size() + footer_size); } - // Send all collected packets - APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf}, - std::span(packet_info, packet_count)); + // Send all collected messages + APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf}, + std::span(message_info, message_count)); if (err != APIError::OK && err != APIError::WOULD_BLOCK) { this->fatal_error_with_log_(LOG_STR("Batch write failed"), err); } @@ -1920,18 +2068,129 @@ void APIConnection::process_batch_() { } } -uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, - bool is_single, uint8_t message_type) const { +// Dispatch message encoding based on message_type +// Switch assigns function pointer, single call site for smaller code size +uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, + bool is_single) { #ifdef USE_EVENT - // Special case: EventResponse uses const char * pointer - if (message_type == EventResponse::MESSAGE_TYPE) { - auto *e = static_cast(entity); - return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single); + // Events need aux_data_index to look up event type from entity + if (item.message_type == EventResponse::MESSAGE_TYPE) { + // Skip if aux_data_index is invalid (should never happen in normal operation) + if (item.aux_data_index == DeferredBatch::AUX_DATA_UNUSED) + return 0; + auto *event = static_cast(item.entity); + return try_send_event_response(event, StringRef::from_maybe_nullptr(event->get_event_type(item.aux_data_index)), + this, remaining_size, is_single); } #endif - // All other message types use function pointers - return data_.function_ptr(entity, conn, remaining_size, is_single); + // All other message types use function pointer lookup via switch + MessageCreatorPtr func = nullptr; + +// Macros to reduce repetitive switch cases +#define CASE_STATE_INFO(entity_name, StateResp, InfoResp) \ + case StateResp::MESSAGE_TYPE: \ + func = &try_send_##entity_name##_state; \ + break; \ + case InfoResp::MESSAGE_TYPE: \ + func = &try_send_##entity_name##_info; \ + break; +#define CASE_INFO_ONLY(entity_name, InfoResp) \ + case InfoResp::MESSAGE_TYPE: \ + func = &try_send_##entity_name##_info; \ + break; + + switch (item.message_type) { +#ifdef USE_BINARY_SENSOR + CASE_STATE_INFO(binary_sensor, BinarySensorStateResponse, ListEntitiesBinarySensorResponse) +#endif +#ifdef USE_COVER + CASE_STATE_INFO(cover, CoverStateResponse, ListEntitiesCoverResponse) +#endif +#ifdef USE_FAN + CASE_STATE_INFO(fan, FanStateResponse, ListEntitiesFanResponse) +#endif +#ifdef USE_LIGHT + CASE_STATE_INFO(light, LightStateResponse, ListEntitiesLightResponse) +#endif +#ifdef USE_SENSOR + CASE_STATE_INFO(sensor, SensorStateResponse, ListEntitiesSensorResponse) +#endif +#ifdef USE_SWITCH + CASE_STATE_INFO(switch, SwitchStateResponse, ListEntitiesSwitchResponse) +#endif +#ifdef USE_BUTTON + CASE_INFO_ONLY(button, ListEntitiesButtonResponse) +#endif +#ifdef USE_TEXT_SENSOR + CASE_STATE_INFO(text_sensor, TextSensorStateResponse, ListEntitiesTextSensorResponse) +#endif +#ifdef USE_CLIMATE + CASE_STATE_INFO(climate, ClimateStateResponse, ListEntitiesClimateResponse) +#endif +#ifdef USE_NUMBER + CASE_STATE_INFO(number, NumberStateResponse, ListEntitiesNumberResponse) +#endif +#ifdef USE_DATETIME_DATE + CASE_STATE_INFO(date, DateStateResponse, ListEntitiesDateResponse) +#endif +#ifdef USE_DATETIME_TIME + CASE_STATE_INFO(time, TimeStateResponse, ListEntitiesTimeResponse) +#endif +#ifdef USE_DATETIME_DATETIME + CASE_STATE_INFO(datetime, DateTimeStateResponse, ListEntitiesDateTimeResponse) +#endif +#ifdef USE_TEXT + CASE_STATE_INFO(text, TextStateResponse, ListEntitiesTextResponse) +#endif +#ifdef USE_SELECT + CASE_STATE_INFO(select, SelectStateResponse, ListEntitiesSelectResponse) +#endif +#ifdef USE_LOCK + CASE_STATE_INFO(lock, LockStateResponse, ListEntitiesLockResponse) +#endif +#ifdef USE_VALVE + CASE_STATE_INFO(valve, ValveStateResponse, ListEntitiesValveResponse) +#endif +#ifdef USE_MEDIA_PLAYER + CASE_STATE_INFO(media_player, MediaPlayerStateResponse, ListEntitiesMediaPlayerResponse) +#endif +#ifdef USE_ALARM_CONTROL_PANEL + CASE_STATE_INFO(alarm_control_panel, AlarmControlPanelStateResponse, ListEntitiesAlarmControlPanelResponse) +#endif +#ifdef USE_WATER_HEATER + CASE_STATE_INFO(water_heater, WaterHeaterStateResponse, ListEntitiesWaterHeaterResponse) +#endif +#ifdef USE_CAMERA + CASE_INFO_ONLY(camera, ListEntitiesCameraResponse) +#endif +#ifdef USE_INFRARED + CASE_INFO_ONLY(infrared, ListEntitiesInfraredResponse) +#endif +#ifdef USE_EVENT + CASE_INFO_ONLY(event, ListEntitiesEventResponse) +#endif +#ifdef USE_UPDATE + CASE_STATE_INFO(update, UpdateStateResponse, ListEntitiesUpdateResponse) +#endif + // Special messages (not entity state/info) + case ListEntitiesDoneResponse::MESSAGE_TYPE: + func = &try_send_list_info_done; + break; + case DisconnectRequest::MESSAGE_TYPE: + func = &try_send_disconnect_request; + break; + case PingRequest::MESSAGE_TYPE: + func = &try_send_ping_request; + break; + default: + return 0; + } + +#undef CASE_STATE_INFO +#undef CASE_INFO_ONLY + + return func(item.entity, this, remaining_size, is_single); } uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, @@ -1962,10 +2221,10 @@ void APIConnection::process_state_subscriptions_() { const auto &it = subs[this->state_subs_at_]; SubscribeHomeAssistantStateResponse resp; - resp.set_entity_id(StringRef(it.entity_id)); + resp.entity_id = StringRef(it.entity_id); // Avoid string copy by using the const char* pointer if it exists - resp.set_attribute(it.attribute != nullptr ? StringRef(it.attribute) : StringRef("")); + resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef(""); resp.once = it.once; if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { @@ -1974,8 +2233,13 @@ void APIConnection::process_state_subscriptions_() { } #endif // USE_API_HOMEASSISTANT_STATES +void APIConnection::log_client_(int level, const LogString *message) { + esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(), + this->helper_->get_client_peername(), LOG_STR_ARG(message)); +} + void APIConnection::log_warning_(const LogString *message, APIError err) { - ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(), + ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(), LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno); } diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index b50be5d0d4..21bf4c4073 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -9,32 +9,24 @@ #include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" +#include "esphome/core/string_ref.h" #include +#include #include namespace esphome::api { -// Client information structure -struct ClientInfo { - std::string name; // Client name from Hello message - std::string peername; // IP:port from socket -}; - // Keepalive timeout in milliseconds static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; // Maximum number of entities to process in a single batch during initial state/info sending -// This was increased from 20 to 24 after removing the unique_id field from entity info messages, -// which reduced message sizes allowing more entities per batch without exceeding packet limits -static constexpr size_t MAX_INITIAL_PER_BATCH = 24; -// Maximum number of packets to process in a single batch (platform-dependent) -// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_ -// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes -#if defined(USE_ESP32) || defined(USE_HOST) -static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HOST has plenty -#else -static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks -#endif +// API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch +// TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then +static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id) +static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id) +// Verify MAX_MESSAGES_PER_BATCH (defined in api_frame_helper.h) can hold the initial batch +static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH, + "MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH"); class APIConnection final : public APIServerConnection { public: @@ -47,8 +39,8 @@ class APIConnection final : public APIServerConnection { void loop(); bool send_list_info_done() { - return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done, - ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE); + return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE, + ListEntitiesDoneResponse::ESTIMATED_SIZE); } #ifdef USE_BINARY_SENSOR bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); @@ -176,8 +168,18 @@ class APIConnection final : public APIServerConnection { void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; #endif +#ifdef USE_WATER_HEATER + bool send_water_heater_state(water_heater::WaterHeater *water_heater); + void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; +#endif + +#ifdef USE_IR_RF + void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override; + void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); +#endif + #ifdef USE_EVENT - void send_event(event::Event *event, const char *event_type); + void send_event(event::Event *event); #endif #ifdef USE_UPDATE @@ -197,16 +199,17 @@ class APIConnection final : public APIServerConnection { void on_get_time_response(const GetTimeResponse &value) override; #endif bool send_hello_response(const HelloRequest &msg) override; -#ifdef USE_API_PASSWORD - bool send_authenticate_response(const AuthenticationRequest &msg) override; -#endif bool send_disconnect_response(const DisconnectRequest &msg) override; bool send_ping_response(const PingRequest &msg) override; bool send_device_info_response(const DeviceInfoRequest &msg) override; - void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } + void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } void subscribe_states(const SubscribeStatesRequest &msg) override { this->flags_.state_subscription = true; - this->initial_state_iterator_.begin(); + // Start initial state iterator only if no iterator is active + // If list_entities is running, we'll start initial_state when it completes + if (this->active_iterator_ == ActiveIterator::NONE) { + this->begin_iterator_(ActiveIterator::INITIAL_STATE); + } } void subscribe_logs(const SubscribeLogsRequest &msg) override { this->flags_.log_subscription = msg.level; @@ -224,9 +227,9 @@ class APIConnection final : public APIServerConnection { #ifdef USE_API_USER_DEFINED_ACTIONS void execute_service(const ExecuteServiceRequest &msg) override; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES - void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message); + void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message, + void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len); #endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #endif // USE_API_USER_DEFINED_ACTION_RESPONSES @@ -251,9 +254,6 @@ class APIConnection final : public APIServerConnection { } void on_fatal_error() override; -#ifdef USE_API_PASSWORD - void on_unauthenticated_access() override; -#endif void on_no_setup_connection() override; ProtoWriteBuffer create_buffer(uint32_t reserve_size) override { // FIXME: ensure no recursive writes can happen @@ -280,13 +280,18 @@ class APIConnection final : public APIServerConnection { bool try_to_clear_buffer(bool log_out_of_space); bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; - const std::string &get_name() const { return this->client_info_.name; } - const std::string &get_peername() const { return this->client_info_.peername; } + const char *get_name() const { return this->helper_->get_client_name(); } + /// Get peer name (IP address) - cached at connection init time + const char *get_peername() const { return this->helper_->get_client_peername(); } protected: // Helper function to handle authentication completion void complete_authentication_(); +#ifdef USE_CAMERA + void try_send_camera_image_(); +#endif + #ifdef USE_API_HOMEASSISTANT_STATES void process_state_subscriptions_(); #endif @@ -310,25 +315,24 @@ class APIConnection final : public APIServerConnection { APIConnection *conn, uint32_t remaining_size, bool is_single) { // Set common fields that are shared by all entity types msg.key = entity->get_object_id_hash(); - // Try to use static reference first to avoid allocation - StringRef static_ref = entity->get_object_id_ref_for_api_(); - // Store dynamic string outside the if-else to maintain lifetime - std::string object_id; - if (!static_ref.empty()) { - msg.set_object_id(static_ref); - } else { - // Dynamic case - need to allocate - object_id = entity->get_object_id(); - msg.set_object_id(StringRef(object_id)); + + // API 1.14+ clients compute object_id client-side from the entity name + // For older clients, we must send object_id for backward compatibility + // See: https://github.com/esphome/backlog/issues/76 + // TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then + // Buffer must remain in scope until encode_message_to_buffer is called + char object_id_buf[OBJECT_ID_MAX_LEN]; + if (!conn->client_supports_api_version(1, 14)) { + msg.object_id = entity->get_object_id_to(object_id_buf); } if (entity->has_own_name()) { - msg.set_name(entity->get_name()); + msg.name = entity->get_name(); } // Set common EntityBase properties #ifdef USE_ENTITY_ICON - msg.set_icon(entity->get_icon_ref()); + msg.icon = entity->get_icon_ref(); #endif msg.disabled_by_default = entity->is_disabled_by_default(); msg.entity_category = static_cast(entity->get_entity_category()); @@ -343,16 +347,24 @@ class APIConnection final : public APIServerConnection { inline bool check_voice_assistant_api_connection_() const; #endif + // Get the max batch size based on client API version + // API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch + // TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly + size_t get_max_batch_size_() const { + return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY; + } + // Helper method to process multiple entities from an iterator in a batch template void process_iterator_batch_(Iterator &iterator) { size_t initial_size = this->deferred_batch_.size(); - while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) { + size_t max_batch = this->get_max_batch_size_(); + while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) { iterator.advance(); } // If the batch is full, process it immediately // Note: iterator.advance() already calls schedule_batch_() via schedule_message_() - if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) { + if (this->deferred_batch_.size() >= max_batch) { this->process_batch_(); } } @@ -456,8 +468,18 @@ class APIConnection final : public APIServerConnection { static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single); #endif +#ifdef USE_WATER_HEATER + static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single); + static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single); +#endif +#ifdef USE_INFRARED + static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, + bool is_single); +#endif #ifdef USE_EVENT - static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn, + static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn, uint32_t remaining_size, bool is_single); static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single); #endif @@ -490,18 +512,27 @@ class APIConnection final : public APIServerConnection { std::unique_ptr helper_; APIServer *parent_; - // Group 2: Larger objects (must be 4-byte aligned) - // These contain vectors/pointers internally, so putting them early ensures good alignment - InitialStateIterator initial_state_iterator_; - ListEntitiesIterator list_entities_iterator_; + // Group 2: Iterator union (saves ~16 bytes vs separate iterators) + // These iterators are never active simultaneously - list_entities runs to completion + // before initial_state begins, so we use a union with explicit construction/destruction. + enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE }; + + union IteratorUnion { + ListEntitiesIterator list_entities; + InitialStateIterator initial_state; + // Constructor/destructor do nothing - use placement new/explicit destructor + IteratorUnion() {} + ~IteratorUnion() {} + } iterator_storage_; + + // Helper methods for iterator lifecycle management + void destroy_active_iterator_(); + void begin_iterator_(ActiveIterator type); #ifdef USE_CAMERA std::unique_ptr image_reader_; #endif - // Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each) - ClientInfo client_info_; - - // Group 4: 4-byte types + // Group 3: 4-byte types uint32_t last_traffic_; #ifdef USE_API_HOMEASSISTANT_STATES int state_subs_at_ = -1; @@ -510,33 +541,17 @@ class APIConnection final : public APIServerConnection { // Function pointer type for message encoding using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); - class MessageCreator { - public: - MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; } - explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; } - - // Call operator - uses message_type to determine union type - uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, - uint8_t message_type) const; - - private: - union Data { - MessageCreatorPtr function_ptr; - const char *const_char_ptr; - } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - }; - // Generic batching mechanism for both state updates and entity info struct DeferredBatch { - struct BatchItem { - EntityBase *entity; // Entity pointer - MessageCreator creator; // Function that creates the message when needed - uint8_t message_type; // Message type for overhead calculation (max 255) - uint8_t estimated_size; // Estimated message size (max 255 bytes) + // Sentinel value for unused aux_data_index + static constexpr uint8_t AUX_DATA_UNUSED = std::numeric_limits::max(); - // Constructor for creating BatchItem - BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) - : entity(entity), creator(creator), message_type(message_type), estimated_size(estimated_size) {} + struct BatchItem { + EntityBase *entity; // 4 bytes - Entity pointer + uint8_t message_type; // 1 byte - Message type for protocol and dispatch + uint8_t estimated_size; // 1 byte - Estimated message size (max 255 bytes) + uint8_t aux_data_index{AUX_DATA_UNUSED}; // 1 byte - For events: index into entity's event_types + // 1 byte padding }; std::vector items; @@ -545,10 +560,11 @@ class APIConnection final : public APIServerConnection { // No pre-allocation - log connections never use batching, and for // connections that do, buffers are released after initial sync anyway - // Add item to the batch - void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); + // Add item to the batch (with deduplication) + void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, + uint8_t aux_data_index = AUX_DATA_UNUSED); // Add item to the front of the batch (for high priority messages like ping) - void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); + void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size); // Clear all items void clear() { @@ -562,6 +578,7 @@ class APIConnection final : public APIServerConnection { bool empty() const { return items.empty(); } size_t size() const { return items.size(); } const BatchItem &operator[](size_t index) const { return items[index]; } + // Release excess capacity - only releases if items already empty void release_buffer() { // Safe to call: batch is processed before release_buffer is called, @@ -608,7 +625,9 @@ class APIConnection final : public APIServerConnection { // 2-byte types immediately after flags_ (no padding between them) uint16_t client_api_version_major_{0}; uint16_t client_api_version_minor_{0}; - // Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary + // 1-byte type to fill padding + ActiveIterator active_iterator_{ActiveIterator::NONE}; + // Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary uint32_t get_batch_delay_ms_() const; // Message will use 8 more bytes than the minimum size, and typical @@ -631,17 +650,15 @@ class APIConnection final : public APIServerConnection { this->flags_.batch_scheduled = false; } -#ifdef HAS_PROTO_MESSAGE_DUMP - // Helper to log a proto message from a MessageCreator object - void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) { - this->flags_.log_only_mode = true; - creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type); - this->flags_.log_only_mode = false; - } + // Dispatch message encoding based on message_type - replaces function pointer storage + // Switch assigns pointer, single call site for smaller code size + uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool is_single); +#ifdef HAS_PROTO_MESSAGE_DUMP void log_batch_item_(const DeferredBatch::BatchItem &item) { - // Use the helper to log the message - this->log_proto_message_(item.entity, item.creator, item.message_type); + this->flags_.log_only_mode = true; + this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true); + this->flags_.log_only_mode = false; } #endif @@ -666,66 +683,36 @@ class APIConnection final : public APIServerConnection { // Helper method to send a message either immediately or via batching // Tries immediate send if should_send_immediately_() returns true and buffer has space // Falls back to batching if immediate send fails or isn't applicable - bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type, - uint8_t estimated_size) { + bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, + uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) { if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) { - // Now actually encode and send - if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) && + DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index}; + if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) && this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { #ifdef HAS_PROTO_MESSAGE_DUMP - // Log the message in verbose mode - this->log_proto_message_(entity, MessageCreator(creator), message_type); + this->log_batch_item_(item); #endif return true; } - - // If immediate send failed, fall through to batching } - - // Fall back to scheduled batching - return this->schedule_message_(entity, creator, message_type, estimated_size); - } - - // Overload for MessageCreator (used by events which need to capture event_type) - bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { - // Try to send immediately if message type should bypass batching and buffer has space - if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) { - // Now actually encode and send - if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) && - this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { -#ifdef HAS_PROTO_MESSAGE_DUMP - // Log the message in verbose mode - this->log_proto_message_(entity, creator, message_type); -#endif - return true; - } - - // If immediate send failed, fall through to batching - } - - // Fall back to scheduled batching - return this->schedule_message_(entity, creator, message_type, estimated_size); + return this->schedule_message_(entity, message_type, estimated_size, aux_data_index); } // Helper function to schedule a deferred message with known message type - bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { - this->deferred_batch_.add_item(entity, creator, message_type, estimated_size); + bool schedule_message_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, + uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) { + this->deferred_batch_.add_item(entity, message_type, estimated_size, aux_data_index); return this->schedule_batch_(); } - // Overload for function pointers (for info messages and current state reads) - bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type, - uint8_t estimated_size) { - return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size); - } - // Helper function to schedule a high priority message at the front of the batch - bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type, - uint8_t estimated_size) { - this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size); + bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { + this->deferred_batch_.add_item_front(entity, message_type, estimated_size); return this->schedule_batch_(); } + // Helper function to log client messages with name and peername + void log_client_(int level, const LogString *message); // Helper function to log API errors with errno void log_warning_(const LogString *message, APIError err); // Helper to handle fatal errors with logging diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 20f8fcaf61..dd44fe9e17 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -1,6 +1,5 @@ #include "api_frame_helper.h" #ifdef USE_API -#include "api_connection.h" // For ClientInfo struct #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -13,12 +12,29 @@ namespace esphome::api { static const char *const TAG = "api.frame_helper"; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t API_MAX_LOG_BYTES = 168; + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS -#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) -#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) +#define LOG_PACKET_RECEIVED(buffer) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Received frame: %s", \ + format_hex_pretty_to(hex_buf_, (buffer).data(), \ + (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ + } while (0) +#define LOG_PACKET_SENDING(data, len) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Sending raw: %s", \ + format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ + } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0) @@ -229,6 +245,8 @@ APIError APIFrameHelper::init_common_() { HELPER_LOG("Bad state for init %d", (int) state_); return APIError::BAD_STATE; } + // Cache peername now while socket is valid - needed for error logging after socket failure + this->socket_->getpeername_to(this->client_peername_); int err = this->socket_->setblocking(false); if (err != 0) { state_ = State::FAILED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index b582bcea9a..27ec1ff915 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -29,24 +29,28 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266 static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms #endif -// Forward declaration -struct ClientInfo; +// Maximum number of messages to batch in a single write operation +// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there) +static constexpr size_t MAX_MESSAGES_PER_BATCH = 34; class ProtoWriteBuffer; +// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars) +static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32; + struct ReadPacketBuffer { const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call) uint16_t data_len; uint16_t type; }; -// Packed packet info structure to minimize memory usage -struct PacketInfo { +// Packed message info structure to minimize memory usage +struct MessageInfo { uint16_t offset; // Offset in buffer where message starts uint16_t payload_size; // Size of the message payload uint8_t message_type; // Message type (0-255) - PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {} + MessageInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {} }; enum class APIError : uint16_t { @@ -82,14 +86,23 @@ const LogString *api_error_to_logstr(APIError err); class APIFrameHelper { public: APIFrameHelper() = default; - explicit APIFrameHelper(std::unique_ptr socket, const ClientInfo *client_info) - : socket_(std::move(socket)), client_info_(client_info) {} + explicit APIFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {} + + // Get client name (null-terminated) + const char *get_client_name() const { return this->client_name_; } + // Get client peername/IP (null-terminated, cached at init time for availability after socket failure) + const char *get_client_peername() const { return this->client_peername_; } + // Set client name from buffer with length (truncates if needed) + void set_client_name(const char *name, size_t len) { + size_t copy_len = std::min(len, sizeof(this->client_name_) - 1); + memcpy(this->client_name_, name, copy_len); + this->client_name_[copy_len] = '\0'; + } virtual ~APIFrameHelper() = default; virtual APIError init() = 0; virtual APIError loop(); virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; } - std::string getpeername() { return socket_->getpeername(); } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } APIError close() { state_ = State::CLOSED; @@ -107,11 +120,32 @@ class APIFrameHelper { } return APIError::OK; } + /// Toggle TCP_NODELAY socket option to control Nagle's algorithm. + /// + /// This is used to allow log messages to coalesce (Nagle enabled) while keeping + /// state updates low-latency (NODELAY enabled). Without this, many small log + /// packets fill the TCP send buffer, crowding out important state updates. + /// + /// State is tracked to minimize setsockopt() overhead - on lwip_raw (ESP8266/RP2040) + /// this is just a boolean assignment; on other platforms it's a lightweight syscall. + /// + /// @param enable true to enable NODELAY (disable Nagle), false to enable Nagle + /// @return true if successful or already in desired state + bool set_nodelay(bool enable) { + if (this->nodelay_enabled_ == enable) + return true; + int val = enable ? 1 : 0; + int err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); + if (err == 0) { + this->nodelay_enabled_ = enable; + } + return err == 0; + } virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0; - // Write multiple protobuf packets in a single operation - // packets contains (message_type, offset, length) for each message in the buffer + // Write multiple protobuf messages in a single operation + // messages contains (message_type, offset, length) for each message in the buffer // The buffer contains all messages with appropriate padding before each - virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) = 0; + virtual APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) = 0; // Get the frame header padding required by this protocol uint8_t frame_header_padding() const { return frame_header_padding_; } // Get the frame footer size required by this protocol @@ -127,12 +161,6 @@ class APIFrameHelper { // Use swap trick since shrink_to_fit() is non-binding and may be ignored std::vector().swap(this->rx_buf_); } - // reusable_iovs_: Safe to release unconditionally. - // Only used within write_protobuf_packets() calls - cleared at start, - // populated with pointers, used for writev(), then function returns. - // The iovecs contain stale pointers after the call (data was either sent - // or copied to tx_buf_), and are cleared on next write_protobuf_packets(). - std::vector().swap(this->reusable_iovs_); } protected: @@ -186,12 +214,12 @@ class APIFrameHelper { // Containers (size varies, but typically 12+ bytes on 32-bit) std::array, API_MAX_SEND_QUEUE> tx_buf_; - std::vector reusable_iovs_; std::vector rx_buf_; - // Pointer to client info (4 bytes on 32-bit) - // Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance. - const ClientInfo *client_info_{nullptr}; + // Client name buffer - stores name from Hello message or initial peername + char client_name_[CLIENT_INFO_NAME_MAX_LEN]{}; + // Cached peername/IP address - captured at init time for availability after socket failure + char client_peername_[socket::SOCKADDR_STR_LEN]{}; // Group smaller types together uint16_t rx_buf_len_ = 0; @@ -201,7 +229,10 @@ class APIFrameHelper { uint8_t tx_buf_head_{0}; uint8_t tx_buf_tail_{0}; uint8_t tx_buf_count_{0}; - // 8 bytes total, 0 bytes padding + // Tracks TCP_NODELAY state to minimize setsockopt() calls. Initialized to true + // since init_common_() enables NODELAY. Used by set_nodelay() to allow log + // messages to coalesce while keeping state updates low-latency. + bool nodelay_enabled_{true}; // Common initialization for both plaintext and noise protocols APIError init_common_(); diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index ae69f0b673..21b0463dfe 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -24,12 +24,29 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit"; #endif static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t API_MAX_LOG_BYTES = 168; + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS -#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) -#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) +#define LOG_PACKET_RECEIVED(buffer) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Received frame: %s", \ + format_hex_pretty_to(hex_buf_, (buffer).data(), \ + (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ + } while (0) +#define LOG_PACKET_SENDING(data, len) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Sending raw: %s", \ + format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ + } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0) @@ -415,12 +432,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { // Resize to include MAC space (required for Noise encryption) buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_); - PacketInfo packet{type, 0, - static_cast(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)}; - return write_protobuf_packets(buffer, std::span(&packet, 1)); + MessageInfo msg{type, 0, + static_cast(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)}; + return write_protobuf_messages(buffer, std::span(&msg, 1)); } -APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) { +APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) { APIError aerr = state_action_(); if (aerr != APIError::OK) { return aerr; @@ -430,20 +447,20 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st return APIError::WOULD_BLOCK; } - if (packets.empty()) { + if (messages.empty()) { return APIError::OK; } uint8_t *buffer_data = buffer.get_buffer()->data(); - this->reusable_iovs_.clear(); - this->reusable_iovs_.reserve(packets.size()); + // Stack-allocated iovec array - no heap allocation + StaticVector iovs; uint16_t total_write_len = 0; - // We need to encrypt each packet in place - for (const auto &packet : packets) { + // We need to encrypt each message in place + for (const auto &msg : messages) { // The buffer already has padding at offset - uint8_t *buf_start = buffer_data + packet.offset; + uint8_t *buf_start = buffer_data + msg.offset; // Write noise header buf_start[0] = 0x01; // indicator @@ -451,10 +468,10 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st // Write message header (to be encrypted) const uint8_t msg_offset = 3; - buf_start[msg_offset] = static_cast(packet.message_type >> 8); // type high byte - buf_start[msg_offset + 1] = static_cast(packet.message_type); // type low byte - buf_start[msg_offset + 2] = static_cast(packet.payload_size >> 8); // data_len high byte - buf_start[msg_offset + 3] = static_cast(packet.payload_size); // data_len low byte + buf_start[msg_offset] = static_cast(msg.message_type >> 8); // type high byte + buf_start[msg_offset + 1] = static_cast(msg.message_type); // type low byte + buf_start[msg_offset + 2] = static_cast(msg.payload_size >> 8); // data_len high byte + buf_start[msg_offset + 3] = static_cast(msg.payload_size); // data_len low byte // payload data is already in the buffer starting at offset + 7 // Make sure we have space for MAC @@ -463,8 +480,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st // Encrypt the message in place NoiseBuffer mbuf; noise_buffer_init(mbuf); - noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size, - 4 + packet.payload_size + frame_footer_size_); + noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + msg.payload_size, + 4 + msg.payload_size + frame_footer_size_); int err = noise_cipherstate_encrypt(send_cipher_, &mbuf); APIError aerr = @@ -476,14 +493,14 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st buf_start[1] = static_cast(mbuf.size >> 8); buf_start[2] = static_cast(mbuf.size); - // Add iovec for this encrypted packet - size_t packet_len = static_cast(3 + mbuf.size); // indicator + size + encrypted data - this->reusable_iovs_.push_back({buf_start, packet_len}); - total_write_len += packet_len; + // Add iovec for this encrypted message + size_t msg_len = static_cast(3 + mbuf.size); // indicator + size + encrypted data + iovs.push_back({buf_start, msg_len}); + total_write_len += msg_len; } - // Send all encrypted packets in one writev call - return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len); + // Send all encrypted messages in one writev call + return this->write_raw_(iovs.data(), iovs.size(), total_write_len); } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { @@ -539,7 +556,8 @@ APIError APINoiseFrameHelper::init_handshake_() { if (aerr != APIError::OK) return aerr; // set_prologue copies it into handshakestate, so we can get rid of it now - prologue_ = {}; + // Use swap idiom to actually release memory (= {} only clears size, not capacity) + std::vector().swap(prologue_); err = noise_handshakestate_start(handshake_); aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED); diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 7eb01058db..183b8c8a51 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -9,8 +9,8 @@ namespace esphome::api { class APINoiseFrameHelper final : public APIFrameHelper { public: - APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx, const ClientInfo *client_info) - : APIFrameHelper(std::move(socket), client_info), ctx_(ctx) { + APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx) + : APIFrameHelper(std::move(socket)), ctx_(ctx) { // Noise header structure: // Pos 0: indicator (0x01) // Pos 1-2: encrypted payload size (16-bit big-endian) @@ -23,7 +23,7 @@ class APINoiseFrameHelper final : public APIFrameHelper { APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; - APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) override; + APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) override; protected: APIError state_action_(); diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index b5d90b2429..3dfd683929 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -1,7 +1,6 @@ #include "api_frame_helper_plaintext.h" #ifdef USE_API #ifdef USE_API_PLAINTEXT -#include "api_connection.h" // For ClientInfo struct #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -18,12 +17,29 @@ namespace esphome::api { static const char *const TAG = "api.plaintext"; -#define HELPER_LOG(msg, ...) \ - ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) +// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t API_MAX_LOG_BYTES = 168; + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__) +#else +#define HELPER_LOG(msg, ...) ((void) 0) +#endif #ifdef HELPER_LOG_PACKETS -#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) -#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str()) +#define LOG_PACKET_RECEIVED(buffer) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Received frame: %s", \ + format_hex_pretty_to(hex_buf_, (buffer).data(), \ + (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ + } while (0) +#define LOG_PACKET_SENDING(data, len) \ + do { \ + char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ + ESP_LOGVV(TAG, "Sending raw: %s", \ + format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ + } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) #define LOG_PACKET_SENDING(data, len) ((void) 0) @@ -216,29 +232,30 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { return APIError::OK; } APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { - PacketInfo packet{type, 0, static_cast(buffer.get_buffer()->size() - frame_header_padding_)}; - return write_protobuf_packets(buffer, std::span(&packet, 1)); + MessageInfo msg{type, 0, static_cast(buffer.get_buffer()->size() - frame_header_padding_)}; + return write_protobuf_messages(buffer, std::span(&msg, 1)); } -APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) { +APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, + std::span messages) { if (state_ != State::DATA) { return APIError::BAD_STATE; } - if (packets.empty()) { + if (messages.empty()) { return APIError::OK; } uint8_t *buffer_data = buffer.get_buffer()->data(); - this->reusable_iovs_.clear(); - this->reusable_iovs_.reserve(packets.size()); + // Stack-allocated iovec array - no heap allocation + StaticVector iovs; uint16_t total_write_len = 0; - for (const auto &packet : packets) { + for (const auto &msg : messages) { // Calculate varint sizes for header layout - uint8_t size_varint_len = api::ProtoSize::varint(static_cast(packet.payload_size)); - uint8_t type_varint_len = api::ProtoSize::varint(static_cast(packet.message_type)); + uint8_t size_varint_len = api::ProtoSize::varint(static_cast(msg.payload_size)); + uint8_t type_varint_len = api::ProtoSize::varint(static_cast(msg.message_type)); uint8_t total_header_len = 1 + size_varint_len + type_varint_len; // Calculate where to start writing the header @@ -266,25 +283,25 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer // // The message starts at offset + frame_header_padding_ // So we write the header starting at offset + frame_header_padding_ - total_header_len - uint8_t *buf_start = buffer_data + packet.offset; + uint8_t *buf_start = buffer_data + msg.offset; uint32_t header_offset = frame_header_padding_ - total_header_len; // Write the plaintext header buf_start[header_offset] = 0x00; // indicator // Encode varints directly into buffer - ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len); - ProtoVarInt(packet.message_type) + ProtoVarInt(msg.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len); + ProtoVarInt(msg.message_type) .encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len); - // Add iovec for this packet (header + payload) - size_t packet_len = static_cast(total_header_len + packet.payload_size); - this->reusable_iovs_.push_back({buf_start + header_offset, packet_len}); - total_write_len += packet_len; + // Add iovec for this message (header + payload) + size_t msg_len = static_cast(total_header_len + msg.payload_size); + iovs.push_back({buf_start + header_offset, msg_len}); + total_write_len += msg_len; } - // Send all packets in one writev call - return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len); + // Send all messages in one writev call + return write_raw_(iovs.data(), iovs.size(), total_write_len); } } // namespace esphome::api diff --git a/esphome/components/api/api_frame_helper_plaintext.h b/esphome/components/api/api_frame_helper_plaintext.h index bba981d26b..96d47e9c7b 100644 --- a/esphome/components/api/api_frame_helper_plaintext.h +++ b/esphome/components/api/api_frame_helper_plaintext.h @@ -7,8 +7,7 @@ namespace esphome::api { class APIPlaintextFrameHelper final : public APIFrameHelper { public: - APIPlaintextFrameHelper(std::unique_ptr socket, const ClientInfo *client_info) - : APIFrameHelper(std::move(socket), client_info) { + explicit APIPlaintextFrameHelper(std::unique_ptr socket) : APIFrameHelper(std::move(socket)) { // Plaintext header structure (worst case): // Pos 0: indicator (0x00) // Pos 1-3: payload size varint (up to 3 bytes) @@ -21,7 +20,7 @@ class APIPlaintextFrameHelper final : public APIFrameHelper { APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; - APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span packets) override; + APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) override; protected: APIError try_read_frame_(); diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index 6b33408e2f..a863f2c7a8 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -27,7 +27,6 @@ extend google.protobuf.MessageOptions { extend google.protobuf.FieldOptions { optional string field_ifdef = 1042; optional uint32 fixed_array_size = 50007; - optional bool no_zero_copy = 50008 [default=false]; optional bool fixed_array_skip_zero = 50009 [default=false]; optional string fixed_array_size_define = 50010; optional string fixed_array_with_length_define = 50011; @@ -80,4 +79,15 @@ extend google.protobuf.FieldOptions { // Example: [(container_pointer_no_template) = "light::ColorModeMask"] // generates: const light::ColorModeMask *supported_color_modes{}; optional string container_pointer_no_template = 50014; + + // packed_buffer: Expose raw packed buffer instead of decoding into container + // When set on a packed repeated field, the generated code stores a pointer + // to the raw protobuf buffer instead of decoding values. This enables + // zero-copy passthrough when the consumer can decode on-demand. + // The field must be a packed repeated field (packed=true). + // Generates three fields: + // - const uint8_t *_data_{nullptr}; + // - uint16_t _length_{0}; + // - uint16_t _count_{0}; + optional bool packed_buffer = 50015 [default=false]; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 4a89ee78e1..743f51dac7 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -23,9 +23,7 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation - this->client_info = value.data(); - this->client_info_len = value.size(); + this->client_info = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -36,71 +34,51 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->api_version_major); buffer.encode_uint32(2, this->api_version_minor); - buffer.encode_string(3, this->server_info_ref_); - buffer.encode_string(4, this->name_ref_); + buffer.encode_string(3, this->server_info); + buffer.encode_string(4, this->name); } void HelloResponse::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->api_version_major); size.add_uint32(1, this->api_version_minor); - size.add_length(1, this->server_info_ref_.size()); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->server_info.size()); + size.add_length(1, this->name.size()); } -#ifdef USE_API_PASSWORD -bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { - switch (field_id) { - case 1: { - // Use raw data directly to avoid allocation - this->password = value.data(); - this->password_len = value.size(); - break; - } - default: - return false; - } - return true; -} -void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } -void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); } -#endif #ifdef USE_AREAS void AreaInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->area_id); - buffer.encode_string(2, this->name_ref_); + buffer.encode_string(2, this->name); } void AreaInfo::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->area_id); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); } #endif #ifdef USE_DEVICES void DeviceInfo::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->device_id); - buffer.encode_string(2, this->name_ref_); + buffer.encode_string(2, this->name); buffer.encode_uint32(3, this->area_id); } void DeviceInfo::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->device_id); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_uint32(1, this->area_id); } #endif void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { -#ifdef USE_API_PASSWORD - buffer.encode_bool(1, this->uses_password); -#endif - buffer.encode_string(2, this->name_ref_); - buffer.encode_string(3, this->mac_address_ref_); - buffer.encode_string(4, this->esphome_version_ref_); - buffer.encode_string(5, this->compilation_time_ref_); - buffer.encode_string(6, this->model_ref_); + buffer.encode_string(2, this->name); + buffer.encode_string(3, this->mac_address); + buffer.encode_string(4, this->esphome_version); + buffer.encode_string(5, this->compilation_time); + buffer.encode_string(6, this->model); #ifdef USE_DEEP_SLEEP buffer.encode_bool(7, this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - buffer.encode_string(8, this->project_name_ref_); + buffer.encode_string(8, this->project_name); #endif #ifdef ESPHOME_PROJECT_NAME - buffer.encode_string(9, this->project_version_ref_); + buffer.encode_string(9, this->project_version); #endif #ifdef USE_WEBSERVER buffer.encode_uint32(10, this->webserver_port); @@ -108,28 +86,28 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #ifdef USE_BLUETOOTH_PROXY buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags); #endif - buffer.encode_string(12, this->manufacturer_ref_); - buffer.encode_string(13, this->friendly_name_ref_); + buffer.encode_string(12, this->manufacturer); + buffer.encode_string(13, this->friendly_name); #ifdef USE_VOICE_ASSISTANT buffer.encode_uint32(17, this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - buffer.encode_string(16, this->suggested_area_ref_); + buffer.encode_string(16, this->suggested_area); #endif #ifdef USE_BLUETOOTH_PROXY - buffer.encode_string(18, this->bluetooth_mac_address_ref_); + buffer.encode_string(18, this->bluetooth_mac_address); #endif #ifdef USE_API_NOISE buffer.encode_bool(19, this->api_encryption_supported); #endif #ifdef USE_DEVICES for (const auto &it : this->devices) { - buffer.encode_message(20, it, true); + buffer.encode_message(20, it); } #endif #ifdef USE_AREAS for (const auto &it : this->areas) { - buffer.encode_message(21, it, true); + buffer.encode_message(21, it); } #endif #ifdef USE_AREAS @@ -143,22 +121,19 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { #endif } void DeviceInfoResponse::calculate_size(ProtoSize &size) const { -#ifdef USE_API_PASSWORD - size.add_bool(1, this->uses_password); -#endif - size.add_length(1, this->name_ref_.size()); - size.add_length(1, this->mac_address_ref_.size()); - size.add_length(1, this->esphome_version_ref_.size()); - size.add_length(1, this->compilation_time_ref_.size()); - size.add_length(1, this->model_ref_.size()); + size.add_length(1, this->name.size()); + size.add_length(1, this->mac_address.size()); + size.add_length(1, this->esphome_version.size()); + size.add_length(1, this->compilation_time.size()); + size.add_length(1, this->model.size()); #ifdef USE_DEEP_SLEEP size.add_bool(1, this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - size.add_length(1, this->project_name_ref_.size()); + size.add_length(1, this->project_name.size()); #endif #ifdef ESPHOME_PROJECT_NAME - size.add_length(1, this->project_version_ref_.size()); + size.add_length(1, this->project_version.size()); #endif #ifdef USE_WEBSERVER size.add_uint32(1, this->webserver_port); @@ -166,16 +141,16 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const { #ifdef USE_BLUETOOTH_PROXY size.add_uint32(1, this->bluetooth_proxy_feature_flags); #endif - size.add_length(1, this->manufacturer_ref_.size()); - size.add_length(1, this->friendly_name_ref_.size()); + size.add_length(1, this->manufacturer.size()); + size.add_length(1, this->friendly_name.size()); #ifdef USE_VOICE_ASSISTANT size.add_uint32(2, this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - size.add_length(2, this->suggested_area_ref_.size()); + size.add_length(2, this->suggested_area.size()); #endif #ifdef USE_BLUETOOTH_PROXY - size.add_length(2, this->bluetooth_mac_address_ref_.size()); + size.add_length(2, this->bluetooth_mac_address.size()); #endif #ifdef USE_API_NOISE size.add_bool(2, this->api_encryption_supported); @@ -202,14 +177,14 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const { } #ifdef USE_BINARY_SENSOR void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); - buffer.encode_string(5, this->device_class_ref_); + buffer.encode_string(3, this->name); + buffer.encode_string(5, this->device_class); buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(8, this->icon_ref_); + buffer.encode_string(8, this->icon); #endif buffer.encode_uint32(9, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -217,14 +192,14 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesBinarySensorResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->name.size()); + size.add_length(1, this->device_class.size()); size.add_bool(1, this->is_status_binary_sensor); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -250,16 +225,16 @@ void BinarySensorStateResponse::calculate_size(ProtoSize &size) const { #endif #ifdef USE_COVER void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->assumed_state); buffer.encode_bool(6, this->supports_position); buffer.encode_bool(7, this->supports_tilt); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(10, this->icon_ref_); + buffer.encode_string(10, this->icon); #endif buffer.encode_uint32(11, static_cast(this->entity_category)); buffer.encode_bool(12, this->supports_stop); @@ -268,16 +243,16 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesCoverResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->assumed_state); size.add_bool(1, this->supports_position); size.add_bool(1, this->supports_tilt); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); size.add_bool(1, this->supports_stop); @@ -343,16 +318,16 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_FAN void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->supports_oscillation); buffer.encode_bool(6, this->supports_speed); buffer.encode_bool(7, this->supports_direction); buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(10, this->icon_ref_); + buffer.encode_string(10, this->icon); #endif buffer.encode_uint32(11, static_cast(this->entity_category)); for (const char *it : *this->supported_preset_modes) { @@ -363,16 +338,16 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->supports_oscillation); size.add_bool(1, this->supports_speed); size.add_bool(1, this->supports_direction); size.add_int32(1, this->supported_speed_count); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); if (!this->supported_preset_modes->empty()) { @@ -390,7 +365,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(3, this->oscillating); buffer.encode_uint32(5, static_cast(this->direction)); buffer.encode_int32(6, this->speed_level); - buffer.encode_string(7, this->preset_mode_ref_); + buffer.encode_string(7, this->preset_mode); #ifdef USE_DEVICES buffer.encode_uint32(8, this->device_id); #endif @@ -401,7 +376,7 @@ void FanStateResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->oscillating); size.add_uint32(1, static_cast(this->direction)); size.add_int32(1, this->speed_level); - size.add_length(1, this->preset_mode_ref_.size()); + size.add_length(1, this->preset_mode.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -447,9 +422,10 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 13: - this->preset_mode = value.as_string(); + case 13: { + this->preset_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -468,9 +444,9 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_LIGHT void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); for (const auto &it : *this->supported_color_modes) { buffer.encode_uint32(12, static_cast(it), true); } @@ -481,7 +457,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(13, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(14, this->icon_ref_); + buffer.encode_string(14, this->icon); #endif buffer.encode_uint32(15, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -489,9 +465,9 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); if (!this->supported_color_modes->empty()) { for (const auto &it : *this->supported_color_modes) { size.add_uint32_force(1, static_cast(it)); @@ -506,7 +482,7 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const { } size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -526,7 +502,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->color_temperature); buffer.encode_float(12, this->cold_white); buffer.encode_float(13, this->warm_white); - buffer.encode_string(9, this->effect_ref_); + buffer.encode_string(9, this->effect); #ifdef USE_DEVICES buffer.encode_uint32(14, this->device_id); #endif @@ -544,7 +520,7 @@ void LightStateResponse::calculate_size(ProtoSize &size) const { size.add_float(1, this->color_temperature); size.add_float(1, this->cold_white); size.add_float(1, this->warm_white); - size.add_length(1, this->effect_ref_.size()); + size.add_length(1, this->effect.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -612,9 +588,7 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 19: { - // Use raw data directly to avoid allocation - this->effect = value.data(); - this->effect_len = value.size(); + this->effect = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -662,16 +636,16 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_SENSOR void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif - buffer.encode_string(6, this->unit_of_measurement_ref_); + buffer.encode_string(6, this->unit_of_measurement); buffer.encode_int32(7, this->accuracy_decimals); buffer.encode_bool(8, this->force_update); - buffer.encode_string(9, this->device_class_ref_); + buffer.encode_string(9, this->device_class); buffer.encode_uint32(10, static_cast(this->state_class)); buffer.encode_bool(12, this->disabled_by_default); buffer.encode_uint32(13, static_cast(this->entity_category)); @@ -680,16 +654,16 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesSensorResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif - size.add_length(1, this->unit_of_measurement_ref_.size()); + size.add_length(1, this->unit_of_measurement.size()); size.add_int32(1, this->accuracy_decimals); size.add_bool(1, this->force_update); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); size.add_uint32(1, static_cast(this->state_class)); size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -716,31 +690,31 @@ void SensorStateResponse::calculate_size(ProtoSize &size) const { #endif #ifdef USE_SWITCH void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_uint32(8, static_cast(this->entity_category)); - buffer.encode_string(9, this->device_class_ref_); + buffer.encode_string(9, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); #endif } void ListEntitiesSwitchResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->assumed_state); size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -787,36 +761,36 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_TEXT_SENSOR void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(9, this->device_id); #endif } void ListEntitiesTextSensorResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif } void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state_ref_); + buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); #ifdef USE_DEVICES buffer.encode_uint32(4, this->device_id); @@ -824,7 +798,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { } void TextSensorStateResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->state_ref_.size()); + size.add_length(1, this->state.size()); size.add_bool(1, this->missing_state); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -855,9 +829,11 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const { #ifdef USE_API_NOISE bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->key = value.as_string(); + case 1: { + this->key = value.data(); + this->key_len = value.size(); break; + } default: return false; } @@ -868,23 +844,23 @@ void NoiseEncryptionSetKeyResponse::calculate_size(ProtoSize &size) const { size #endif #ifdef USE_API_HOMEASSISTANT_SERVICES void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->key_ref_); + buffer.encode_string(1, this->key); buffer.encode_string(2, this->value); } void HomeassistantServiceMap::calculate_size(ProtoSize &size) const { - size.add_length(1, this->key_ref_.size()); + size.add_length(1, this->key.size()); size.add_length(1, this->value.size()); } void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->service_ref_); + buffer.encode_string(1, this->service); for (auto &it : this->data) { - buffer.encode_message(2, it, true); + buffer.encode_message(2, it); } for (auto &it : this->data_template) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it); } for (auto &it : this->variables) { - buffer.encode_message(4, it, true); + buffer.encode_message(4, it); } buffer.encode_bool(5, this->is_event); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES @@ -898,7 +874,7 @@ void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const { #endif } void HomeassistantActionRequest::calculate_size(ProtoSize &size) const { - size.add_length(1, this->service_ref_.size()); + size.add_length(1, this->service.size()); size.add_repeated_message(1, this->data); size.add_repeated_message(1, this->data_template); size.add_repeated_message(1, this->variables); @@ -930,12 +906,12 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v } bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 3: - this->error_message = value.as_string(); + case 3: { + this->error_message = StringRef(reinterpret_cast(value.data()), value.size()); break; + } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON case 4: { - // Use raw data directly to avoid allocation this->response_data = value.data(); this->response_data_len = value.size(); break; @@ -949,26 +925,29 @@ bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDe #endif #ifdef USE_API_HOMEASSISTANT_STATES void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->entity_id_ref_); - buffer.encode_string(2, this->attribute_ref_); + buffer.encode_string(1, this->entity_id); + buffer.encode_string(2, this->attribute); buffer.encode_bool(3, this->once); } void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->entity_id_ref_.size()); - size.add_length(1, this->attribute_ref_.size()); + size.add_length(1, this->entity_id.size()); + size.add_length(1, this->attribute.size()); size.add_bool(1, this->once); } bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->entity_id = value.as_string(); + case 1: { + this->entity_id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->state = value.as_string(); + } + case 2: { + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 3: - this->attribute = value.as_string(); + } + case 3: { + this->attribute = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -978,9 +957,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation - this->timezone = value.data(); - this->timezone_len = value.size(); + this->timezone = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1000,23 +977,23 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { } #ifdef USE_API_USER_DEFINED_ACTIONS void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->name_ref_); + buffer.encode_string(1, this->name); buffer.encode_uint32(2, static_cast(this->type)); } void ListEntitiesServicesArgument::calculate_size(ProtoSize &size) const { - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_uint32(1, static_cast(this->type)); } void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->name_ref_); + buffer.encode_string(1, this->name); buffer.encode_fixed32(2, this->key); for (auto &it : this->args) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it); } buffer.encode_uint32(4, static_cast(this->supports_response)); } void ListEntitiesServicesResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_fixed32(1, this->key); size.add_repeated_message(1, this->args); size.add_uint32(1, static_cast(this->supports_response)); @@ -1045,9 +1022,10 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) } bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 4: - this->string_ = value.as_string(); + case 4: { + this->string_ = StringRef(reinterpret_cast(value.data()), value.size()); break; + } case 9: this->string_array.push_back(value.as_string()); break; @@ -1128,7 +1106,7 @@ void ExecuteServiceRequest::decode(const uint8_t *buffer, size_t length) { void ExecuteServiceResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->call_id); buffer.encode_bool(2, this->success); - buffer.encode_string(3, this->error_message_ref_); + buffer.encode_string(3, this->error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON buffer.encode_bytes(4, this->response_data, this->response_data_len); #endif @@ -1136,20 +1114,20 @@ void ExecuteServiceResponse::encode(ProtoWriteBuffer buffer) const { void ExecuteServiceResponse::calculate_size(ProtoSize &size) const { size.add_uint32(1, this->call_id); size.add_bool(1, this->success); - size.add_length(1, this->error_message_ref_.size()); + size.add_length(1, this->error_message.size()); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - size.add_length(4, this->response_data_len); + size.add_length(1, this->response_data_len); #endif } #endif #ifdef USE_CAMERA void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(6, this->icon_ref_); + buffer.encode_string(6, this->icon); #endif buffer.encode_uint32(7, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -1157,12 +1135,12 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesCameraResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_uint32(1, static_cast(this->entity_category)); #ifdef USE_DEVICES @@ -1201,9 +1179,9 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { #endif #ifdef USE_CLIMATE void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); buffer.encode_bool(5, this->supports_current_temperature); buffer.encode_bool(6, this->supports_two_point_target_temperature); for (const auto &it : *this->supported_modes) { @@ -1230,7 +1208,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(18, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(19, this->icon_ref_); + buffer.encode_string(19, this->icon); #endif buffer.encode_uint32(20, static_cast(this->entity_category)); buffer.encode_float(21, this->visual_current_temperature_step); @@ -1244,9 +1222,9 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(27, this->feature_flags); } void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); size.add_bool(1, this->supports_current_temperature); size.add_bool(1, this->supports_two_point_target_temperature); if (!this->supported_modes->empty()) { @@ -1285,7 +1263,7 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const { } size.add_bool(2, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size.add_length(2, this->icon_ref_.size()); + size.add_length(2, this->icon.size()); #endif size.add_uint32(2, static_cast(this->entity_category)); size.add_float(2, this->visual_current_temperature_step); @@ -1308,9 +1286,9 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(8, static_cast(this->action)); buffer.encode_uint32(9, static_cast(this->fan_mode)); buffer.encode_uint32(10, static_cast(this->swing_mode)); - buffer.encode_string(11, this->custom_fan_mode_ref_); + buffer.encode_string(11, this->custom_fan_mode); buffer.encode_uint32(12, static_cast(this->preset)); - buffer.encode_string(13, this->custom_preset_ref_); + buffer.encode_string(13, this->custom_preset); buffer.encode_float(14, this->current_humidity); buffer.encode_float(15, this->target_humidity); #ifdef USE_DEVICES @@ -1327,9 +1305,9 @@ void ClimateStateResponse::calculate_size(ProtoSize &size) const { size.add_uint32(1, static_cast(this->action)); size.add_uint32(1, static_cast(this->fan_mode)); size.add_uint32(1, static_cast(this->swing_mode)); - size.add_length(1, this->custom_fan_mode_ref_.size()); + size.add_length(1, this->custom_fan_mode.size()); size.add_uint32(1, static_cast(this->preset)); - size.add_length(1, this->custom_preset_ref_.size()); + size.add_length(1, this->custom_preset.size()); size.add_float(1, this->current_humidity); size.add_float(1, this->target_humidity); #ifdef USE_DEVICES @@ -1392,12 +1370,14 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) } bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 17: - this->custom_fan_mode = value.as_string(); + case 17: { + this->custom_fan_mode = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 21: - this->custom_preset = value.as_string(); + } + case 21: { + this->custom_preset = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -1426,41 +1406,149 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { return true; } #endif +#ifdef USE_WATER_HEATER +void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); +#ifdef USE_ENTITY_ICON + buffer.encode_string(4, this->icon); +#endif + buffer.encode_bool(5, this->disabled_by_default); + buffer.encode_uint32(6, static_cast(this->entity_category)); +#ifdef USE_DEVICES + buffer.encode_uint32(7, this->device_id); +#endif + buffer.encode_float(8, this->min_temperature); + buffer.encode_float(9, this->max_temperature); + buffer.encode_float(10, this->target_temperature_step); + for (const auto &it : *this->supported_modes) { + buffer.encode_uint32(11, static_cast(it), true); + } + buffer.encode_uint32(12, this->supported_features); +} +void ListEntitiesWaterHeaterResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name.size()); +#ifdef USE_ENTITY_ICON + size.add_length(1, this->icon.size()); +#endif + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); +#ifdef USE_DEVICES + size.add_uint32(1, this->device_id); +#endif + size.add_float(1, this->min_temperature); + size.add_float(1, this->max_temperature); + size.add_float(1, this->target_temperature_step); + if (!this->supported_modes->empty()) { + for (const auto &it : *this->supported_modes) { + size.add_uint32_force(1, static_cast(it)); + } + } + size.add_uint32(1, this->supported_features); +} +void WaterHeaterStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_float(2, this->current_temperature); + buffer.encode_float(3, this->target_temperature); + buffer.encode_uint32(4, static_cast(this->mode)); +#ifdef USE_DEVICES + buffer.encode_uint32(5, this->device_id); +#endif + buffer.encode_uint32(6, this->state); + buffer.encode_float(7, this->target_temperature_low); + buffer.encode_float(8, this->target_temperature_high); +} +void WaterHeaterStateResponse::calculate_size(ProtoSize &size) const { + size.add_fixed32(1, this->key); + size.add_float(1, this->current_temperature); + size.add_float(1, this->target_temperature); + size.add_uint32(1, static_cast(this->mode)); +#ifdef USE_DEVICES + size.add_uint32(1, this->device_id); +#endif + size.add_uint32(1, this->state); + size.add_float(1, this->target_temperature_low); + size.add_float(1, this->target_temperature_high); +} +bool WaterHeaterCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: + this->has_fields = value.as_uint32(); + break; + case 3: + this->mode = static_cast(value.as_uint32()); + break; +#ifdef USE_DEVICES + case 5: + this->device_id = value.as_uint32(); + break; +#endif + case 6: + this->state = value.as_uint32(); + break; + default: + return false; + } + return true; +} +bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: + this->key = value.as_fixed32(); + break; + case 4: + this->target_temperature = value.as_float(); + break; + case 7: + this->target_temperature_low = value.as_float(); + break; + case 8: + this->target_temperature_high = value.as_float(); + break; + default: + return false; + } + return true; +} +#endif #ifdef USE_NUMBER void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_float(6, this->min_value); buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_uint32(10, static_cast(this->entity_category)); - buffer.encode_string(11, this->unit_of_measurement_ref_); + buffer.encode_string(11, this->unit_of_measurement); buffer.encode_uint32(12, static_cast(this->mode)); - buffer.encode_string(13, this->device_class_ref_); + buffer.encode_string(13, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(14, this->device_id); #endif } void ListEntitiesNumberResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_float(1, this->min_value); size.add_float(1, this->max_value); size.add_float(1, this->step); size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->unit_of_measurement_ref_.size()); + size.add_length(1, this->unit_of_measurement.size()); size.add_uint32(1, static_cast(this->mode)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -1509,11 +1597,11 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_SELECT void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif for (const char *it : *this->options) { buffer.encode_string(6, it, strlen(it), true); @@ -1525,11 +1613,11 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif if (!this->options->empty()) { for (const char *it : *this->options) { @@ -1544,7 +1632,7 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const { } void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state_ref_); + buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); #ifdef USE_DEVICES buffer.encode_uint32(4, this->device_id); @@ -1552,7 +1640,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { } void SelectStateResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->state_ref_.size()); + size.add_length(1, this->state.size()); size.add_bool(1, this->missing_state); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -1573,9 +1661,7 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation - this->state = value.data(); - this->state_len = value.size(); + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; } default: @@ -1596,15 +1682,15 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_SIREN void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); - for (auto &it : this->tones) { - buffer.encode_string(7, it, true); + for (const char *it : *this->tones) { + buffer.encode_string(7, it, strlen(it), true); } buffer.encode_bool(8, this->supports_duration); buffer.encode_bool(9, this->supports_volume); @@ -1614,16 +1700,16 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); - if (!this->tones.empty()) { - for (const auto &it : this->tones) { - size.add_length_force(1, it.size()); + if (!this->tones->empty()) { + for (const char *it : *this->tones) { + size.add_length_force(1, strlen(it)); } } size.add_bool(1, this->supports_duration); @@ -1679,9 +1765,10 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 5: - this->tone = value.as_string(); + case 5: { + this->tone = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -1703,35 +1790,35 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_LOCK void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_bool(8, this->assumed_state); buffer.encode_bool(9, this->supports_open); buffer.encode_bool(10, this->requires_code); - buffer.encode_string(11, this->code_format_ref_); + buffer.encode_string(11, this->code_format); #ifdef USE_DEVICES buffer.encode_uint32(12, this->device_id); #endif } void ListEntitiesLockResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); size.add_bool(1, this->assumed_state); size.add_bool(1, this->supports_open); size.add_bool(1, this->requires_code); - size.add_length(1, this->code_format_ref_.size()); + size.add_length(1, this->code_format.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -1770,9 +1857,10 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 4: - this->code = value.as_string(); + case 4: { + this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -1791,29 +1879,29 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_BUTTON void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(9, this->device_id); #endif } void ListEntitiesButtonResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -1843,31 +1931,31 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_MEDIA_PLAYER void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->format_ref_); + buffer.encode_string(1, this->format); buffer.encode_uint32(2, this->sample_rate); buffer.encode_uint32(3, this->num_channels); buffer.encode_uint32(4, static_cast(this->purpose)); buffer.encode_uint32(5, this->sample_bytes); } void MediaPlayerSupportedFormat::calculate_size(ProtoSize &size) const { - size.add_length(1, this->format_ref_.size()); + size.add_length(1, this->format.size()); size.add_uint32(1, this->sample_rate); size.add_uint32(1, this->num_channels); size.add_uint32(1, static_cast(this->purpose)); size.add_uint32(1, this->sample_bytes); } void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_bool(8, this->supports_pause); for (auto &it : this->supported_formats) { - buffer.encode_message(9, it, true); + buffer.encode_message(9, it); } #ifdef USE_DEVICES buffer.encode_uint32(10, this->device_id); @@ -1875,11 +1963,11 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(11, this->feature_flags); } void ListEntitiesMediaPlayerResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -1940,9 +2028,10 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val } bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 7: - this->media_url = value.as_string(); + case 7: { + this->media_url = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -1987,7 +2076,7 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const { } void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const { for (uint16_t i = 0; i < this->advertisements_len; i++) { - buffer.encode_message(1, this->advertisements[i], true); + buffer.encode_message(1, this->advertisements[i]); } } void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const { @@ -2060,7 +2149,7 @@ void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->handle); buffer.encode_uint32(3, this->properties); for (auto &it : this->descriptors) { - buffer.encode_message(4, it, true); + buffer.encode_message(4, it); } buffer.encode_uint32(5, this->short_uuid); } @@ -2081,7 +2170,7 @@ void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const { } buffer.encode_uint32(2, this->handle); for (auto &it : this->characteristics) { - buffer.encode_message(3, it, true); + buffer.encode_message(3, it); } buffer.encode_uint32(4, this->short_uuid); } @@ -2097,7 +2186,7 @@ void BluetoothGATTService::calculate_size(ProtoSize &size) const { void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); for (auto &it : this->services) { - buffer.encode_message(2, it, true); + buffer.encode_message(2, it); } } void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const { @@ -2150,7 +2239,6 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 4: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -2189,7 +2277,6 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 3: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -2346,17 +2433,17 @@ void VoiceAssistantAudioSettings::calculate_size(ProtoSize &size) const { } void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); - buffer.encode_string(2, this->conversation_id_ref_); + buffer.encode_string(2, this->conversation_id); buffer.encode_uint32(3, this->flags); buffer.encode_message(4, this->audio_settings); - buffer.encode_string(5, this->wake_word_phrase_ref_); + buffer.encode_string(5, this->wake_word_phrase); } void VoiceAssistantRequest::calculate_size(ProtoSize &size) const { size.add_bool(1, this->start); - size.add_length(1, this->conversation_id_ref_.size()); + size.add_length(1, this->conversation_id.size()); size.add_uint32(1, this->flags); size.add_message_object(1, this->audio_settings); - size.add_length(1, this->wake_word_phrase_ref_.size()); + size.add_length(1, this->wake_word_phrase.size()); } bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { @@ -2373,12 +2460,14 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->name = value.as_string(); + case 1: { + this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->value = value.as_string(); + } + case 2: { + this->value = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2417,20 +2506,22 @@ bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->data = value.as_string(); + case 1: { + this->data = value.data(); + this->data_len = value.size(); break; + } default: return false; } return true; } void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const { - buffer.encode_bytes(1, this->data_ptr_, this->data_len_); + buffer.encode_bytes(1, this->data, this->data_len); buffer.encode_bool(2, this->end); } void VoiceAssistantAudio::calculate_size(ProtoSize &size) const { - size.add_length(1, this->data_len_); + size.add_length(1, this->data_len); size.add_bool(1, this->end); } bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { @@ -2454,12 +2545,14 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar } bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->timer_id = value.as_string(); + case 2: { + this->timer_id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 3: - this->name = value.as_string(); + } + case 3: { + this->name = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2477,15 +2570,18 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt } bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->media_id = value.as_string(); + case 1: { + this->media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->text = value.as_string(); + } + case 2: { + this->text = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 3: - this->preannounce_media_id = value.as_string(); + } + case 3: { + this->preannounce_media_id = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2494,15 +2590,15 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); } void VoiceAssistantAnnounceFinished::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); } void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->id_ref_); - buffer.encode_string(2, this->wake_word_ref_); + buffer.encode_string(1, this->id); + buffer.encode_string(2, this->wake_word); for (auto &it : this->trained_languages) { buffer.encode_string(3, it, true); } } void VoiceAssistantWakeWord::calculate_size(ProtoSize &size) const { - size.add_length(1, this->id_ref_.size()); - size.add_length(1, this->wake_word_ref_.size()); + size.add_length(1, this->id.size()); + size.add_length(1, this->wake_word.size()); if (!this->trained_languages.empty()) { for (const auto &it : this->trained_languages) { size.add_length_force(1, it.size()); @@ -2521,24 +2617,29 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn } bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 1: - this->id = value.as_string(); + case 1: { + this->id = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 2: - this->wake_word = value.as_string(); + } + case 2: { + this->wake_word = StringRef(reinterpret_cast(value.data()), value.size()); break; + } case 3: this->trained_languages.push_back(value.as_string()); break; - case 4: - this->model_type = value.as_string(); + case 4: { + this->model_type = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 6: - this->model_hash = value.as_string(); + } + case 6: { + this->model_hash = StringRef(reinterpret_cast(value.data()), value.size()); break; - case 7: - this->url = value.as_string(); + } + case 7: { + this->url = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2557,7 +2658,7 @@ bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoL } void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->available_wake_words) { - buffer.encode_message(1, it, true); + buffer.encode_message(1, it); } for (const auto &it : *this->active_wake_words) { buffer.encode_string(2, it, true); @@ -2586,11 +2687,11 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt #endif #ifdef USE_ALARM_CONTROL_PANEL void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -2602,11 +2703,11 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons #endif } void ListEntitiesAlarmControlPanelResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -2648,9 +2749,10 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI } bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 3: - this->code = value.as_string(); + case 3: { + this->code = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2669,34 +2771,34 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit #endif #ifdef USE_TEXT void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); buffer.encode_uint32(8, this->min_length); buffer.encode_uint32(9, this->max_length); - buffer.encode_string(10, this->pattern_ref_); + buffer.encode_string(10, this->pattern); buffer.encode_uint32(11, static_cast(this->mode)); #ifdef USE_DEVICES buffer.encode_uint32(12, this->device_id); #endif } void ListEntitiesTextResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); size.add_uint32(1, this->min_length); size.add_uint32(1, this->max_length); - size.add_length(1, this->pattern_ref_.size()); + size.add_length(1, this->pattern.size()); size.add_uint32(1, static_cast(this->mode)); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -2704,7 +2806,7 @@ void ListEntitiesTextResponse::calculate_size(ProtoSize &size) const { } void TextStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state_ref_); + buffer.encode_string(2, this->state); buffer.encode_bool(3, this->missing_state); #ifdef USE_DEVICES buffer.encode_uint32(4, this->device_id); @@ -2712,7 +2814,7 @@ void TextStateResponse::encode(ProtoWriteBuffer buffer) const { } void TextStateResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->state_ref_.size()); + size.add_length(1, this->state.size()); size.add_bool(1, this->missing_state); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); @@ -2732,9 +2834,10 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { } bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: - this->state = value.as_string(); + case 2: { + this->state = StringRef(reinterpret_cast(value.data()), value.size()); break; + } default: return false; } @@ -2753,11 +2856,11 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_DATETIME_DATE void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -2766,11 +2869,11 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesDateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -2832,11 +2935,11 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_DATETIME_TIME void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -2845,11 +2948,11 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesTimeResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -2911,15 +3014,15 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_EVENT void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); for (const char *it : *this->event_types) { buffer.encode_string(9, it, strlen(it), true); } @@ -2928,15 +3031,15 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); if (!this->event_types->empty()) { for (const char *it : *this->event_types) { size.add_length_force(1, strlen(it)); @@ -2948,14 +3051,14 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const { } void EventResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->event_type_ref_); + buffer.encode_string(2, this->event_type); #ifdef USE_DEVICES buffer.encode_uint32(3, this->device_id); #endif } void EventResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->key); - size.add_length(1, this->event_type_ref_.size()); + size.add_length(1, this->event_type.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -2963,15 +3066,15 @@ void EventResponse::calculate_size(ProtoSize &size) const { #endif #ifdef USE_VALVE void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->assumed_state); buffer.encode_bool(10, this->supports_position); buffer.encode_bool(11, this->supports_stop); @@ -2980,15 +3083,15 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesValveResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); size.add_bool(1, this->assumed_state); size.add_bool(1, this->supports_position); size.add_bool(1, this->supports_stop); @@ -3046,11 +3149,11 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_DATETIME_DATETIME void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); @@ -3059,11 +3162,11 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { #endif } void ListEntitiesDateTimeResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); @@ -3115,29 +3218,29 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { #endif #ifdef USE_UPDATE void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { - buffer.encode_string(1, this->object_id_ref_); + buffer.encode_string(1, this->object_id); buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name_ref_); + buffer.encode_string(3, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon_ref_); + buffer.encode_string(5, this->icon); #endif buffer.encode_bool(6, this->disabled_by_default); buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class_ref_); + buffer.encode_string(8, this->device_class); #ifdef USE_DEVICES buffer.encode_uint32(9, this->device_id); #endif } void ListEntitiesUpdateResponse::calculate_size(ProtoSize &size) const { - size.add_length(1, this->object_id_ref_.size()); + size.add_length(1, this->object_id.size()); size.add_fixed32(1, this->key); - size.add_length(1, this->name_ref_.size()); + size.add_length(1, this->name.size()); #ifdef USE_ENTITY_ICON - size.add_length(1, this->icon_ref_.size()); + size.add_length(1, this->icon.size()); #endif size.add_bool(1, this->disabled_by_default); size.add_uint32(1, static_cast(this->entity_category)); - size.add_length(1, this->device_class_ref_.size()); + size.add_length(1, this->device_class.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -3148,11 +3251,11 @@ void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(3, this->in_progress); buffer.encode_bool(4, this->has_progress); buffer.encode_float(5, this->progress); - buffer.encode_string(6, this->current_version_ref_); - buffer.encode_string(7, this->latest_version_ref_); - buffer.encode_string(8, this->title_ref_); - buffer.encode_string(9, this->release_summary_ref_); - buffer.encode_string(10, this->release_url_ref_); + buffer.encode_string(6, this->current_version); + buffer.encode_string(7, this->latest_version); + buffer.encode_string(8, this->title); + buffer.encode_string(9, this->release_summary); + buffer.encode_string(10, this->release_url); #ifdef USE_DEVICES buffer.encode_uint32(11, this->device_id); #endif @@ -3163,11 +3266,11 @@ void UpdateStateResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->in_progress); size.add_bool(1, this->has_progress); size.add_float(1, this->progress); - size.add_length(1, this->current_version_ref_.size()); - size.add_length(1, this->latest_version_ref_.size()); - size.add_length(1, this->title_ref_.size()); - size.add_length(1, this->release_summary_ref_.size()); - size.add_length(1, this->release_url_ref_.size()); + size.add_length(1, this->current_version.size()); + size.add_length(1, this->latest_version.size()); + size.add_length(1, this->title.size()); + size.add_length(1, this->release_summary.size()); + size.add_length(1, this->release_url.size()); #ifdef USE_DEVICES size.add_uint32(1, this->device_id); #endif @@ -3202,7 +3305,6 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -3227,7 +3329,6 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 2: { - // Use raw data directly to avoid allocation this->data = value.data(); this->data_len = value.size(); break; @@ -3243,7 +3344,100 @@ void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const { } void ZWaveProxyRequest::calculate_size(ProtoSize &size) const { size.add_uint32(1, static_cast(this->type)); - size.add_length(2, this->data_len); + size.add_length(1, this->data_len); +} +#endif +#ifdef USE_INFRARED +void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); +#ifdef USE_ENTITY_ICON + buffer.encode_string(4, this->icon); +#endif + buffer.encode_bool(5, this->disabled_by_default); + buffer.encode_uint32(6, static_cast(this->entity_category)); +#ifdef USE_DEVICES + buffer.encode_uint32(7, this->device_id); +#endif + buffer.encode_uint32(8, this->capabilities); +} +void ListEntitiesInfraredResponse::calculate_size(ProtoSize &size) const { + size.add_length(1, this->object_id.size()); + size.add_fixed32(1, this->key); + size.add_length(1, this->name.size()); +#ifdef USE_ENTITY_ICON + size.add_length(1, this->icon.size()); +#endif + size.add_bool(1, this->disabled_by_default); + size.add_uint32(1, static_cast(this->entity_category)); +#ifdef USE_DEVICES + size.add_uint32(1, this->device_id); +#endif + size.add_uint32(1, this->capabilities); +} +#endif +#ifdef USE_IR_RF +bool InfraredRFTransmitRawTimingsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { +#ifdef USE_DEVICES + case 1: + this->device_id = value.as_uint32(); + break; +#endif + case 3: + this->carrier_frequency = value.as_uint32(); + break; + case 4: + this->repeat_count = value.as_uint32(); + break; + default: + return false; + } + return true; +} +bool InfraredRFTransmitRawTimingsRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 5: { + this->timings_data_ = value.data(); + this->timings_length_ = value.size(); + this->timings_count_ = count_packed_varints(value.data(), value.size()); + break; + } + default: + return false; + } + return true; +} +bool InfraredRFTransmitRawTimingsRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: + this->key = value.as_fixed32(); + break; + default: + return false; + } + return true; +} +void InfraredRFReceiveEvent::encode(ProtoWriteBuffer buffer) const { +#ifdef USE_DEVICES + buffer.encode_uint32(1, this->device_id); +#endif + buffer.encode_fixed32(2, this->key); + for (const auto &it : *this->timings) { + buffer.encode_sint32(3, it, true); + } +} +void InfraredRFReceiveEvent::calculate_size(ProtoSize &size) const { +#ifdef USE_DEVICES + size.add_uint32(1, this->device_id); +#endif + size.add_fixed32(1, this->key); + if (!this->timings->empty()) { + for (const auto &it : *this->timings) { + size.add_sint32_force(1, it); + } + } } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index f23a62fc3c..cf6c65f285 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -129,6 +129,25 @@ enum ClimatePreset : uint32_t { CLIMATE_PRESET_ACTIVITY = 7, }; #endif +#ifdef USE_WATER_HEATER +enum WaterHeaterMode : uint32_t { + WATER_HEATER_MODE_OFF = 0, + WATER_HEATER_MODE_ECO = 1, + WATER_HEATER_MODE_ELECTRIC = 2, + WATER_HEATER_MODE_PERFORMANCE = 3, + WATER_HEATER_MODE_HIGH_DEMAND = 4, + WATER_HEATER_MODE_HEAT_PUMP = 5, + WATER_HEATER_MODE_GAS = 6, +}; +#endif +enum WaterHeaterCommandHasField : uint32_t { + WATER_HEATER_COMMAND_HAS_NONE = 0, + WATER_HEATER_COMMAND_HAS_MODE = 1, + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2, + WATER_HEATER_COMMAND_HAS_STATE = 4, + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8, + WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16, +}; #ifdef USE_NUMBER enum NumberMode : uint32_t { NUMBER_MODE_AUTO = 0, @@ -296,15 +315,12 @@ enum ZWaveProxyRequestType : uint32_t { class InfoResponseProtoMessage : public ProtoMessage { public: ~InfoResponseProtoMessage() override = default; - StringRef object_id_ref_{}; - void set_object_id(const StringRef &ref) { this->object_id_ref_ = ref; } + StringRef object_id{}; uint32_t key{0}; - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; bool disabled_by_default{false}; #ifdef USE_ENTITY_ICON - StringRef icon_ref_{}; - void set_icon(const StringRef &ref) { this->icon_ref_ = ref; } + StringRef icon{}; #endif enums::EntityCategory entity_category{}; #ifdef USE_DEVICES @@ -338,16 +354,15 @@ class CommandProtoMessage : public ProtoDecodableMessage { class HelloRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 1; - static constexpr uint8_t ESTIMATED_SIZE = 27; + static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "hello_request"; } #endif - const uint8_t *client_info{nullptr}; - uint16_t client_info_len{0}; + StringRef client_info{}; uint32_t api_version_major{0}; uint32_t api_version_minor{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -363,52 +378,16 @@ class HelloResponse final : public ProtoMessage { #endif uint32_t api_version_major{0}; uint32_t api_version_minor{0}; - StringRef server_info_ref_{}; - void set_server_info(const StringRef &ref) { this->server_info_ref_ = ref; } - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef server_info{}; + StringRef name{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: }; -#ifdef USE_API_PASSWORD -class AuthenticationRequest final : public ProtoDecodableMessage { - public: - static constexpr uint8_t MESSAGE_TYPE = 3; - static constexpr uint8_t ESTIMATED_SIZE = 19; -#ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "authentication_request"; } -#endif - const uint8_t *password{nullptr}; - uint16_t password_len{0}; -#ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; -#endif - - protected: - bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; -}; -class AuthenticationResponse final : public ProtoMessage { - public: - static constexpr uint8_t MESSAGE_TYPE = 4; - static constexpr uint8_t ESTIMATED_SIZE = 2; -#ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "authentication_response"; } -#endif - bool invalid_password{false}; - void encode(ProtoWriteBuffer buffer) const override; - void calculate_size(ProtoSize &size) const override; -#ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; -#endif - - protected: -}; -#endif class DisconnectRequest final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 5; @@ -417,7 +396,7 @@ class DisconnectRequest final : public ProtoMessage { const char *message_name() const override { return "disconnect_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -430,7 +409,7 @@ class DisconnectResponse final : public ProtoMessage { const char *message_name() const override { return "disconnect_response"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -443,7 +422,7 @@ class PingRequest final : public ProtoMessage { const char *message_name() const override { return "ping_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -456,7 +435,7 @@ class PingResponse final : public ProtoMessage { const char *message_name() const override { return "ping_response"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -469,7 +448,7 @@ class DeviceInfoRequest final : public ProtoMessage { const char *message_name() const override { return "device_info_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -478,12 +457,11 @@ class DeviceInfoRequest final : public ProtoMessage { class AreaInfo final : public ProtoMessage { public: uint32_t area_id{0}; - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -493,13 +471,12 @@ class AreaInfo final : public ProtoMessage { class DeviceInfo final : public ProtoMessage { public: uint32_t device_id{0}; - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; uint32_t area_id{0}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -508,33 +485,23 @@ class DeviceInfo final : public ProtoMessage { class DeviceInfoResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 10; - static constexpr uint16_t ESTIMATED_SIZE = 257; + static constexpr uint8_t ESTIMATED_SIZE = 255; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "device_info_response"; } #endif -#ifdef USE_API_PASSWORD - bool uses_password{false}; -#endif - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } - StringRef mac_address_ref_{}; - void set_mac_address(const StringRef &ref) { this->mac_address_ref_ = ref; } - StringRef esphome_version_ref_{}; - void set_esphome_version(const StringRef &ref) { this->esphome_version_ref_ = ref; } - StringRef compilation_time_ref_{}; - void set_compilation_time(const StringRef &ref) { this->compilation_time_ref_ = ref; } - StringRef model_ref_{}; - void set_model(const StringRef &ref) { this->model_ref_ = ref; } + StringRef name{}; + StringRef mac_address{}; + StringRef esphome_version{}; + StringRef compilation_time{}; + StringRef model{}; #ifdef USE_DEEP_SLEEP bool has_deep_sleep{false}; #endif #ifdef ESPHOME_PROJECT_NAME - StringRef project_name_ref_{}; - void set_project_name(const StringRef &ref) { this->project_name_ref_ = ref; } + StringRef project_name{}; #endif #ifdef ESPHOME_PROJECT_NAME - StringRef project_version_ref_{}; - void set_project_version(const StringRef &ref) { this->project_version_ref_ = ref; } + StringRef project_version{}; #endif #ifdef USE_WEBSERVER uint32_t webserver_port{0}; @@ -542,20 +509,16 @@ class DeviceInfoResponse final : public ProtoMessage { #ifdef USE_BLUETOOTH_PROXY uint32_t bluetooth_proxy_feature_flags{0}; #endif - StringRef manufacturer_ref_{}; - void set_manufacturer(const StringRef &ref) { this->manufacturer_ref_ = ref; } - StringRef friendly_name_ref_{}; - void set_friendly_name(const StringRef &ref) { this->friendly_name_ref_ = ref; } + StringRef manufacturer{}; + StringRef friendly_name{}; #ifdef USE_VOICE_ASSISTANT uint32_t voice_assistant_feature_flags{0}; #endif #ifdef USE_AREAS - StringRef suggested_area_ref_{}; - void set_suggested_area(const StringRef &ref) { this->suggested_area_ref_ = ref; } + StringRef suggested_area{}; #endif #ifdef USE_BLUETOOTH_PROXY - StringRef bluetooth_mac_address_ref_{}; - void set_bluetooth_mac_address(const StringRef &ref) { this->bluetooth_mac_address_ref_ = ref; } + StringRef bluetooth_mac_address{}; #endif #ifdef USE_API_NOISE bool api_encryption_supported{false}; @@ -578,7 +541,7 @@ class DeviceInfoResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -591,7 +554,7 @@ class ListEntitiesRequest final : public ProtoMessage { const char *message_name() const override { return "list_entities_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -604,7 +567,7 @@ class ListEntitiesDoneResponse final : public ProtoMessage { const char *message_name() const override { return "list_entities_done_response"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -617,7 +580,7 @@ class SubscribeStatesRequest final : public ProtoMessage { const char *message_name() const override { return "subscribe_states_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -630,13 +593,12 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_binary_sensor_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; bool is_status_binary_sensor{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -653,7 +615,7 @@ class BinarySensorStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -670,13 +632,12 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_tilt{false}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; bool supports_stop{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -694,7 +655,7 @@ class CoverStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -712,7 +673,7 @@ class CoverCommandRequest final : public CommandProtoMessage { float tilt{0.0f}; bool stop{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -736,7 +697,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -752,12 +713,11 @@ class FanStateResponse final : public StateResponseProtoMessage { bool oscillating{false}; enums::FanDirection direction{}; int32_t speed_level{0}; - StringRef preset_mode_ref_{}; - void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; } + StringRef preset_mode{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -778,9 +738,9 @@ class FanCommandRequest final : public CommandProtoMessage { bool has_speed_level{false}; int32_t speed_level{0}; bool has_preset_mode{false}; - std::string preset_mode{}; + StringRef preset_mode{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -804,7 +764,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -827,12 +787,11 @@ class LightStateResponse final : public StateResponseProtoMessage { float color_temperature{0.0f}; float cold_white{0.0f}; float warm_white{0.0f}; - StringRef effect_ref_{}; - void set_effect(const StringRef &ref) { this->effect_ref_ = ref; } + StringRef effect{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -840,7 +799,7 @@ class LightStateResponse final : public StateResponseProtoMessage { class LightCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 32; - static constexpr uint8_t ESTIMATED_SIZE = 122; + static constexpr uint8_t ESTIMATED_SIZE = 112; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "light_command_request"; } #endif @@ -869,10 +828,9 @@ class LightCommandRequest final : public CommandProtoMessage { bool has_flash_length{false}; uint32_t flash_length{0}; bool has_effect{false}; - const uint8_t *effect{nullptr}; - uint16_t effect_len{0}; + StringRef effect{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -889,17 +847,15 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_sensor_response"; } #endif - StringRef unit_of_measurement_ref_{}; - void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; } + StringRef unit_of_measurement{}; int32_t accuracy_decimals{0}; bool force_update{false}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; enums::SensorStateClass state_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -916,7 +872,7 @@ class SensorStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -931,12 +887,11 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage { const char *message_name() const override { return "list_entities_switch_response"; } #endif bool assumed_state{false}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -952,7 +907,7 @@ class SwitchStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -966,7 +921,7 @@ class SwitchCommandRequest final : public CommandProtoMessage { #endif bool state{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -982,12 +937,11 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_text_sensor_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -999,13 +953,12 @@ class TextSensorStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_sensor_state_response"; } #endif - StringRef state_ref_{}; - void set_state(const StringRef &ref) { this->state_ref_ = ref; } + StringRef state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1021,7 +974,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage { enums::LogLevel level{}; bool dump_config{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1030,7 +983,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage { class SubscribeLogsResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 29; - static constexpr uint8_t ESTIMATED_SIZE = 11; + static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_logs_response"; } #endif @@ -1044,7 +997,7 @@ class SubscribeLogsResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1053,13 +1006,14 @@ class SubscribeLogsResponse final : public ProtoMessage { class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 124; - static constexpr uint8_t ESTIMATED_SIZE = 9; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "noise_encryption_set_key_request"; } #endif - std::string key{}; + const uint8_t *key{nullptr}; + uint16_t key_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1076,7 +1030,7 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1091,20 +1045,19 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage { const char *message_name() const override { return "subscribe_homeassistant_services_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: }; class HomeassistantServiceMap final : public ProtoMessage { public: - StringRef key_ref_{}; - void set_key(const StringRef &ref) { this->key_ref_ = ref; } - std::string value{}; + StringRef key{}; + StringRef value{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1116,8 +1069,7 @@ class HomeassistantActionRequest final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "homeassistant_action_request"; } #endif - StringRef service_ref_{}; - void set_service(const StringRef &ref) { this->service_ref_ = ref; } + StringRef service{}; FixedVector data{}; FixedVector data_template{}; FixedVector variables{}; @@ -1129,12 +1081,12 @@ class HomeassistantActionRequest final : public ProtoMessage { bool wants_response{false}; #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - std::string response_template{}; + StringRef response_template{}; #endif void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1150,13 +1102,13 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage { #endif uint32_t call_id{0}; bool success{false}; - std::string error_message{}; + StringRef error_message{}; #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1173,7 +1125,7 @@ class SubscribeHomeAssistantStatesRequest final : public ProtoMessage { const char *message_name() const override { return "subscribe_home_assistant_states_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1185,15 +1137,13 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "subscribe_home_assistant_state_response"; } #endif - StringRef entity_id_ref_{}; - void set_entity_id(const StringRef &ref) { this->entity_id_ref_ = ref; } - StringRef attribute_ref_{}; - void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; } + StringRef entity_id{}; + StringRef attribute{}; bool once{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1205,11 +1155,11 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "home_assistant_state_response"; } #endif - std::string entity_id{}; - std::string state{}; - std::string attribute{}; + StringRef entity_id{}; + StringRef state{}; + StringRef attribute{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1224,7 +1174,7 @@ class GetTimeRequest final : public ProtoMessage { const char *message_name() const override { return "get_time_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1232,15 +1182,14 @@ class GetTimeRequest final : public ProtoMessage { class GetTimeResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 37; - static constexpr uint8_t ESTIMATED_SIZE = 24; + static constexpr uint8_t ESTIMATED_SIZE = 14; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "get_time_response"; } #endif uint32_t epoch_seconds{0}; - const uint8_t *timezone{nullptr}; - uint16_t timezone_len{0}; + StringRef timezone{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1250,13 +1199,12 @@ class GetTimeResponse final : public ProtoDecodableMessage { #ifdef USE_API_USER_DEFINED_ACTIONS class ListEntitiesServicesArgument final : public ProtoMessage { public: - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; enums::ServiceArgType type{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1268,15 +1216,14 @@ class ListEntitiesServicesResponse final : public ProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_services_response"; } #endif - StringRef name_ref_{}; - void set_name(const StringRef &ref) { this->name_ref_ = ref; } + StringRef name{}; uint32_t key{0}; FixedVector args{}; enums::SupportsResponseType supports_response{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1286,7 +1233,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { bool bool_{false}; int32_t legacy_int{0}; float float_{0.0f}; - std::string string_{}; + StringRef string_{}; int32_t int_{0}; FixedVector bool_array{}; FixedVector int_array{}; @@ -1294,7 +1241,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { FixedVector string_array{}; void decode(const uint8_t *buffer, size_t length) override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1319,7 +1266,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage { #endif void decode(const uint8_t *buffer, size_t length) override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1338,8 +1285,7 @@ class ExecuteServiceResponse final : public ProtoMessage { #endif uint32_t call_id{0}; bool success{false}; - StringRef error_message_ref_{}; - void set_error_message(const StringRef &ref) { this->error_message_ref_ = ref; } + StringRef error_message{}; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; @@ -1347,7 +1293,7 @@ class ExecuteServiceResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1364,7 +1310,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1372,7 +1318,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage { class CameraImageResponse final : public StateResponseProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 44; - static constexpr uint8_t ESTIMATED_SIZE = 20; + static constexpr uint8_t ESTIMATED_SIZE = 30; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "camera_image_response"; } #endif @@ -1386,7 +1332,7 @@ class CameraImageResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1401,7 +1347,7 @@ class CameraImageRequest final : public ProtoDecodableMessage { bool single{false}; bool stream{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1437,7 +1383,7 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1457,17 +1403,15 @@ class ClimateStateResponse final : public StateResponseProtoMessage { enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; - StringRef custom_fan_mode_ref_{}; - void set_custom_fan_mode(const StringRef &ref) { this->custom_fan_mode_ref_ = ref; } + StringRef custom_fan_mode{}; enums::ClimatePreset preset{}; - StringRef custom_preset_ref_{}; - void set_custom_preset(const StringRef &ref) { this->custom_preset_ref_ = ref; } + StringRef custom_preset{}; float current_humidity{0.0f}; float target_humidity{0.0f}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1492,15 +1436,15 @@ class ClimateCommandRequest final : public CommandProtoMessage { bool has_swing_mode{false}; enums::ClimateSwingMode swing_mode{}; bool has_custom_fan_mode{false}; - std::string custom_fan_mode{}; + StringRef custom_fan_mode{}; bool has_preset{false}; enums::ClimatePreset preset{}; bool has_custom_preset{false}; - std::string custom_preset{}; + StringRef custom_preset{}; bool has_target_humidity{false}; float target_humidity{0.0f}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1509,6 +1453,70 @@ class ClimateCommandRequest final : public CommandProtoMessage { bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif +#ifdef USE_WATER_HEATER +class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 132; + static constexpr uint8_t ESTIMATED_SIZE = 63; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "list_entities_water_heater_response"; } +#endif + float min_temperature{0.0f}; + float max_temperature{0.0f}; + float target_temperature_step{0.0f}; + const water_heater::WaterHeaterModeMask *supported_modes{}; + uint32_t supported_features{0}; + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +class WaterHeaterStateResponse final : public StateResponseProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 133; + static constexpr uint8_t ESTIMATED_SIZE = 35; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "water_heater_state_response"; } +#endif + float current_temperature{0.0f}; + float target_temperature{0.0f}; + enums::WaterHeaterMode mode{}; + uint32_t state{0}; + float target_temperature_low{0.0f}; + float target_temperature_high{0.0f}; + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +class WaterHeaterCommandRequest final : public CommandProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 134; + static constexpr uint8_t ESTIMATED_SIZE = 34; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "water_heater_command_request"; } +#endif + uint32_t has_fields{0}; + enums::WaterHeaterMode mode{}; + float target_temperature{0.0f}; + uint32_t state{0}; + float target_temperature_low{0.0f}; + float target_temperature_high{0.0f}; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +#endif #ifdef USE_NUMBER class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { public: @@ -1520,15 +1528,13 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { float min_value{0.0f}; float max_value{0.0f}; float step{0.0f}; - StringRef unit_of_measurement_ref_{}; - void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; } + StringRef unit_of_measurement{}; enums::NumberMode mode{}; - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1545,7 +1551,7 @@ class NumberStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1559,7 +1565,7 @@ class NumberCommandRequest final : public CommandProtoMessage { #endif float state{0.0f}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1579,7 +1585,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1591,13 +1597,12 @@ class SelectStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_state_response"; } #endif - StringRef state_ref_{}; - void set_state(const StringRef &ref) { this->state_ref_ = ref; } + StringRef state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1605,14 +1610,13 @@ class SelectStateResponse final : public StateResponseProtoMessage { class SelectCommandRequest final : public CommandProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 54; - static constexpr uint8_t ESTIMATED_SIZE = 28; + static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "select_command_request"; } #endif - const uint8_t *state{nullptr}; - uint16_t state_len{0}; + StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1629,13 +1633,13 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_siren_response"; } #endif - std::vector tones{}; + const FixedVector *tones{}; bool supports_duration{false}; bool supports_volume{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1651,7 +1655,7 @@ class SirenStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1666,13 +1670,13 @@ class SirenCommandRequest final : public CommandProtoMessage { bool has_state{false}; bool state{false}; bool has_tone{false}; - std::string tone{}; + StringRef tone{}; bool has_duration{false}; uint32_t duration{0}; bool has_volume{false}; float volume{0.0f}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1692,12 +1696,11 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_open{false}; bool requires_code{false}; - StringRef code_format_ref_{}; - void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; } + StringRef code_format{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1713,7 +1716,7 @@ class LockStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1727,9 +1730,9 @@ class LockCommandRequest final : public CommandProtoMessage { #endif enums::LockCommand command{}; bool has_code{false}; - std::string code{}; + StringRef code{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1746,12 +1749,11 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_button_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1764,7 +1766,7 @@ class ButtonCommandRequest final : public CommandProtoMessage { const char *message_name() const override { return "button_command_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1775,8 +1777,7 @@ class ButtonCommandRequest final : public CommandProtoMessage { #ifdef USE_MEDIA_PLAYER class MediaPlayerSupportedFormat final : public ProtoMessage { public: - StringRef format_ref_{}; - void set_format(const StringRef &ref) { this->format_ref_ = ref; } + StringRef format{}; uint32_t sample_rate{0}; uint32_t num_channels{0}; enums::MediaPlayerFormatPurpose purpose{}; @@ -1784,7 +1785,7 @@ class MediaPlayerSupportedFormat final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1802,7 +1803,7 @@ class ListEntitiesMediaPlayerResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1820,7 +1821,7 @@ class MediaPlayerStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1837,11 +1838,11 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage { bool has_volume{false}; float volume{0.0f}; bool has_media_url{false}; - std::string media_url{}; + StringRef media_url{}; bool has_announcement{false}; bool announcement{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1860,7 +1861,7 @@ class SubscribeBluetoothLEAdvertisementsRequest final : public ProtoDecodableMes #endif uint32_t flags{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1876,7 +1877,7 @@ class BluetoothLERawAdvertisement final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1893,7 +1894,7 @@ class BluetoothLERawAdvertisementsResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1910,7 +1911,7 @@ class BluetoothDeviceRequest final : public ProtoDecodableMessage { bool has_address_type{false}; uint32_t address_type{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1930,7 +1931,7 @@ class BluetoothDeviceConnectionResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1944,7 +1945,7 @@ class BluetoothGATTGetServicesRequest final : public ProtoDecodableMessage { #endif uint64_t address{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1958,7 +1959,7 @@ class BluetoothGATTDescriptor final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1973,7 +1974,7 @@ class BluetoothGATTCharacteristic final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -1987,7 +1988,7 @@ class BluetoothGATTService final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2004,7 +2005,7 @@ class BluetoothGATTGetServicesResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2020,7 +2021,7 @@ class BluetoothGATTGetServicesDoneResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2035,7 +2036,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage { uint64_t address{0}; uint32_t handle{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2044,7 +2045,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage { class BluetoothGATTReadResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 74; - static constexpr uint8_t ESTIMATED_SIZE = 17; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_read_response"; } #endif @@ -2059,7 +2060,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2077,7 +2078,7 @@ class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { const uint8_t *data{nullptr}; uint16_t data_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2094,7 +2095,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { uint64_t address{0}; uint32_t handle{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2112,7 +2113,7 @@ class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { const uint8_t *data{nullptr}; uint16_t data_len{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2130,7 +2131,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage { uint32_t handle{0}; bool enable{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2139,7 +2140,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage { class BluetoothGATTNotifyDataResponse final : public ProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 79; - static constexpr uint8_t ESTIMATED_SIZE = 17; + static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; } #endif @@ -2154,7 +2155,7 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2167,7 +2168,7 @@ class SubscribeBluetoothConnectionsFreeRequest final : public ProtoMessage { const char *message_name() const override { return "subscribe_bluetooth_connections_free_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2185,7 +2186,7 @@ class BluetoothConnectionsFreeResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2203,7 +2204,7 @@ class BluetoothGATTErrorResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2220,7 +2221,7 @@ class BluetoothGATTWriteResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2237,7 +2238,7 @@ class BluetoothGATTNotifyResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2255,7 +2256,7 @@ class BluetoothDevicePairingResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2273,7 +2274,7 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2286,7 +2287,7 @@ class UnsubscribeBluetoothLEAdvertisementsRequest final : public ProtoMessage { const char *message_name() const override { return "unsubscribe_bluetooth_le_advertisements_request"; } #endif #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2304,7 +2305,7 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2322,7 +2323,7 @@ class BluetoothScannerStateResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2336,7 +2337,7 @@ class BluetoothScannerSetModeRequest final : public ProtoDecodableMessage { #endif enums::BluetoothScannerMode mode{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2354,7 +2355,7 @@ class SubscribeVoiceAssistantRequest final : public ProtoDecodableMessage { bool subscribe{false}; uint32_t flags{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2368,7 +2369,7 @@ class VoiceAssistantAudioSettings final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2381,16 +2382,14 @@ class VoiceAssistantRequest final : public ProtoMessage { const char *message_name() const override { return "voice_assistant_request"; } #endif bool start{false}; - StringRef conversation_id_ref_{}; - void set_conversation_id(const StringRef &ref) { this->conversation_id_ref_ = ref; } + StringRef conversation_id{}; uint32_t flags{0}; VoiceAssistantAudioSettings audio_settings{}; - StringRef wake_word_phrase_ref_{}; - void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; } + StringRef wake_word_phrase{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2405,7 +2404,7 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage { uint32_t port{0}; bool error{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2413,10 +2412,10 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage { }; class VoiceAssistantEventData final : public ProtoDecodableMessage { public: - std::string name{}; - std::string value{}; + StringRef name{}; + StringRef value{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2432,7 +2431,7 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage { enums::VoiceAssistantEvent event_type{}; std::vector data{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2442,22 +2441,17 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage { class VoiceAssistantAudio final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 106; - static constexpr uint8_t ESTIMATED_SIZE = 11; + static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_audio"; } #endif - std::string data{}; - const uint8_t *data_ptr_{nullptr}; - size_t data_len_{0}; - void set_data(const uint8_t *data, size_t len) { - this->data_ptr_ = data; - this->data_len_ = len; - } + const uint8_t *data{nullptr}; + uint16_t data_len{0}; bool end{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2472,13 +2466,13 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage { const char *message_name() const override { return "voice_assistant_timer_event_response"; } #endif enums::VoiceAssistantTimerEvent event_type{}; - std::string timer_id{}; - std::string name{}; + StringRef timer_id{}; + StringRef name{}; uint32_t total_seconds{0}; uint32_t seconds_left{0}; bool is_active{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2492,12 +2486,12 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "voice_assistant_announce_request"; } #endif - std::string media_id{}; - std::string text{}; - std::string preannounce_media_id{}; + StringRef media_id{}; + StringRef text{}; + StringRef preannounce_media_id{}; bool start_conversation{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2515,37 +2509,35 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: }; class VoiceAssistantWakeWord final : public ProtoMessage { public: - StringRef id_ref_{}; - void set_id(const StringRef &ref) { this->id_ref_ = ref; } - StringRef wake_word_ref_{}; - void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; } + StringRef id{}; + StringRef wake_word{}; std::vector trained_languages{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: }; class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage { public: - std::string id{}; - std::string wake_word{}; + StringRef id{}; + StringRef wake_word{}; std::vector trained_languages{}; - std::string model_type{}; + StringRef model_type{}; uint32_t model_size{0}; - std::string model_hash{}; - std::string url{}; + StringRef model_hash{}; + StringRef url{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2561,7 +2553,7 @@ class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage { #endif std::vector external_wake_words{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2580,7 +2572,7 @@ class VoiceAssistantConfigurationResponse final : public ProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2594,7 +2586,7 @@ class VoiceAssistantSetConfiguration final : public ProtoDecodableMessage { #endif std::vector active_wake_words{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2615,7 +2607,7 @@ class ListEntitiesAlarmControlPanelResponse final : public InfoResponseProtoMess void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2631,7 +2623,7 @@ class AlarmControlPanelStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2644,9 +2636,9 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage { const char *message_name() const override { return "alarm_control_panel_command_request"; } #endif enums::AlarmControlPanelStateCommand command{}; - std::string code{}; + StringRef code{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2665,13 +2657,12 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage { #endif uint32_t min_length{0}; uint32_t max_length{0}; - StringRef pattern_ref_{}; - void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; } + StringRef pattern{}; enums::TextMode mode{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2683,13 +2674,12 @@ class TextStateResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_state_response"; } #endif - StringRef state_ref_{}; - void set_state(const StringRef &ref) { this->state_ref_ = ref; } + StringRef state{}; bool missing_state{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2701,9 +2691,9 @@ class TextCommandRequest final : public CommandProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "text_command_request"; } #endif - std::string state{}; + StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2723,7 +2713,7 @@ class ListEntitiesDateResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2742,7 +2732,7 @@ class DateStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2758,7 +2748,7 @@ class DateCommandRequest final : public CommandProtoMessage { uint32_t month{0}; uint32_t day{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2777,7 +2767,7 @@ class ListEntitiesTimeResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2796,7 +2786,7 @@ class TimeStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2812,7 +2802,7 @@ class TimeCommandRequest final : public CommandProtoMessage { uint32_t minute{0}; uint32_t second{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2828,13 +2818,12 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_event_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; const FixedVector *event_types{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2846,12 +2835,11 @@ class EventResponse final : public StateResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "event_response"; } #endif - StringRef event_type_ref_{}; - void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; } + StringRef event_type{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2865,15 +2853,14 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_valve_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2890,7 +2877,7 @@ class ValveStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2906,7 +2893,7 @@ class ValveCommandRequest final : public CommandProtoMessage { float position{0.0f}; bool stop{false}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2925,7 +2912,7 @@ class ListEntitiesDateTimeResponse final : public InfoResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2942,7 +2929,7 @@ class DateTimeStateResponse final : public StateResponseProtoMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2956,7 +2943,7 @@ class DateTimeCommandRequest final : public CommandProtoMessage { #endif uint32_t epoch_seconds{0}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2972,12 +2959,11 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const char *message_name() const override { return "list_entities_update_response"; } #endif - StringRef device_class_ref_{}; - void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } + StringRef device_class{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -2993,20 +2979,15 @@ class UpdateStateResponse final : public StateResponseProtoMessage { bool in_progress{false}; bool has_progress{false}; float progress{0.0f}; - StringRef current_version_ref_{}; - void set_current_version(const StringRef &ref) { this->current_version_ref_ = ref; } - StringRef latest_version_ref_{}; - void set_latest_version(const StringRef &ref) { this->latest_version_ref_ = ref; } - StringRef title_ref_{}; - void set_title(const StringRef &ref) { this->title_ref_ = ref; } - StringRef release_summary_ref_{}; - void set_release_summary(const StringRef &ref) { this->release_summary_ref_ = ref; } - StringRef release_url_ref_{}; - void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; } + StringRef current_version{}; + StringRef latest_version{}; + StringRef title{}; + StringRef release_summary{}; + StringRef release_url{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -3020,7 +3001,7 @@ class UpdateCommandRequest final : public CommandProtoMessage { #endif enums::UpdateCommand command{}; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -3041,7 +3022,7 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -3060,7 +3041,7 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage { void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP - void dump_to(std::string &out) const override; + const char *dump_to(DumpBuffer &out) const override; #endif protected: @@ -3068,5 +3049,70 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage { bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; #endif +#ifdef USE_INFRARED +class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 135; + static constexpr uint8_t ESTIMATED_SIZE = 44; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "list_entities_infrared_response"; } +#endif + uint32_t capabilities{0}; + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +#endif +#ifdef USE_IR_RF +class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 136; + static constexpr uint8_t ESTIMATED_SIZE = 220; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "infrared_rf_transmit_raw_timings_request"; } +#endif +#ifdef USE_DEVICES + uint32_t device_id{0}; +#endif + uint32_t key{0}; + uint32_t carrier_frequency{0}; + uint32_t repeat_count{0}; + const uint8_t *timings_data_{nullptr}; + uint16_t timings_length_{0}; + uint16_t timings_count_{0}; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class InfraredRFReceiveEvent final : public ProtoMessage { + public: + static constexpr uint8_t MESSAGE_TYPE = 137; + static constexpr uint8_t ESTIMATED_SIZE = 17; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *message_name() const override { return "infrared_rf_receive_event"; } +#endif +#ifdef USE_DEVICES + uint32_t device_id{0}; +#endif + uint32_t key{0}; + const std::vector *timings{}; + void encode(ProtoWriteBuffer buffer) const override; + void calculate_size(ProtoSize &size) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 5e271f41cb..29121f05e0 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -10,7 +10,7 @@ namespace esphome::api { // Helper function to append a quoted string, handling empty StringRef -static inline void append_quoted_string(std::string &out, const StringRef &ref) { +static inline void append_quoted_string(DumpBuffer &out, const StringRef &ref) { out.append("'"); if (!ref.empty()) { out.append(ref.c_str()); @@ -19,87 +19,102 @@ static inline void append_quoted_string(std::string &out, const StringRef &ref) } // Common helpers for dump_field functions -static inline void append_field_prefix(std::string &out, const char *field_name, int indent) { +static inline void append_field_prefix(DumpBuffer &out, const char *field_name, int indent) { out.append(indent, ' ').append(field_name).append(": "); } -static inline void append_with_newline(std::string &out, const char *str) { +static inline void append_with_newline(DumpBuffer &out, const char *str) { out.append(str); out.append("\n"); } +static inline void append_uint(DumpBuffer &out, uint32_t value) { + char buf[16]; + snprintf(buf, sizeof(buf), "%" PRIu32, value); + out.append(buf); +} + // RAII helper for message dump formatting class MessageDumpHelper { public: - MessageDumpHelper(std::string &out, const char *message_name) : out_(out) { + MessageDumpHelper(DumpBuffer &out, const char *message_name) : out_(out) { out_.append(message_name); out_.append(" {\n"); } ~MessageDumpHelper() { out_.append(" }"); } private: - std::string &out_; + DumpBuffer &out_; }; // Helper functions to reduce code duplication in dump methods -static void dump_field(std::string &out, const char *field_name, int32_t value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, int32_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%" PRId32, value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, uint32_t value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, uint32_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%" PRIu32, value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, float value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, float value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%g", value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, uint64_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%" PRIu64, value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, bool value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, bool value, int indent = 2) { append_field_prefix(out, field_name, indent); out.append(YESNO(value)); out.append("\n"); } -static void dump_field(std::string &out, const char *field_name, const std::string &value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, const std::string &value, int indent = 2) { append_field_prefix(out, field_name, indent); - out.append("'").append(value).append("'"); + out.append("'").append(value.c_str()).append("'"); out.append("\n"); } -static void dump_field(std::string &out, const char *field_name, StringRef value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, StringRef value, int indent = 2) { append_field_prefix(out, field_name, indent); append_quoted_string(out, value); out.append("\n"); } -static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, const char *value, int indent = 2) { append_field_prefix(out, field_name, indent); out.append("'").append(value).append("'"); out.append("\n"); } -template static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) { +template static void dump_field(DumpBuffer &out, const char *field_name, T value, int indent = 2) { append_field_prefix(out, field_name, indent); out.append(proto_enum_to_string(value)); out.append("\n"); } +// Helper for bytes fields - uses stack buffer to avoid heap allocation +// Buffer sized for 160 bytes of data (480 chars with separators) to fit typical log buffer +static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint8_t *data, size_t len, int indent = 2) { + char hex_buf[format_hex_pretty_size(160)]; + append_field_prefix(out, field_name, indent); + format_hex_pretty_to(hex_buf, data, len); + append_with_newline(out, hex_buf); +} + template<> const char *proto_enum_to_string(enums::EntityCategory value) { switch (value) { case enums::ENTITY_CATEGORY_NONE: @@ -348,6 +363,47 @@ template<> const char *proto_enum_to_string(enums::Climate } } #endif +#ifdef USE_WATER_HEATER +template<> const char *proto_enum_to_string(enums::WaterHeaterMode value) { + switch (value) { + case enums::WATER_HEATER_MODE_OFF: + return "WATER_HEATER_MODE_OFF"; + case enums::WATER_HEATER_MODE_ECO: + return "WATER_HEATER_MODE_ECO"; + case enums::WATER_HEATER_MODE_ELECTRIC: + return "WATER_HEATER_MODE_ELECTRIC"; + case enums::WATER_HEATER_MODE_PERFORMANCE: + return "WATER_HEATER_MODE_PERFORMANCE"; + case enums::WATER_HEATER_MODE_HIGH_DEMAND: + return "WATER_HEATER_MODE_HIGH_DEMAND"; + case enums::WATER_HEATER_MODE_HEAT_PUMP: + return "WATER_HEATER_MODE_HEAT_PUMP"; + case enums::WATER_HEATER_MODE_GAS: + return "WATER_HEATER_MODE_GAS"; + default: + return "UNKNOWN"; + } +} +#endif +template<> +const char *proto_enum_to_string(enums::WaterHeaterCommandHasField value) { + switch (value) { + case enums::WATER_HEATER_COMMAND_HAS_NONE: + return "WATER_HEATER_COMMAND_HAS_NONE"; + case enums::WATER_HEATER_COMMAND_HAS_MODE: + return "WATER_HEATER_COMMAND_HAS_MODE"; + case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE: + return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE"; + case enums::WATER_HEATER_COMMAND_HAS_STATE: + return "WATER_HEATER_COMMAND_HAS_STATE"; + case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW: + return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW"; + case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH: + return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH"; + default: + return "UNKNOWN"; + } +} #ifdef USE_NUMBER template<> const char *proto_enum_to_string(enums::NumberMode value) { switch (value) { @@ -692,71 +748,73 @@ template<> const char *proto_enum_to_string(enums: } #endif -void HelloRequest::dump_to(std::string &out) const { +const char *HelloRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "HelloRequest"); - out.append(" client_info: "); - out.append(format_hex_pretty(this->client_info, this->client_info_len)); - out.append("\n"); + dump_field(out, "client_info", this->client_info); dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_minor", this->api_version_minor); + return out.c_str(); } -void HelloResponse::dump_to(std::string &out) const { +const char *HelloResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "HelloResponse"); dump_field(out, "api_version_major", this->api_version_major); dump_field(out, "api_version_minor", this->api_version_minor); - dump_field(out, "server_info", this->server_info_ref_); - dump_field(out, "name", this->name_ref_); + dump_field(out, "server_info", this->server_info); + dump_field(out, "name", this->name); + return out.c_str(); } -#ifdef USE_API_PASSWORD -void AuthenticationRequest::dump_to(std::string &out) const { - MessageDumpHelper helper(out, "AuthenticationRequest"); - out.append(" password: "); - out.append(format_hex_pretty(this->password, this->password_len)); - out.append("\n"); +const char *DisconnectRequest::dump_to(DumpBuffer &out) const { + out.append("DisconnectRequest {}"); + return out.c_str(); } -void AuthenticationResponse::dump_to(std::string &out) const { - MessageDumpHelper helper(out, "AuthenticationResponse"); - dump_field(out, "invalid_password", this->invalid_password); +const char *DisconnectResponse::dump_to(DumpBuffer &out) const { + out.append("DisconnectResponse {}"); + return out.c_str(); +} +const char *PingRequest::dump_to(DumpBuffer &out) const { + out.append("PingRequest {}"); + return out.c_str(); +} +const char *PingResponse::dump_to(DumpBuffer &out) const { + out.append("PingResponse {}"); + return out.c_str(); +} +const char *DeviceInfoRequest::dump_to(DumpBuffer &out) const { + out.append("DeviceInfoRequest {}"); + return out.c_str(); } -#endif -void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } -void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } -void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } -void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); } -void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } #ifdef USE_AREAS -void AreaInfo::dump_to(std::string &out) const { +const char *AreaInfo::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "AreaInfo"); dump_field(out, "area_id", this->area_id); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); + return out.c_str(); } #endif #ifdef USE_DEVICES -void DeviceInfo::dump_to(std::string &out) const { +const char *DeviceInfo::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "DeviceInfo"); dump_field(out, "device_id", this->device_id); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "area_id", this->area_id); + return out.c_str(); } #endif -void DeviceInfoResponse::dump_to(std::string &out) const { +const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "DeviceInfoResponse"); -#ifdef USE_API_PASSWORD - dump_field(out, "uses_password", this->uses_password); -#endif - dump_field(out, "name", this->name_ref_); - dump_field(out, "mac_address", this->mac_address_ref_); - dump_field(out, "esphome_version", this->esphome_version_ref_); - dump_field(out, "compilation_time", this->compilation_time_ref_); - dump_field(out, "model", this->model_ref_); + dump_field(out, "name", this->name); + dump_field(out, "mac_address", this->mac_address); + dump_field(out, "esphome_version", this->esphome_version); + dump_field(out, "compilation_time", this->compilation_time); + dump_field(out, "model", this->model); #ifdef USE_DEEP_SLEEP dump_field(out, "has_deep_sleep", this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - dump_field(out, "project_name", this->project_name_ref_); + dump_field(out, "project_name", this->project_name); #endif #ifdef ESPHOME_PROJECT_NAME - dump_field(out, "project_version", this->project_version_ref_); + dump_field(out, "project_version", this->project_version); #endif #ifdef USE_WEBSERVER dump_field(out, "webserver_port", this->webserver_port); @@ -764,16 +822,16 @@ void DeviceInfoResponse::dump_to(std::string &out) const { #ifdef USE_BLUETOOTH_PROXY dump_field(out, "bluetooth_proxy_feature_flags", this->bluetooth_proxy_feature_flags); #endif - dump_field(out, "manufacturer", this->manufacturer_ref_); - dump_field(out, "friendly_name", this->friendly_name_ref_); + dump_field(out, "manufacturer", this->manufacturer); + dump_field(out, "friendly_name", this->friendly_name); #ifdef USE_VOICE_ASSISTANT dump_field(out, "voice_assistant_feature_flags", this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - dump_field(out, "suggested_area", this->suggested_area_ref_); + dump_field(out, "suggested_area", this->suggested_area); #endif #ifdef USE_BLUETOOTH_PROXY - dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address_ref_); + dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address); #endif #ifdef USE_API_NOISE dump_field(out, "api_encryption_supported", this->api_encryption_supported); @@ -803,28 +861,39 @@ void DeviceInfoResponse::dump_to(std::string &out) const { #ifdef USE_ZWAVE_PROXY dump_field(out, "zwave_home_id", this->zwave_home_id); #endif + return out.c_str(); +} +const char *ListEntitiesRequest::dump_to(DumpBuffer &out) const { + out.append("ListEntitiesRequest {}"); + return out.c_str(); +} +const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const { + out.append("ListEntitiesDoneResponse {}"); + return out.c_str(); +} +const char *SubscribeStatesRequest::dump_to(DumpBuffer &out) const { + out.append("SubscribeStatesRequest {}"); + return out.c_str(); } -void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } -void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } -void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); } #ifdef USE_BINARY_SENSOR -void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { +const char *ListEntitiesBinarySensorResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "name", this->name); + dump_field(out, "device_class", this->device_class); dump_field(out, "is_status_binary_sensor", this->is_status_binary_sensor); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void BinarySensorStateResponse::dump_to(std::string &out) const { +const char *BinarySensorStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BinarySensorStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); @@ -832,29 +901,31 @@ void BinarySensorStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_COVER -void ListEntitiesCoverResponse::dump_to(std::string &out) const { +const char *ListEntitiesCoverResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesCoverResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "supports_position", this->supports_position); dump_field(out, "supports_tilt", this->supports_tilt); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "supports_stop", this->supports_stop); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void CoverStateResponse::dump_to(std::string &out) const { +const char *CoverStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "CoverStateResponse"); dump_field(out, "key", this->key); dump_field(out, "position", this->position); @@ -863,8 +934,9 @@ void CoverStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void CoverCommandRequest::dump_to(std::string &out) const { +const char *CoverCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "CoverCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "has_position", this->has_position); @@ -875,21 +947,22 @@ void CoverCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_FAN -void ListEntitiesFanResponse::dump_to(std::string &out) const { +const char *ListEntitiesFanResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesFanResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "supports_oscillation", this->supports_oscillation); dump_field(out, "supports_speed", this->supports_speed); dump_field(out, "supports_direction", this->supports_direction); dump_field(out, "supported_speed_count", this->supported_speed_count); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); for (const auto &it : *this->supported_preset_modes) { @@ -898,20 +971,22 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void FanStateResponse::dump_to(std::string &out) const { +const char *FanStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "FanStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); dump_field(out, "oscillating", this->oscillating); dump_field(out, "direction", static_cast(this->direction)); dump_field(out, "speed_level", this->speed_level); - dump_field(out, "preset_mode", this->preset_mode_ref_); + dump_field(out, "preset_mode", this->preset_mode); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void FanCommandRequest::dump_to(std::string &out) const { +const char *FanCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "FanCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "has_state", this->has_state); @@ -927,14 +1002,15 @@ void FanCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_LIGHT -void ListEntitiesLightResponse::dump_to(std::string &out) const { +const char *ListEntitiesLightResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesLightResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); for (const auto &it : *this->supported_color_modes) { dump_field(out, "supported_color_modes", static_cast(it), 4); } @@ -945,14 +1021,15 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { } dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void LightStateResponse::dump_to(std::string &out) const { +const char *LightStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "LightStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); @@ -966,12 +1043,13 @@ void LightStateResponse::dump_to(std::string &out) const { dump_field(out, "color_temperature", this->color_temperature); dump_field(out, "cold_white", this->cold_white); dump_field(out, "warm_white", this->warm_white); - dump_field(out, "effect", this->effect_ref_); + dump_field(out, "effect", this->effect); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void LightCommandRequest::dump_to(std::string &out) const { +const char *LightCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "LightCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "has_state", this->has_state); @@ -999,35 +1077,35 @@ void LightCommandRequest::dump_to(std::string &out) const { dump_field(out, "has_flash_length", this->has_flash_length); dump_field(out, "flash_length", this->flash_length); dump_field(out, "has_effect", this->has_effect); - out.append(" effect: "); - out.append(format_hex_pretty(this->effect, this->effect_len)); - out.append("\n"); + dump_field(out, "effect", this->effect); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_SENSOR -void ListEntitiesSensorResponse::dump_to(std::string &out) const { +const char *ListEntitiesSensorResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesSensorResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif - dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_); + dump_field(out, "unit_of_measurement", this->unit_of_measurement); dump_field(out, "accuracy_decimals", this->accuracy_decimals); dump_field(out, "force_update", this->force_update); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); dump_field(out, "state_class", static_cast(this->state_class)); dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void SensorStateResponse::dump_to(std::string &out) const { +const char *SensorStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SensorStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); @@ -1035,101 +1113,112 @@ void SensorStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_SWITCH -void ListEntitiesSwitchResponse::dump_to(std::string &out) const { +const char *ListEntitiesSwitchResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesSwitchResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void SwitchStateResponse::dump_to(std::string &out) const { +const char *SwitchStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SwitchStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void SwitchCommandRequest::dump_to(std::string &out) const { +const char *SwitchCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SwitchCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_TEXT_SENSOR -void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { +const char *ListEntitiesTextSensorResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesTextSensorResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void TextSensorStateResponse::dump_to(std::string &out) const { +const char *TextSensorStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "TextSensorStateResponse"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state_ref_); + dump_field(out, "state", this->state); dump_field(out, "missing_state", this->missing_state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif -void SubscribeLogsRequest::dump_to(std::string &out) const { +const char *SubscribeLogsRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SubscribeLogsRequest"); dump_field(out, "level", static_cast(this->level)); dump_field(out, "dump_config", this->dump_config); + return out.c_str(); } -void SubscribeLogsResponse::dump_to(std::string &out) const { +const char *SubscribeLogsResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SubscribeLogsResponse"); dump_field(out, "level", static_cast(this->level)); - out.append(" message: "); - out.append(format_hex_pretty(this->message_ptr_, this->message_len_)); - out.append("\n"); + dump_bytes_field(out, "message", this->message_ptr_, this->message_len_); + return out.c_str(); } #ifdef USE_API_NOISE -void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const { +const char *NoiseEncryptionSetKeyRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest"); - out.append(" key: "); - out.append(format_hex_pretty(reinterpret_cast(this->key.data()), this->key.size())); - out.append("\n"); + dump_bytes_field(out, "key", this->key, this->key_len); + return out.c_str(); +} +const char *NoiseEncryptionSetKeyResponse::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse"); + dump_field(out, "success", this->success); + return out.c_str(); } -void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); } #endif #ifdef USE_API_HOMEASSISTANT_SERVICES -void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { +const char *SubscribeHomeassistantServicesRequest::dump_to(DumpBuffer &out) const { out.append("SubscribeHomeassistantServicesRequest {}"); + return out.c_str(); } -void HomeassistantServiceMap::dump_to(std::string &out) const { +const char *HomeassistantServiceMap::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "HomeassistantServiceMap"); - dump_field(out, "key", this->key_ref_); + dump_field(out, "key", this->key); dump_field(out, "value", this->value); + return out.c_str(); } -void HomeassistantActionRequest::dump_to(std::string &out) const { +const char *HomeassistantActionRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "HomeassistantActionRequest"); - dump_field(out, "service", this->service_ref_); + dump_field(out, "service", this->service); for (const auto &it : this->data) { out.append(" data: "); it.dump_to(out); @@ -1155,55 +1244,61 @@ void HomeassistantActionRequest::dump_to(std::string &out) const { #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON dump_field(out, "response_template", this->response_template); #endif + return out.c_str(); } #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES -void HomeassistantActionResponse::dump_to(std::string &out) const { +const char *HomeassistantActionResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "HomeassistantActionResponse"); dump_field(out, "call_id", this->call_id); dump_field(out, "success", this->success); dump_field(out, "error_message", this->error_message); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - out.append(" response_data: "); - out.append(format_hex_pretty(this->response_data, this->response_data_len)); - out.append("\n"); + dump_bytes_field(out, "response_data", this->response_data, this->response_data_len); #endif + return out.c_str(); } #endif #ifdef USE_API_HOMEASSISTANT_STATES -void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { +const char *SubscribeHomeAssistantStatesRequest::dump_to(DumpBuffer &out) const { out.append("SubscribeHomeAssistantStatesRequest {}"); + return out.c_str(); } -void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { +const char *SubscribeHomeAssistantStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse"); - dump_field(out, "entity_id", this->entity_id_ref_); - dump_field(out, "attribute", this->attribute_ref_); + dump_field(out, "entity_id", this->entity_id); + dump_field(out, "attribute", this->attribute); dump_field(out, "once", this->once); + return out.c_str(); } -void HomeAssistantStateResponse::dump_to(std::string &out) const { +const char *HomeAssistantStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "HomeAssistantStateResponse"); dump_field(out, "entity_id", this->entity_id); dump_field(out, "state", this->state); dump_field(out, "attribute", this->attribute); + return out.c_str(); } #endif -void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } -void GetTimeResponse::dump_to(std::string &out) const { +const char *GetTimeRequest::dump_to(DumpBuffer &out) const { + out.append("GetTimeRequest {}"); + return out.c_str(); +} +const char *GetTimeResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "GetTimeResponse"); dump_field(out, "epoch_seconds", this->epoch_seconds); - out.append(" timezone: "); - out.append(format_hex_pretty(this->timezone, this->timezone_len)); - out.append("\n"); + dump_field(out, "timezone", this->timezone); + return out.c_str(); } #ifdef USE_API_USER_DEFINED_ACTIONS -void ListEntitiesServicesArgument::dump_to(std::string &out) const { +const char *ListEntitiesServicesArgument::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesServicesArgument"); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "type", static_cast(this->type)); + return out.c_str(); } -void ListEntitiesServicesResponse::dump_to(std::string &out) const { +const char *ListEntitiesServicesResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesServicesResponse"); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "key", this->key); for (const auto &it : this->args) { out.append(" args: "); @@ -1211,8 +1306,9 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const { out.append("\n"); } dump_field(out, "supports_response", static_cast(this->supports_response)); + return out.c_str(); } -void ExecuteServiceArgument::dump_to(std::string &out) const { +const char *ExecuteServiceArgument::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ExecuteServiceArgument"); dump_field(out, "bool_", this->bool_); dump_field(out, "legacy_int", this->legacy_int); @@ -1231,8 +1327,9 @@ void ExecuteServiceArgument::dump_to(std::string &out) const { for (const auto &it : this->string_array) { dump_field(out, "string_array", it, 4); } + return out.c_str(); } -void ExecuteServiceRequest::dump_to(std::string &out) const { +const char *ExecuteServiceRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ExecuteServiceRequest"); dump_field(out, "key", this->key); for (const auto &it : this->args) { @@ -1246,59 +1343,60 @@ void ExecuteServiceRequest::dump_to(std::string &out) const { #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES dump_field(out, "return_response", this->return_response); #endif + return out.c_str(); } #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES -void ExecuteServiceResponse::dump_to(std::string &out) const { +const char *ExecuteServiceResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ExecuteServiceResponse"); dump_field(out, "call_id", this->call_id); dump_field(out, "success", this->success); - dump_field(out, "error_message", this->error_message_ref_); + dump_field(out, "error_message", this->error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - out.append(" response_data: "); - out.append(format_hex_pretty(this->response_data, this->response_data_len)); - out.append("\n"); + dump_bytes_field(out, "response_data", this->response_data, this->response_data_len); #endif + return out.c_str(); } #endif #ifdef USE_CAMERA -void ListEntitiesCameraResponse::dump_to(std::string &out) const { +const char *ListEntitiesCameraResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesCameraResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void CameraImageResponse::dump_to(std::string &out) const { +const char *CameraImageResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "CameraImageResponse"); dump_field(out, "key", this->key); - out.append(" data: "); - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); - out.append("\n"); + dump_bytes_field(out, "data", this->data_ptr_, this->data_len_); dump_field(out, "done", this->done); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void CameraImageRequest::dump_to(std::string &out) const { +const char *CameraImageRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "CameraImageRequest"); dump_field(out, "single", this->single); dump_field(out, "stream", this->stream); + return out.c_str(); } #endif #ifdef USE_CLIMATE -void ListEntitiesClimateResponse::dump_to(std::string &out) const { +const char *ListEntitiesClimateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesClimateResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); dump_field(out, "supports_current_temperature", this->supports_current_temperature); dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature); for (const auto &it : *this->supported_modes) { @@ -1325,7 +1423,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { } dump_field(out, "disabled_by_default", this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "visual_current_temperature_step", this->visual_current_temperature_step); @@ -1337,8 +1435,9 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { dump_field(out, "device_id", this->device_id); #endif dump_field(out, "feature_flags", this->feature_flags); + return out.c_str(); } -void ClimateStateResponse::dump_to(std::string &out) const { +const char *ClimateStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ClimateStateResponse"); dump_field(out, "key", this->key); dump_field(out, "mode", static_cast(this->mode)); @@ -1349,16 +1448,17 @@ void ClimateStateResponse::dump_to(std::string &out) const { dump_field(out, "action", static_cast(this->action)); dump_field(out, "fan_mode", static_cast(this->fan_mode)); dump_field(out, "swing_mode", static_cast(this->swing_mode)); - dump_field(out, "custom_fan_mode", this->custom_fan_mode_ref_); + dump_field(out, "custom_fan_mode", this->custom_fan_mode); dump_field(out, "preset", static_cast(this->preset)); - dump_field(out, "custom_preset", this->custom_preset_ref_); + dump_field(out, "custom_preset", this->custom_preset); dump_field(out, "current_humidity", this->current_humidity); dump_field(out, "target_humidity", this->target_humidity); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void ClimateCommandRequest::dump_to(std::string &out) const { +const char *ClimateCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ClimateCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "has_mode", this->has_mode); @@ -1384,30 +1484,84 @@ void ClimateCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); +} +#endif +#ifdef USE_WATER_HEATER +const char *ListEntitiesWaterHeaterResponse::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse"); + dump_field(out, "object_id", this->object_id); + dump_field(out, "key", this->key); + dump_field(out, "name", this->name); +#ifdef USE_ENTITY_ICON + dump_field(out, "icon", this->icon); +#endif + dump_field(out, "disabled_by_default", this->disabled_by_default); + dump_field(out, "entity_category", static_cast(this->entity_category)); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "min_temperature", this->min_temperature); + dump_field(out, "max_temperature", this->max_temperature); + dump_field(out, "target_temperature_step", this->target_temperature_step); + for (const auto &it : *this->supported_modes) { + dump_field(out, "supported_modes", static_cast(it), 4); + } + dump_field(out, "supported_features", this->supported_features); + return out.c_str(); +} +const char *WaterHeaterStateResponse::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "WaterHeaterStateResponse"); + dump_field(out, "key", this->key); + dump_field(out, "current_temperature", this->current_temperature); + dump_field(out, "target_temperature", this->target_temperature); + dump_field(out, "mode", static_cast(this->mode)); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "state", this->state); + dump_field(out, "target_temperature_low", this->target_temperature_low); + dump_field(out, "target_temperature_high", this->target_temperature_high); + return out.c_str(); +} +const char *WaterHeaterCommandRequest::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "WaterHeaterCommandRequest"); + dump_field(out, "key", this->key); + dump_field(out, "has_fields", this->has_fields); + dump_field(out, "mode", static_cast(this->mode)); + dump_field(out, "target_temperature", this->target_temperature); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "state", this->state); + dump_field(out, "target_temperature_low", this->target_temperature_low); + dump_field(out, "target_temperature_high", this->target_temperature_high); + return out.c_str(); } #endif #ifdef USE_NUMBER -void ListEntitiesNumberResponse::dump_to(std::string &out) const { +const char *ListEntitiesNumberResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesNumberResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "min_value", this->min_value); dump_field(out, "max_value", this->max_value); dump_field(out, "step", this->step); dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_); + dump_field(out, "unit_of_measurement", this->unit_of_measurement); dump_field(out, "mode", static_cast(this->mode)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void NumberStateResponse::dump_to(std::string &out) const { +const char *NumberStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "NumberStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); @@ -1415,24 +1569,26 @@ void NumberStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void NumberCommandRequest::dump_to(std::string &out) const { +const char *NumberCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "NumberCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_SELECT -void ListEntitiesSelectResponse::dump_to(std::string &out) const { +const char *ListEntitiesSelectResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesSelectResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif for (const auto &it : *this->options) { dump_field(out, "options", it, 4); @@ -1442,38 +1598,39 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void SelectStateResponse::dump_to(std::string &out) const { +const char *SelectStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SelectStateResponse"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state_ref_); + dump_field(out, "state", this->state); dump_field(out, "missing_state", this->missing_state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void SelectCommandRequest::dump_to(std::string &out) const { +const char *SelectCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SelectCommandRequest"); dump_field(out, "key", this->key); - out.append(" state: "); - out.append(format_hex_pretty(this->state, this->state_len)); - out.append("\n"); + dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_SIREN -void ListEntitiesSirenResponse::dump_to(std::string &out) const { +const char *ListEntitiesSirenResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesSirenResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); - for (const auto &it : this->tones) { + for (const auto &it : *this->tones) { dump_field(out, "tones", it, 4); } dump_field(out, "supports_duration", this->supports_duration); @@ -1482,16 +1639,18 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void SirenStateResponse::dump_to(std::string &out) const { +const char *SirenStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SirenStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void SirenCommandRequest::dump_to(std::string &out) const { +const char *SirenCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SirenCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "has_state", this->has_state); @@ -1505,36 +1664,39 @@ void SirenCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_LOCK -void ListEntitiesLockResponse::dump_to(std::string &out) const { +const char *ListEntitiesLockResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesLockResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "supports_open", this->supports_open); dump_field(out, "requires_code", this->requires_code); - dump_field(out, "code_format", this->code_format_ref_); + dump_field(out, "code_format", this->code_format); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void LockStateResponse::dump_to(std::string &out) const { +const char *LockStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "LockStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", static_cast(this->state)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void LockCommandRequest::dump_to(std::string &out) const { +const char *LockCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "LockCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); @@ -1543,48 +1705,52 @@ void LockCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_BUTTON -void ListEntitiesButtonResponse::dump_to(std::string &out) const { +const char *ListEntitiesButtonResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesButtonResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void ButtonCommandRequest::dump_to(std::string &out) const { +const char *ButtonCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ButtonCommandRequest"); dump_field(out, "key", this->key); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_MEDIA_PLAYER -void MediaPlayerSupportedFormat::dump_to(std::string &out) const { +const char *MediaPlayerSupportedFormat::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "MediaPlayerSupportedFormat"); - dump_field(out, "format", this->format_ref_); + dump_field(out, "format", this->format); dump_field(out, "sample_rate", this->sample_rate); dump_field(out, "num_channels", this->num_channels); dump_field(out, "purpose", static_cast(this->purpose)); dump_field(out, "sample_bytes", this->sample_bytes); + return out.c_str(); } -void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { +const char *ListEntitiesMediaPlayerResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesMediaPlayerResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -1598,8 +1764,9 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { dump_field(out, "device_id", this->device_id); #endif dump_field(out, "feature_flags", this->feature_flags); + return out.c_str(); } -void MediaPlayerStateResponse::dump_to(std::string &out) const { +const char *MediaPlayerStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "MediaPlayerStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", static_cast(this->state)); @@ -1608,8 +1775,9 @@ void MediaPlayerStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void MediaPlayerCommandRequest::dump_to(std::string &out) const { +const char *MediaPlayerCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "MediaPlayerCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "has_command", this->has_command); @@ -1623,54 +1791,63 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_BLUETOOTH_PROXY -void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { +const char *SubscribeBluetoothLEAdvertisementsRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SubscribeBluetoothLEAdvertisementsRequest"); dump_field(out, "flags", this->flags); + return out.c_str(); } -void BluetoothLERawAdvertisement::dump_to(std::string &out) const { +const char *BluetoothLERawAdvertisement::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothLERawAdvertisement"); dump_field(out, "address", this->address); dump_field(out, "rssi", this->rssi); dump_field(out, "address_type", this->address_type); - out.append(" data: "); - out.append(format_hex_pretty(this->data, this->data_len)); - out.append("\n"); + dump_bytes_field(out, "data", this->data, this->data_len); + return out.c_str(); } -void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const { +const char *BluetoothLERawAdvertisementsResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse"); for (uint16_t i = 0; i < this->advertisements_len; i++) { out.append(" advertisements: "); this->advertisements[i].dump_to(out); out.append("\n"); } + return out.c_str(); } -void BluetoothDeviceRequest::dump_to(std::string &out) const { +const char *BluetoothDeviceRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothDeviceRequest"); dump_field(out, "address", this->address); dump_field(out, "request_type", static_cast(this->request_type)); dump_field(out, "has_address_type", this->has_address_type); dump_field(out, "address_type", this->address_type); + return out.c_str(); } -void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const { +const char *BluetoothDeviceConnectionResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothDeviceConnectionResponse"); dump_field(out, "address", this->address); dump_field(out, "connected", this->connected); dump_field(out, "mtu", this->mtu); dump_field(out, "error", this->error); + return out.c_str(); } -void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { dump_field(out, "address", this->address); } -void BluetoothGATTDescriptor::dump_to(std::string &out) const { +const char *BluetoothGATTGetServicesRequest::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "BluetoothGATTGetServicesRequest"); + dump_field(out, "address", this->address); + return out.c_str(); +} +const char *BluetoothGATTDescriptor::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTDescriptor"); for (const auto &it : this->uuid) { dump_field(out, "uuid", it, 4); } dump_field(out, "handle", this->handle); dump_field(out, "short_uuid", this->short_uuid); + return out.c_str(); } -void BluetoothGATTCharacteristic::dump_to(std::string &out) const { +const char *BluetoothGATTCharacteristic::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTCharacteristic"); for (const auto &it : this->uuid) { dump_field(out, "uuid", it, 4); @@ -1683,8 +1860,9 @@ void BluetoothGATTCharacteristic::dump_to(std::string &out) const { out.append("\n"); } dump_field(out, "short_uuid", this->short_uuid); + return out.c_str(); } -void BluetoothGATTService::dump_to(std::string &out) const { +const char *BluetoothGATTService::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTService"); for (const auto &it : this->uuid) { dump_field(out, "uuid", it, 4); @@ -1696,8 +1874,9 @@ void BluetoothGATTService::dump_to(std::string &out) const { out.append("\n"); } dump_field(out, "short_uuid", this->short_uuid); + return out.c_str(); } -void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const { +const char *BluetoothGATTGetServicesResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTGetServicesResponse"); dump_field(out, "address", this->address); for (const auto &it : this->services) { @@ -1705,152 +1884,169 @@ void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } + return out.c_str(); } -void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const { +const char *BluetoothGATTGetServicesDoneResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTGetServicesDoneResponse"); dump_field(out, "address", this->address); + return out.c_str(); } -void BluetoothGATTReadRequest::dump_to(std::string &out) const { +const char *BluetoothGATTReadRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTReadRequest"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); + return out.c_str(); } -void BluetoothGATTReadResponse::dump_to(std::string &out) const { +const char *BluetoothGATTReadResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTReadResponse"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); - out.append(" data: "); - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); - out.append("\n"); + dump_bytes_field(out, "data", this->data_ptr_, this->data_len_); + return out.c_str(); } -void BluetoothGATTWriteRequest::dump_to(std::string &out) const { +const char *BluetoothGATTWriteRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTWriteRequest"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); dump_field(out, "response", this->response); - out.append(" data: "); - out.append(format_hex_pretty(this->data, this->data_len)); - out.append("\n"); + dump_bytes_field(out, "data", this->data, this->data_len); + return out.c_str(); } -void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const { +const char *BluetoothGATTReadDescriptorRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTReadDescriptorRequest"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); + return out.c_str(); } -void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const { +const char *BluetoothGATTWriteDescriptorRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTWriteDescriptorRequest"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); - out.append(" data: "); - out.append(format_hex_pretty(this->data, this->data_len)); - out.append("\n"); + dump_bytes_field(out, "data", this->data, this->data_len); + return out.c_str(); } -void BluetoothGATTNotifyRequest::dump_to(std::string &out) const { +const char *BluetoothGATTNotifyRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTNotifyRequest"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); dump_field(out, "enable", this->enable); + return out.c_str(); } -void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { +const char *BluetoothGATTNotifyDataResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTNotifyDataResponse"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); - out.append(" data: "); - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); - out.append("\n"); + dump_bytes_field(out, "data", this->data_ptr_, this->data_len_); + return out.c_str(); } -void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const { +const char *SubscribeBluetoothConnectionsFreeRequest::dump_to(DumpBuffer &out) const { out.append("SubscribeBluetoothConnectionsFreeRequest {}"); + return out.c_str(); } -void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const { +const char *BluetoothConnectionsFreeResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothConnectionsFreeResponse"); dump_field(out, "free", this->free); dump_field(out, "limit", this->limit); for (const auto &it : this->allocated) { dump_field(out, "allocated", it, 4); } + return out.c_str(); } -void BluetoothGATTErrorResponse::dump_to(std::string &out) const { +const char *BluetoothGATTErrorResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTErrorResponse"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); dump_field(out, "error", this->error); + return out.c_str(); } -void BluetoothGATTWriteResponse::dump_to(std::string &out) const { +const char *BluetoothGATTWriteResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTWriteResponse"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); + return out.c_str(); } -void BluetoothGATTNotifyResponse::dump_to(std::string &out) const { +const char *BluetoothGATTNotifyResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothGATTNotifyResponse"); dump_field(out, "address", this->address); dump_field(out, "handle", this->handle); + return out.c_str(); } -void BluetoothDevicePairingResponse::dump_to(std::string &out) const { +const char *BluetoothDevicePairingResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothDevicePairingResponse"); dump_field(out, "address", this->address); dump_field(out, "paired", this->paired); dump_field(out, "error", this->error); + return out.c_str(); } -void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { +const char *BluetoothDeviceUnpairingResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothDeviceUnpairingResponse"); dump_field(out, "address", this->address); dump_field(out, "success", this->success); dump_field(out, "error", this->error); + return out.c_str(); } -void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { +const char *UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(DumpBuffer &out) const { out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}"); + return out.c_str(); } -void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const { +const char *BluetoothDeviceClearCacheResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothDeviceClearCacheResponse"); dump_field(out, "address", this->address); dump_field(out, "success", this->success); dump_field(out, "error", this->error); + return out.c_str(); } -void BluetoothScannerStateResponse::dump_to(std::string &out) const { +const char *BluetoothScannerStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothScannerStateResponse"); dump_field(out, "state", static_cast(this->state)); dump_field(out, "mode", static_cast(this->mode)); dump_field(out, "configured_mode", static_cast(this->configured_mode)); + return out.c_str(); } -void BluetoothScannerSetModeRequest::dump_to(std::string &out) const { +const char *BluetoothScannerSetModeRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest"); dump_field(out, "mode", static_cast(this->mode)); + return out.c_str(); } #endif #ifdef USE_VOICE_ASSISTANT -void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { +const char *SubscribeVoiceAssistantRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "SubscribeVoiceAssistantRequest"); dump_field(out, "subscribe", this->subscribe); dump_field(out, "flags", this->flags); + return out.c_str(); } -void VoiceAssistantAudioSettings::dump_to(std::string &out) const { +const char *VoiceAssistantAudioSettings::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantAudioSettings"); dump_field(out, "noise_suppression_level", this->noise_suppression_level); dump_field(out, "auto_gain", this->auto_gain); dump_field(out, "volume_multiplier", this->volume_multiplier); + return out.c_str(); } -void VoiceAssistantRequest::dump_to(std::string &out) const { +const char *VoiceAssistantRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantRequest"); dump_field(out, "start", this->start); - dump_field(out, "conversation_id", this->conversation_id_ref_); + dump_field(out, "conversation_id", this->conversation_id); dump_field(out, "flags", this->flags); out.append(" audio_settings: "); this->audio_settings.dump_to(out); out.append("\n"); - dump_field(out, "wake_word_phrase", this->wake_word_phrase_ref_); + dump_field(out, "wake_word_phrase", this->wake_word_phrase); + return out.c_str(); } -void VoiceAssistantResponse::dump_to(std::string &out) const { +const char *VoiceAssistantResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantResponse"); dump_field(out, "port", this->port); dump_field(out, "error", this->error); + return out.c_str(); } -void VoiceAssistantEventData::dump_to(std::string &out) const { +const char *VoiceAssistantEventData::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventData"); dump_field(out, "name", this->name); dump_field(out, "value", this->value); + return out.c_str(); } -void VoiceAssistantEventResponse::dump_to(std::string &out) const { +const char *VoiceAssistantEventResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantEventResponse"); dump_field(out, "event_type", static_cast(this->event_type)); for (const auto &it : this->data) { @@ -1858,19 +2054,15 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const { it.dump_to(out); out.append("\n"); } + return out.c_str(); } -void VoiceAssistantAudio::dump_to(std::string &out) const { +const char *VoiceAssistantAudio::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantAudio"); - out.append(" data: "); - if (this->data_ptr_ != nullptr) { - out.append(format_hex_pretty(this->data_ptr_, this->data_len_)); - } else { - out.append(format_hex_pretty(reinterpret_cast(this->data.data()), this->data.size())); - } - out.append("\n"); + dump_bytes_field(out, "data", this->data, this->data_len); dump_field(out, "end", this->end); + return out.c_str(); } -void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { +const char *VoiceAssistantTimerEventResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse"); dump_field(out, "event_type", static_cast(this->event_type)); dump_field(out, "timer_id", this->timer_id); @@ -1878,24 +2070,31 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { dump_field(out, "total_seconds", this->total_seconds); dump_field(out, "seconds_left", this->seconds_left); dump_field(out, "is_active", this->is_active); + return out.c_str(); } -void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const { +const char *VoiceAssistantAnnounceRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest"); dump_field(out, "media_id", this->media_id); dump_field(out, "text", this->text); dump_field(out, "preannounce_media_id", this->preannounce_media_id); dump_field(out, "start_conversation", this->start_conversation); + return out.c_str(); } -void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); } -void VoiceAssistantWakeWord::dump_to(std::string &out) const { +const char *VoiceAssistantAnnounceFinished::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "VoiceAssistantAnnounceFinished"); + dump_field(out, "success", this->success); + return out.c_str(); +} +const char *VoiceAssistantWakeWord::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantWakeWord"); - dump_field(out, "id", this->id_ref_); - dump_field(out, "wake_word", this->wake_word_ref_); + dump_field(out, "id", this->id); + dump_field(out, "wake_word", this->wake_word); for (const auto &it : this->trained_languages) { dump_field(out, "trained_languages", it, 4); } + return out.c_str(); } -void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const { +const char *VoiceAssistantExternalWakeWord::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord"); dump_field(out, "id", this->id); dump_field(out, "wake_word", this->wake_word); @@ -1906,16 +2105,18 @@ void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const { dump_field(out, "model_size", this->model_size); dump_field(out, "model_hash", this->model_hash); dump_field(out, "url", this->url); + return out.c_str(); } -void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { +const char *VoiceAssistantConfigurationRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest"); for (const auto &it : this->external_wake_words) { out.append(" external_wake_words: "); it.dump_to(out); out.append("\n"); } + return out.c_str(); } -void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { +const char *VoiceAssistantConfigurationResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse"); for (const auto &it : this->available_wake_words) { out.append(" available_wake_words: "); @@ -1926,22 +2127,24 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { dump_field(out, "active_wake_words", it, 4); } dump_field(out, "max_active_wake_words", this->max_active_wake_words); + return out.c_str(); } -void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { +const char *VoiceAssistantSetConfiguration::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "VoiceAssistantSetConfiguration"); for (const auto &it : this->active_wake_words) { dump_field(out, "active_wake_words", it, 4); } + return out.c_str(); } #endif #ifdef USE_ALARM_CONTROL_PANEL -void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { +const char *ListEntitiesAlarmControlPanelResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesAlarmControlPanelResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); @@ -1951,16 +2154,18 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void AlarmControlPanelStateResponse::dump_to(std::string &out) const { +const char *AlarmControlPanelStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "AlarmControlPanelStateResponse"); dump_field(out, "key", this->key); dump_field(out, "state", static_cast(this->state)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { +const char *AlarmControlPanelCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); @@ -1968,61 +2173,66 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_TEXT -void ListEntitiesTextResponse::dump_to(std::string &out) const { +const char *ListEntitiesTextResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesTextResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); dump_field(out, "min_length", this->min_length); dump_field(out, "max_length", this->max_length); - dump_field(out, "pattern", this->pattern_ref_); + dump_field(out, "pattern", this->pattern); dump_field(out, "mode", static_cast(this->mode)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void TextStateResponse::dump_to(std::string &out) const { +const char *TextStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "TextStateResponse"); dump_field(out, "key", this->key); - dump_field(out, "state", this->state_ref_); + dump_field(out, "state", this->state); dump_field(out, "missing_state", this->missing_state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void TextCommandRequest::dump_to(std::string &out) const { +const char *TextCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "TextCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "state", this->state); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_DATETIME_DATE -void ListEntitiesDateResponse::dump_to(std::string &out) const { +const char *ListEntitiesDateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesDateResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void DateStateResponse::dump_to(std::string &out) const { +const char *DateStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "DateStateResponse"); dump_field(out, "key", this->key); dump_field(out, "missing_state", this->missing_state); @@ -2032,8 +2242,9 @@ void DateStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void DateCommandRequest::dump_to(std::string &out) const { +const char *DateCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "DateCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "year", this->year); @@ -2042,24 +2253,26 @@ void DateCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_DATETIME_TIME -void ListEntitiesTimeResponse::dump_to(std::string &out) const { +const char *ListEntitiesTimeResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesTimeResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void TimeStateResponse::dump_to(std::string &out) const { +const char *TimeStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "TimeStateResponse"); dump_field(out, "key", this->key); dump_field(out, "missing_state", this->missing_state); @@ -2069,8 +2282,9 @@ void TimeStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void TimeCommandRequest::dump_to(std::string &out) const { +const char *TimeCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "TimeCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "hour", this->hour); @@ -2079,56 +2293,60 @@ void TimeCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_EVENT -void ListEntitiesEventResponse::dump_to(std::string &out) const { +const char *ListEntitiesEventResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesEventResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); for (const auto &it : *this->event_types) { dump_field(out, "event_types", it, 4); } #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void EventResponse::dump_to(std::string &out) const { +const char *EventResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "EventResponse"); dump_field(out, "key", this->key); - dump_field(out, "event_type", this->event_type_ref_); + dump_field(out, "event_type", this->event_type); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_VALVE -void ListEntitiesValveResponse::dump_to(std::string &out) const { +const char *ListEntitiesValveResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesValveResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); dump_field(out, "assumed_state", this->assumed_state); dump_field(out, "supports_position", this->supports_position); dump_field(out, "supports_stop", this->supports_stop); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void ValveStateResponse::dump_to(std::string &out) const { +const char *ValveStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ValveStateResponse"); dump_field(out, "key", this->key); dump_field(out, "position", this->position); @@ -2136,8 +2354,9 @@ void ValveStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void ValveCommandRequest::dump_to(std::string &out) const { +const char *ValveCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ValveCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "has_position", this->has_position); @@ -2146,24 +2365,26 @@ void ValveCommandRequest::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_DATETIME_DATETIME -void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { +const char *ListEntitiesDateTimeResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesDateTimeResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void DateTimeStateResponse::dump_to(std::string &out) const { +const char *DateTimeStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "DateTimeStateResponse"); dump_field(out, "key", this->key); dump_field(out, "missing_state", this->missing_state); @@ -2171,70 +2392,120 @@ void DateTimeStateResponse::dump_to(std::string &out) const { #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void DateTimeCommandRequest::dump_to(std::string &out) const { +const char *DateTimeCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "DateTimeCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "epoch_seconds", this->epoch_seconds); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_UPDATE -void ListEntitiesUpdateResponse::dump_to(std::string &out) const { +const char *ListEntitiesUpdateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ListEntitiesUpdateResponse"); - dump_field(out, "object_id", this->object_id_ref_); + dump_field(out, "object_id", this->object_id); dump_field(out, "key", this->key); - dump_field(out, "name", this->name_ref_); + dump_field(out, "name", this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon_ref_); + dump_field(out, "icon", this->icon); #endif dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class_ref_); + dump_field(out, "device_class", this->device_class); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void UpdateStateResponse::dump_to(std::string &out) const { +const char *UpdateStateResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "UpdateStateResponse"); dump_field(out, "key", this->key); dump_field(out, "missing_state", this->missing_state); dump_field(out, "in_progress", this->in_progress); dump_field(out, "has_progress", this->has_progress); dump_field(out, "progress", this->progress); - dump_field(out, "current_version", this->current_version_ref_); - dump_field(out, "latest_version", this->latest_version_ref_); - dump_field(out, "title", this->title_ref_); - dump_field(out, "release_summary", this->release_summary_ref_); - dump_field(out, "release_url", this->release_url_ref_); + dump_field(out, "current_version", this->current_version); + dump_field(out, "latest_version", this->latest_version); + dump_field(out, "title", this->title); + dump_field(out, "release_summary", this->release_summary); + dump_field(out, "release_url", this->release_url); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } -void UpdateCommandRequest::dump_to(std::string &out) const { +const char *UpdateCommandRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "UpdateCommandRequest"); dump_field(out, "key", this->key); dump_field(out, "command", static_cast(this->command)); #ifdef USE_DEVICES dump_field(out, "device_id", this->device_id); #endif + return out.c_str(); } #endif #ifdef USE_ZWAVE_PROXY -void ZWaveProxyFrame::dump_to(std::string &out) const { +const char *ZWaveProxyFrame::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ZWaveProxyFrame"); - out.append(" data: "); - out.append(format_hex_pretty(this->data, this->data_len)); - out.append("\n"); + dump_bytes_field(out, "data", this->data, this->data_len); + return out.c_str(); } -void ZWaveProxyRequest::dump_to(std::string &out) const { +const char *ZWaveProxyRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, "ZWaveProxyRequest"); dump_field(out, "type", static_cast(this->type)); - out.append(" data: "); - out.append(format_hex_pretty(this->data, this->data_len)); - out.append("\n"); + dump_bytes_field(out, "data", this->data, this->data_len); + return out.c_str(); +} +#endif +#ifdef USE_INFRARED +const char *ListEntitiesInfraredResponse::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "ListEntitiesInfraredResponse"); + dump_field(out, "object_id", this->object_id); + dump_field(out, "key", this->key); + dump_field(out, "name", this->name); +#ifdef USE_ENTITY_ICON + dump_field(out, "icon", this->icon); +#endif + dump_field(out, "disabled_by_default", this->disabled_by_default); + dump_field(out, "entity_category", static_cast(this->entity_category)); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "capabilities", this->capabilities); + return out.c_str(); +} +#endif +#ifdef USE_IR_RF +const char *InfraredRFTransmitRawTimingsRequest::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "InfraredRFTransmitRawTimingsRequest"); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "key", this->key); + dump_field(out, "carrier_frequency", this->carrier_frequency); + dump_field(out, "repeat_count", this->repeat_count); + out.append(" timings: "); + out.append("packed buffer ["); + append_uint(out, this->timings_count_); + out.append(" values, "); + append_uint(out, this->timings_length_); + out.append(" bytes]\n"); + return out.c_str(); +} +const char *InfraredRFReceiveEvent::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, "InfraredRFReceiveEvent"); +#ifdef USE_DEVICES + dump_field(out, "device_id", this->device_id); +#endif + dump_field(out, "key", this->key); + for (const auto &it : *this->timings) { + dump_field(out, "timings", it, 4); + } + return out.c_str(); } #endif diff --git a/esphome/components/api/api_pb2_includes.h b/esphome/components/api/api_pb2_includes.h index 55d95304b1..f45e091c6f 100644 --- a/esphome/components/api/api_pb2_includes.h +++ b/esphome/components/api/api_pb2_includes.h @@ -10,6 +10,10 @@ #include "esphome/components/climate/climate_traits.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif + #ifdef USE_LIGHT #include "esphome/components/light/light_traits.h" #endif diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 45f6ecd30e..4b7148e6c0 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -8,8 +8,12 @@ namespace esphome::api { static const char *const TAG = "api.service"; #ifdef HAS_PROTO_MESSAGE_DUMP -void APIServerConnectionBase::log_send_message_(const char *name, const std::string &dump) { - ESP_LOGVV(TAG, "send_message %s: %s", name, dump.c_str()); +void APIServerConnectionBase::log_send_message_(const char *name, const char *dump) { + ESP_LOGVV(TAG, "send_message %s: %s", name, dump); +} +void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) { + DumpBuffer dump_buf; + ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf)); } #endif @@ -19,27 +23,16 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, HelloRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_hello_request"), msg); #endif this->on_hello_request(msg); break; } -#ifdef USE_API_PASSWORD - case AuthenticationRequest::MESSAGE_TYPE: { - AuthenticationRequest msg; - msg.decode(msg_data, msg_size); -#ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str()); -#endif - this->on_authentication_request(msg); - break; - } -#endif case DisconnectRequest::MESSAGE_TYPE: { DisconnectRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_disconnect_request"), msg); #endif this->on_disconnect_request(msg); break; @@ -48,7 +41,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, DisconnectResponse msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_disconnect_response"), msg); #endif this->on_disconnect_response(msg); break; @@ -57,7 +50,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, PingRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_ping_request"), msg); #endif this->on_ping_request(msg); break; @@ -66,7 +59,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, PingResponse msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_ping_response"), msg); #endif this->on_ping_response(msg); break; @@ -75,7 +68,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, DeviceInfoRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_device_info_request"), msg); #endif this->on_device_info_request(msg); break; @@ -84,7 +77,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ListEntitiesRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_list_entities_request"), msg); #endif this->on_list_entities_request(msg); break; @@ -93,7 +86,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SubscribeStatesRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_subscribe_states_request"), msg); #endif this->on_subscribe_states_request(msg); break; @@ -102,7 +95,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SubscribeLogsRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_subscribe_logs_request"), msg); #endif this->on_subscribe_logs_request(msg); break; @@ -112,7 +105,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, CoverCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_cover_command_request"), msg); #endif this->on_cover_command_request(msg); break; @@ -123,7 +116,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, FanCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_fan_command_request"), msg); #endif this->on_fan_command_request(msg); break; @@ -134,7 +127,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, LightCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_light_command_request"), msg); #endif this->on_light_command_request(msg); break; @@ -145,7 +138,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SwitchCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_switch_command_request"), msg); #endif this->on_switch_command_request(msg); break; @@ -156,7 +149,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SubscribeHomeassistantServicesRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"), msg); #endif this->on_subscribe_homeassistant_services_request(msg); break; @@ -166,7 +159,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, GetTimeResponse msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_get_time_response"), msg); #endif this->on_get_time_response(msg); break; @@ -176,7 +169,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SubscribeHomeAssistantStatesRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"), msg); #endif this->on_subscribe_home_assistant_states_request(msg); break; @@ -187,7 +180,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, HomeAssistantStateResponse msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_home_assistant_state_response"), msg); #endif this->on_home_assistant_state_response(msg); break; @@ -198,7 +191,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ExecuteServiceRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_execute_service_request"), msg); #endif this->on_execute_service_request(msg); break; @@ -209,7 +202,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, CameraImageRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_camera_image_request"), msg); #endif this->on_camera_image_request(msg); break; @@ -220,7 +213,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ClimateCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_climate_command_request"), msg); #endif this->on_climate_command_request(msg); break; @@ -231,7 +224,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, NumberCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_number_command_request"), msg); #endif this->on_number_command_request(msg); break; @@ -242,7 +235,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SelectCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_select_command_request"), msg); #endif this->on_select_command_request(msg); break; @@ -253,7 +246,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SirenCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_siren_command_request"), msg); #endif this->on_siren_command_request(msg); break; @@ -264,7 +257,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, LockCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_lock_command_request"), msg); #endif this->on_lock_command_request(msg); break; @@ -275,7 +268,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ButtonCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_button_command_request"), msg); #endif this->on_button_command_request(msg); break; @@ -286,7 +279,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, MediaPlayerCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_media_player_command_request"), msg); #endif this->on_media_player_command_request(msg); break; @@ -297,7 +290,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SubscribeBluetoothLEAdvertisementsRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_le_advertisements_request"), msg); #endif this->on_subscribe_bluetooth_le_advertisements_request(msg); break; @@ -308,7 +301,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothDeviceRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_device_request"), msg); #endif this->on_bluetooth_device_request(msg); break; @@ -319,7 +312,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothGATTGetServicesRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_gatt_get_services_request"), msg); #endif this->on_bluetooth_gatt_get_services_request(msg); break; @@ -330,7 +323,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothGATTReadRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_request"), msg); #endif this->on_bluetooth_gatt_read_request(msg); break; @@ -341,7 +334,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothGATTWriteRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_request"), msg); #endif this->on_bluetooth_gatt_write_request(msg); break; @@ -352,7 +345,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothGATTReadDescriptorRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_descriptor_request"), msg); #endif this->on_bluetooth_gatt_read_descriptor_request(msg); break; @@ -363,7 +356,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothGATTWriteDescriptorRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_descriptor_request"), msg); #endif this->on_bluetooth_gatt_write_descriptor_request(msg); break; @@ -374,7 +367,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothGATTNotifyRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_gatt_notify_request"), msg); #endif this->on_bluetooth_gatt_notify_request(msg); break; @@ -385,7 +378,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SubscribeBluetoothConnectionsFreeRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"), msg); #endif this->on_subscribe_bluetooth_connections_free_request(msg); break; @@ -396,7 +389,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, UnsubscribeBluetoothLEAdvertisementsRequest msg; // Empty message: no decode needed #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"), msg); #endif this->on_unsubscribe_bluetooth_le_advertisements_request(msg); break; @@ -407,7 +400,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, SubscribeVoiceAssistantRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_subscribe_voice_assistant_request"), msg); #endif this->on_subscribe_voice_assistant_request(msg); break; @@ -418,7 +411,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, VoiceAssistantResponse msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_voice_assistant_response"), msg); #endif this->on_voice_assistant_response(msg); break; @@ -429,7 +422,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, VoiceAssistantEventResponse msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_voice_assistant_event_response"), msg); #endif this->on_voice_assistant_event_response(msg); break; @@ -440,7 +433,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, AlarmControlPanelCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_alarm_control_panel_command_request"), msg); #endif this->on_alarm_control_panel_command_request(msg); break; @@ -451,7 +444,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, TextCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_text_command_request"), msg); #endif this->on_text_command_request(msg); break; @@ -462,7 +455,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, DateCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_date_command_request"), msg); #endif this->on_date_command_request(msg); break; @@ -473,7 +466,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, TimeCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_time_command_request"), msg); #endif this->on_time_command_request(msg); break; @@ -484,7 +477,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, VoiceAssistantAudio msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_voice_assistant_audio"), msg); #endif this->on_voice_assistant_audio(msg); break; @@ -495,7 +488,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ValveCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_valve_command_request"), msg); #endif this->on_valve_command_request(msg); break; @@ -506,7 +499,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, DateTimeCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_date_time_command_request"), msg); #endif this->on_date_time_command_request(msg); break; @@ -517,7 +510,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, VoiceAssistantTimerEventResponse msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_voice_assistant_timer_event_response"), msg); #endif this->on_voice_assistant_timer_event_response(msg); break; @@ -528,7 +521,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, UpdateCommandRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_update_command_request"), msg); #endif this->on_update_command_request(msg); break; @@ -539,7 +532,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, VoiceAssistantAnnounceRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_voice_assistant_announce_request"), msg); #endif this->on_voice_assistant_announce_request(msg); break; @@ -550,7 +543,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, VoiceAssistantConfigurationRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_voice_assistant_configuration_request"), msg); #endif this->on_voice_assistant_configuration_request(msg); break; @@ -561,7 +554,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, VoiceAssistantSetConfiguration msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_voice_assistant_set_configuration"), msg); #endif this->on_voice_assistant_set_configuration(msg); break; @@ -572,7 +565,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, NoiseEncryptionSetKeyRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_noise_encryption_set_key_request"), msg); #endif this->on_noise_encryption_set_key_request(msg); break; @@ -583,7 +576,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, BluetoothScannerSetModeRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_bluetooth_scanner_set_mode_request"), msg); #endif this->on_bluetooth_scanner_set_mode_request(msg); break; @@ -594,7 +587,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ZWaveProxyFrame msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_z_wave_proxy_frame"), msg); #endif this->on_z_wave_proxy_frame(msg); break; @@ -605,7 +598,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ZWaveProxyRequest msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_z_wave_proxy_request"), msg); #endif this->on_z_wave_proxy_request(msg); break; @@ -616,11 +609,33 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, HomeassistantActionResponse msg; msg.decode(msg_data, msg_size); #ifdef HAS_PROTO_MESSAGE_DUMP - ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str()); + this->log_receive_message_(LOG_STR("on_homeassistant_action_response"), msg); #endif this->on_homeassistant_action_response(msg); break; } +#endif +#ifdef USE_WATER_HEATER + case WaterHeaterCommandRequest::MESSAGE_TYPE: { + WaterHeaterCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + this->log_receive_message_(LOG_STR("on_water_heater_command_request"), msg); +#endif + this->on_water_heater_command_request(msg); + break; + } +#endif +#ifdef USE_IR_RF + case InfraredRFTransmitRawTimingsRequest::MESSAGE_TYPE: { + InfraredRFTransmitRawTimingsRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + this->log_receive_message_(LOG_STR("on_infrared_rf_transmit_raw_timings_request"), msg); +#endif + this->on_infrared_rf_transmit_raw_timings_request(msg); + break; + } #endif default: break; @@ -632,13 +647,6 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) { this->on_fatal_error(); } } -#ifdef USE_API_PASSWORD -void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) { - if (!this->send_authenticate_response(msg)) { - this->on_fatal_error(); - } -} -#endif void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) { if (!this->send_disconnect_response(msg)) { this->on_fatal_error(); @@ -826,14 +834,16 @@ void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { th #ifdef USE_ZWAVE_PROXY void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); } #endif +#ifdef USE_IR_RF +void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { + this->infrared_rf_transmit_raw_timings(msg); +} +#endif void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements for messages switch (msg_type) { - case HelloRequest::MESSAGE_TYPE: // No setup required -#ifdef USE_API_PASSWORD - case AuthenticationRequest::MESSAGE_TYPE: // No setup required -#endif + case HelloRequest::MESSAGE_TYPE: // No setup required case DisconnectRequest::MESSAGE_TYPE: // No setup required case PingRequest::MESSAGE_TYPE: // No setup required break; // Skip all checks for these messages diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 6d94046a23..200991c282 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -12,24 +12,22 @@ class APIServerConnectionBase : public ProtoService { public: #ifdef HAS_PROTO_MESSAGE_DUMP protected: - void log_send_message_(const char *name, const std::string &dump); + void log_send_message_(const char *name, const char *dump); + void log_receive_message_(const LogString *name, const ProtoMessage &msg); public: #endif bool send_message(const ProtoMessage &msg, uint8_t message_type) { #ifdef HAS_PROTO_MESSAGE_DUMP - this->log_send_message_(msg.message_name(), msg.dump()); + DumpBuffer dump_buf; + this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf)); #endif return this->send_message_(msg, message_type); } virtual void on_hello_request(const HelloRequest &value){}; -#ifdef USE_API_PASSWORD - virtual void on_authentication_request(const AuthenticationRequest &value){}; -#endif - virtual void on_disconnect_request(const DisconnectRequest &value){}; virtual void on_disconnect_response(const DisconnectResponse &value){}; virtual void on_ping_request(const PingRequest &value){}; @@ -91,6 +89,10 @@ class APIServerConnectionBase : public ProtoService { virtual void on_climate_command_request(const ClimateCommandRequest &value){}; #endif +#ifdef USE_WATER_HEATER + virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){}; +#endif + #ifdef USE_NUMBER virtual void on_number_command_request(const NumberCommandRequest &value){}; #endif @@ -217,6 +219,11 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_ZWAVE_PROXY virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; #endif + +#ifdef USE_IR_RF + virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){}; +#endif + protected: void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; @@ -224,9 +231,6 @@ class APIServerConnectionBase : public ProtoService { class APIServerConnection : public APIServerConnectionBase { public: virtual bool send_hello_response(const HelloRequest &msg) = 0; -#ifdef USE_API_PASSWORD - virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0; -#endif virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0; virtual bool send_ping_response(const PingRequest &msg) = 0; virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0; @@ -350,12 +354,12 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_ZWAVE_PROXY virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0; +#endif +#ifdef USE_IR_RF + virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; -#ifdef USE_API_PASSWORD - void on_authentication_request(const AuthenticationRequest &msg) override; -#endif void on_disconnect_request(const DisconnectRequest &msg) override; void on_ping_request(const PingRequest &msg) override; void on_device_info_request(const DeviceInfoRequest &msg) override; @@ -479,6 +483,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_ZWAVE_PROXY void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; +#endif +#ifdef USE_IR_RF + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; #endif void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index b1a5ee5d57..a4eeb4dd5e 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -125,15 +125,18 @@ void APIServer::loop() { if (!sock) break; + char peername[socket::SOCKADDR_STR_LEN]; + sock->getpeername_to(peername); + // Check if we're at the connection limit if (this->clients_.size() >= this->max_connections_) { - ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str()); + ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername); // Immediately close - socket destructor will handle cleanup sock.reset(); continue; } - ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str()); + ESP_LOGD(TAG, "Accept %s", peername); auto *conn = new APIConnection(std::move(sock), this); this->clients_.emplace_back(conn); @@ -166,8 +169,7 @@ void APIServer::loop() { // Network is down - disconnect all clients for (auto &client : this->clients_) { client->on_fatal_error(); - ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(), - client->client_info_.peername.c_str()); + client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect")); } // Continue to process and clean up the clients below } @@ -184,13 +186,16 @@ void APIServer::loop() { } // Rare case: handle disconnection -#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername); -#endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES this->unregister_active_action_calls_for_connection(client.get()); #endif - ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str()); + ESP_LOGV(TAG, "Remove connection %s", client->get_name()); + +#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER + // Save client info before removal for the trigger + std::string client_name(client->get_name()); + std::string client_peername(client->get_peername()); +#endif // Swap with the last element and pop (avoids expensive vector shifts) if (client_index < this->clients_.size() - 1) { @@ -203,6 +208,11 @@ void APIServer::loop() { this->status_set_warning(); this->last_connected_ = App.get_loop_component_start_time(); } + +#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER + // Fire trigger after client is removed so api.connected reflects the true state + this->client_disconnected_trigger_->trigger(client_name, client_peername); +#endif // Don't increment client_index since we need to process the swapped element } } @@ -224,38 +234,6 @@ void APIServer::dump_config() { #endif } -#ifdef USE_API_PASSWORD -bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const { - // depend only on input password length - const char *a = this->password_.c_str(); - uint32_t len_a = this->password_.length(); - const char *b = reinterpret_cast(password_data); - uint32_t len_b = password_len; - - // disable optimization with volatile - volatile uint32_t length = len_b; - volatile const char *left = nullptr; - volatile const char *right = b; - uint8_t result = 0; - - if (len_a == length) { - left = *((volatile const char **) &a); - result = 0; - } - if (len_a != length) { - left = b; - result = 1; - } - - for (size_t i = 0; i < length; i++) { - result |= *left++ ^ *right++; // NOLINT - } - - return result == 0; -} - -#endif - void APIServer::handle_disconnect(APIConnection *conn) {} // Macro for controller update dispatch @@ -335,14 +313,16 @@ API_DISPATCH_UPDATE(valve::Valve, valve) API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player) #endif +#ifdef USE_WATER_HEATER +API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater) +#endif + #ifdef USE_EVENT -// Event is a special case - unlike other entities with simple state fields, -// events store their state in a member accessed via obj->get_last_event_type() void APIServer::on_event(event::Event *obj) { if (obj->is_internal()) return; for (auto &c : this->clients_) - c->send_event(obj, obj->get_last_event_type()); + c->send_event(obj); } #endif @@ -365,6 +345,21 @@ void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) { } #endif +#ifdef USE_IR_RF +void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_id, uint32_t key, + const std::vector *timings) { + InfraredRFReceiveEvent resp{}; +#ifdef USE_DEVICES + resp.device_id = device_id; +#endif + resp.key = key; + resp.timings = timings; + + for (auto &c : this->clients_) + c->send_infrared_rf_receive_event(resp); +} +#endif + #ifdef USE_ALARM_CONTROL_PANEL API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel) #endif @@ -373,10 +368,6 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; void APIServer::set_port(uint16_t port) { this->port_ = port; } -#ifdef USE_API_PASSWORD -void APIServer::set_password(const std::string &password) { this->password_ = password; } -#endif - void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; } #ifdef USE_API_HOMEASSISTANT_SERVICES @@ -390,7 +381,7 @@ void APIServer::register_action_response_callback(uint32_t call_id, ActionRespon this->action_response_callbacks_.push_back({call_id, std::move(callback)}); } -void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) { +void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message) { for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) { if (it->call_id == call_id) { auto callback = std::move(it->callback); @@ -402,7 +393,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std } } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON -void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message, +void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len) { for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) { if (it->call_id == call_id) { @@ -420,8 +411,8 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std #ifdef USE_API_HOMEASSISTANT_STATES // Helper to add subscription (reduces duplication) -void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, - std::function f, bool once) { +void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function f, + bool once) { this->state_subs_.push_back(HomeAssistantStateSubscription{ .entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once, // entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation) @@ -430,7 +421,7 @@ void APIServer::add_state_subscription_(const char *entity_id, const char *attri // Helper to add subscription with heap-allocated strings (reduces duplication) void APIServer::add_state_subscription_(std::string entity_id, optional attribute, - std::function f, bool once) { + std::function f, bool once) { HomeAssistantStateSubscription sub; // Allocate heap storage for the strings sub.entity_id_dynamic_storage = std::make_unique(std::move(entity_id)); @@ -450,23 +441,43 @@ void APIServer::add_state_subscription_(std::string entity_id, optional f) { + std::function f) { this->add_state_subscription_(entity_id, attribute, std::move(f), false); } void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute, - std::function f) { + std::function f) { this->add_state_subscription_(entity_id, attribute, std::move(f), true); } -// Existing std::string overload (for custom_api_device.h - heap allocation) +// std::string overload with StringRef callback (zero-allocation callback) void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, - std::function f) { + std::function f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false); } void APIServer::get_home_assistant_state(std::string entity_id, optional attribute, - std::function f) { + std::function f) { + this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true); +} + +// Legacy helper: wraps std::string callback and delegates to StringRef version +void APIServer::add_state_subscription_(std::string entity_id, optional attribute, + std::function f, bool once) { + // Wrap callback to convert StringRef -> std::string, then delegate + this->add_state_subscription_(std::move(entity_id), std::move(attribute), + std::function([f = std::move(f)](StringRef state) { f(state.str()); }), + once); +} + +// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string) +void APIServer::subscribe_home_assistant_state(std::string entity_id, optional attribute, + std::function f) { + this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false); +} + +void APIServer::get_home_assistant_state(std::string entity_id, optional attribute, + std::function f) { this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true); } @@ -602,8 +613,7 @@ void APIServer::on_shutdown() { if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) { // If we can't send the disconnect request directly (tx_buffer full), // schedule it at the front of the batch so it will be sent with priority - c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE, - DisconnectRequest::ESTIMATED_SIZE); + c->schedule_message_front_(nullptr, DisconnectRequest::MESSAGE_TYPE, DisconnectRequest::ESTIMATED_SIZE); } } } @@ -635,18 +645,18 @@ uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConn this->active_action_calls_.push_back({action_call_id, client_call_id, conn}); // Schedule automatic cleanup after timeout (client will have given up by then) - this->set_timeout(str_sprintf("action_call_%u", action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS, - [this, action_call_id]() { - ESP_LOGD(TAG, "Action call %u timed out", action_call_id); - this->unregister_active_action_call(action_call_id); - }); + // Uses numeric ID overload to avoid heap allocation from str_sprintf + this->set_timeout(action_call_id, USE_API_ACTION_CALL_TIMEOUT_MS, [this, action_call_id]() { + ESP_LOGD(TAG, "Action call %u timed out", action_call_id); + this->unregister_active_action_call(action_call_id); + }); return action_call_id; } void APIServer::unregister_active_action_call(uint32_t action_call_id) { - // Cancel the timeout for this action call - this->cancel_timeout(str_sprintf("action_call_%u", action_call_id)); + // Cancel the timeout for this action call (uses numeric ID overload) + this->cancel_timeout(action_call_id); // Swap-and-pop is more efficient than remove_if for unordered vectors for (size_t i = 0; i < this->active_action_calls_.size(); i++) { @@ -662,8 +672,8 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con // Remove all active action calls for disconnected connection using swap-and-pop for (size_t i = 0; i < this->active_action_calls_.size();) { if (this->active_action_calls_[i].connection == conn) { - // Cancel the timeout for this action call - this->cancel_timeout(str_sprintf("action_call_%u", this->active_action_calls_[i].action_call_id)); + // Cancel the timeout for this action call (uses numeric ID overload) + this->cancel_timeout(this->active_action_calls_[i].action_call_id); std::swap(this->active_action_calls_[i], this->active_action_calls_.back()); this->active_action_calls_.pop_back(); @@ -674,7 +684,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con } } -void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) { +void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) { for (auto &call : this->active_action_calls_) { if (call.action_call_id == action_call_id) { call.connection->send_execute_service_response(call.client_call_id, success, error_message); @@ -684,7 +694,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id); } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON -void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, +void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len) { for (auto &call : this->active_action_calls_) { if (call.action_call_id == action_call_id) { diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index ad7d8bf63d..93421ef801 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -10,6 +10,7 @@ #include "esphome/core/component.h" #include "esphome/core/controller.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" #include "list_entities.h" #include "subscribe_state.h" #ifdef USE_LOGGER @@ -59,10 +60,6 @@ class APIServer : public Component, #endif #ifdef USE_CAMERA void on_camera_image(const std::shared_ptr &image) override; -#endif -#ifdef USE_API_PASSWORD - bool check_password(const uint8_t *password_data, size_t password_len) const; - void set_password(const std::string &password); #endif void set_port(uint16_t port); void set_reboot_timeout(uint32_t reboot_timeout); @@ -133,6 +130,9 @@ class APIServer : public Component, #ifdef USE_MEDIA_PLAYER void on_media_player_update(media_player::MediaPlayer *obj) override; #endif +#ifdef USE_WATER_HEATER + void on_water_heater_update(water_heater::WaterHeater *obj) override; +#endif #ifdef USE_API_HOMEASSISTANT_SERVICES void send_homeassistant_action(const HomeassistantActionRequest &call); @@ -140,10 +140,10 @@ class APIServer : public Component, // Action response handling using ActionResponseCallback = std::function; void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback); - void handle_action_response(uint32_t call_id, bool success, const std::string &error_message); + void handle_action_response(uint32_t call_id, bool success, StringRef error_message); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - void handle_action_response(uint32_t call_id, bool success, const std::string &error_message, - const uint8_t *response_data, size_t response_data_len); + void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data, + size_t response_data_len); #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES @@ -162,9 +162,9 @@ class APIServer : public Component, void unregister_active_action_call(uint32_t action_call_id); void unregister_active_action_calls_for_connection(APIConnection *conn); // Send response for a specific action call (uses action_call_id, sends client_call_id in response) - void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message); + void send_action_response(uint32_t action_call_id, bool success, StringRef error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message, + void send_action_response(uint32_t action_call_id, bool success, StringRef error_message, const uint8_t *response_data, size_t response_data_len); #endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #endif // USE_API_USER_DEFINED_ACTION_RESPONSES @@ -185,6 +185,9 @@ class APIServer : public Component, #ifdef USE_ZWAVE_PROXY void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg); #endif +#ifdef USE_IR_RF + void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector *timings); +#endif bool is_connected(bool state_subscription_only = false) const; @@ -192,7 +195,7 @@ class APIServer : public Component, struct HomeAssistantStateSubscription { const char *entity_id; // Pointer to flash (internal) or heap (external) const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute) - std::function callback; + std::function callback; bool once; // Dynamic storage for external components using std::string API (custom_api_device.h) @@ -202,14 +205,20 @@ class APIServer : public Component, }; // New const char* overload (for internal components - zero allocation) - void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function f); - void get_home_assistant_state(const char *entity_id, const char *attribute, std::function f); + void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function f); + void get_home_assistant_state(const char *entity_id, const char *attribute, std::function f); - // Existing std::string overload (for custom_api_device.h - heap allocation) + // std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback) void subscribe_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function f); void get_home_assistant_state(std::string entity_id, optional attribute, - std::function f); + std::function f); + + // Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback) + void subscribe_home_assistant_state(std::string entity_id, optional attribute, + std::function f); + void get_home_assistant_state(std::string entity_id, optional attribute, + std::function f); const std::vector &get_state_subs() const; #endif @@ -233,10 +242,13 @@ class APIServer : public Component, #endif // USE_API_NOISE #ifdef USE_API_HOMEASSISTANT_STATES // Helper methods to reduce code duplication - void add_state_subscription_(const char *entity_id, const char *attribute, std::function f, + void add_state_subscription_(const char *entity_id, const char *attribute, std::function f, bool once); + void add_state_subscription_(std::string entity_id, optional attribute, std::function f, + bool once); + // Legacy helper: wraps std::string callback and delegates to StringRef version void add_state_subscription_(std::string entity_id, optional attribute, - std::function f, bool once); + std::function f, bool once); #endif // USE_API_HOMEASSISTANT_STATES // Pointers and pointer-like types first (4 bytes each) std::unique_ptr socket_ = nullptr; @@ -253,9 +265,6 @@ class APIServer : public Component, // Vectors and strings (12 bytes each on 32-bit) std::vector> clients_; -#ifdef USE_API_PASSWORD - std::string password_; -#endif std::vector shared_write_buffer_; // Shared proto write buffer for all connections #ifdef USE_API_HOMEASSISTANT_STATES std::vector state_subs_; diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index ca1fc089fa..200d0938bd 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -16,7 +16,7 @@ with warnings.catch_warnings(): import contextlib -from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ +from esphome.const import CONF_KEY, CONF_PORT, __version__ from esphome.core import CORE from . import CONF_ENCRYPTION @@ -35,7 +35,6 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: conf = config["api"] name = config["esphome"]["name"] port: int = int(conf[CONF_PORT]) - password: str = conf[CONF_PASSWORD] noise_psk: str | None = None if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)): noise_psk = key @@ -50,7 +49,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: cli = APIClient( addresses[0], # Primary address for compatibility port, - password, + "", # Password auth removed in 2026.1.0 client_info=f"ESPHome Logs {__version__}", noise_psk=noise_psk, addresses=addresses, # Pass all addresses for automatic retry diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 5e9165326d..2fd9cb0dd2 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -122,21 +122,36 @@ class CustomAPIDevice { * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature"); * } * - * void on_state_changed(std::string state) { - * // State of sensor.weather_forecast is `state` + * void on_state_changed(StringRef state) { + * // State of climate.kitchen current_temperature is `state` + * // Use state.c_str() for C string, state.str() for std::string * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. - * @param callback The member function to call when the entity state changes. + * @param callback The member function to call when the entity state changes (zero-allocation). * @param entity_id The entity_id to track. * @param attribute The entity state attribute to track. */ template + void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id, + const std::string &attribute = "") { + auto f = std::bind(callback, (T *) this, std::placeholders::_1); + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); + } + + /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). + * + * @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0. + */ + template + ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, std::placeholders::_1); - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), f); + // Explicit type to disambiguate overload resolution + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), + std::function(f)); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant. @@ -148,23 +163,45 @@ class CustomAPIDevice { * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); * } * - * void on_state_changed(std::string entity_id, std::string state) { + * void on_state_changed(const std::string &entity_id, StringRef state) { * // State of `entity_id` is `state` * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. - * @param callback The member function to call when the entity state changes. + * @param callback The member function to call when the entity state changes (zero-allocation for state). * @param entity_id The entity_id to track. * @param attribute The entity state attribute to track. */ template + void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id, + const std::string &attribute = "") { + auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); + } + + /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). + * + * @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0. + */ + template + ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), f); + // Explicit type to disambiguate overload resolution + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), + std::function(f)); } #else + template + void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id, + const std::string &attribute = "") { + static_assert(sizeof(T) == 0, + "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " + "of your YAML configuration"); + } + template void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, const std::string &attribute = "") { @@ -173,6 +210,14 @@ class CustomAPIDevice { "of your YAML configuration"); } + template + void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id, + const std::string &attribute = "") { + static_assert(sizeof(T) == 0, + "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " + "of your YAML configuration"); + } + template void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, const std::string &attribute = "") { @@ -195,7 +240,7 @@ class CustomAPIDevice { */ void call_homeassistant_service(const std::string &service_name) { HomeassistantActionRequest resp; - resp.set_service(StringRef(service_name)); + resp.service = StringRef(service_name); global_api_server->send_homeassistant_action(resp); } @@ -215,12 +260,12 @@ class CustomAPIDevice { */ void call_homeassistant_service(const std::string &service_name, const std::map &data) { HomeassistantActionRequest resp; - resp.set_service(StringRef(service_name)); + resp.service = StringRef(service_name); resp.data.init(data.size()); for (auto &it : data) { auto &kv = resp.data.emplace_back(); - kv.set_key(StringRef(it.first)); - kv.value = it.second; + kv.key = StringRef(it.first); + kv.value = StringRef(it.second); // data map lives until send completes } global_api_server->send_homeassistant_action(resp); } @@ -237,7 +282,7 @@ class CustomAPIDevice { */ void fire_homeassistant_event(const std::string &event_name) { HomeassistantActionRequest resp; - resp.set_service(StringRef(event_name)); + resp.service = StringRef(event_name); resp.is_event = true; global_api_server->send_homeassistant_action(resp); } @@ -257,13 +302,13 @@ class CustomAPIDevice { */ void fire_homeassistant_event(const std::string &service_name, const std::map &data) { HomeassistantActionRequest resp; - resp.set_service(StringRef(service_name)); + resp.service = StringRef(service_name); resp.is_event = true; resp.data.init(data.size()); for (auto &it : data) { auto &kv = resp.data.emplace_back(); - kv.set_key(StringRef(it.first)); - kv.value = it.second; + kv.key = StringRef(it.first); + kv.value = StringRef(it.second); // data map lives until send completes } global_api_server->send_homeassistant_action(resp); } diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 2da6e15362..9bffe18764 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -67,10 +67,10 @@ template class TemplatableKeyValuePair { // the callback is invoked synchronously while the message is on the stack). class ActionResponse { public: - ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {} + ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {} #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len) + ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len) : success_(success), error_message_(error_message) { if (data == nullptr || data_len == 0) return; @@ -147,13 +147,23 @@ template class HomeAssistantServiceCallAction : public Actionservice_.value(x...); - resp.set_service(StringRef(service_value)); + resp.service = StringRef(service_value); resp.is_event = this->flags_.is_event; - this->populate_service_map(resp.data, this->data_, x...); - this->populate_service_map(resp.data_template, this->data_template_, x...); - this->populate_service_map(resp.variables, this->variables_, x...); + + // Local storage for lambda-evaluated strings - lives until after send + FixedVector data_storage; + FixedVector data_template_storage; + FixedVector variables_storage; + + this->populate_service_map(resp.data, this->data_, data_storage, x...); + this->populate_service_map(resp.data_template, this->data_template_, data_template_storage, x...); + this->populate_service_map(resp.variables, this->variables_, variables_storage, x...); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON + // IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns. + std::string response_template_value; +#endif if (this->flags_.wants_status) { // Generate a unique call ID for this service call static uint32_t call_id_counter = 1; @@ -164,8 +174,8 @@ template class HomeAssistantServiceCallAction : public Actionflags_.has_response_template) { - std::string response_template_value = this->response_template_.value(x...); - resp.response_template = response_template_value; + response_template_value = this->response_template_.value(x...); + resp.response_template = StringRef(response_template_value); } } #endif @@ -205,12 +215,31 @@ template class HomeAssistantServiceCallAction : public Action - static void populate_service_map(VectorType &dest, SourceType &source, Ts... x) { + static void populate_service_map(VectorType &dest, SourceType &source, FixedVector &value_storage, + Ts... x) { dest.init(source.size()); + + // Count non-static strings to allocate exact storage needed + size_t lambda_count = 0; + for (const auto &it : source) { + if (!it.value.is_static_string()) { + lambda_count++; + } + } + value_storage.init(lambda_count); + for (auto &it : source) { auto &kv = dest.emplace_back(); - kv.set_key(StringRef(it.key)); - kv.value = it.value.value(x...); + kv.key = StringRef(it.key); + + if (it.value.is_static_string()) { + // Static string from YAML - zero allocation + kv.value = StringRef(it.value.get_static_string()); + } else { + // Lambda evaluation - store result, reference it + value_storage.push_back(it.value.value(x...)); + kv.value = StringRef(value_storage.back()); + } } } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index b4d1454153..fe43a47c3b 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -73,6 +73,12 @@ LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMedia LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel, ListEntitiesAlarmControlPanelResponse) #endif +#ifdef USE_WATER_HEATER +LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse) +#endif +#ifdef USE_INFRARED +LIST_ENTITIES_HANDLER(infrared, infrared::Infrared, ListEntitiesInfraredResponse) +#endif #ifdef USE_EVENT LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse) #endif diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 4c90dbbad8..bef36dd015 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -9,11 +9,10 @@ namespace esphome::api { class APIConnection; // Macro for generating ListEntitiesIterator handlers -// Calls schedule_message_ with try_send_*_info +// Calls schedule_message_ which dispatches to try_send_*_info #define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \ bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ - return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \ - ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \ + return this->client_->schedule_message_(entity, ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \ } class ListEntitiesIterator : public ComponentIterator { @@ -82,6 +81,12 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; #endif +#ifdef USE_WATER_HEATER + bool on_water_heater(water_heater::WaterHeater *entity) override; +#endif +#ifdef USE_INFRARED + bool on_infrared(infrared::Infrared *entity) override; +#endif #ifdef USE_EVENT bool on_event(event::Event *entity) override; #endif diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 4f0d0846d7..eac26997cf 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -139,12 +139,4 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) { } } -#ifdef HAS_PROTO_MESSAGE_DUMP -std::string ProtoMessage::dump() const { - std::string out; - this->dump_to(out); - return out; -} -#endif - } // namespace esphome::api diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 83b6922be1..2e0df297c3 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -39,6 +39,24 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) { return (value & 1) ? static_cast(~(value >> 1)) : static_cast(value >> 1); } +/// Count number of varints in a packed buffer +inline uint16_t count_packed_varints(const uint8_t *data, size_t len) { + uint16_t count = 0; + while (len > 0) { + // Skip varint bytes until we find one without continuation bit + while (len > 0 && (*data & 0x80)) { + data++; + len--; + } + if (len > 0) { + data++; + len--; + count++; + } + } + return count; +} + /* * StringRef Ownership Model for API Protocol Messages * =================================================== @@ -54,16 +72,16 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) { * 3. Global/static strings: StringRef(GLOBAL_CONSTANT) - Always safe * 4. Local variables: Safe ONLY if encoding happens before function returns: * std::string temp = compute_value(); - * msg.set_field(StringRef(temp)); + * msg.field = StringRef(temp); * return this->send_message(msg); // temp is valid during encoding * * Unsafe Patterns (WILL cause crashes/corruption): - * 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value - * 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary + * 1. Temporaries: msg.field = StringRef(obj.get_string()) // get_string() returns by value + * 2. Concatenation: msg.field = StringRef(str1 + str2) // Result is temporary * * For unsafe patterns, store in a local variable first: * std::string temp = get_string(); // or str1 + str2 - * msg.set_field(StringRef(temp)); + * msg.field = StringRef(temp); * * The send_*_response pattern ensures proper lifetime management by encoding * within the same function scope where temporaries are created. @@ -180,9 +198,10 @@ class ProtoVarInt { uint64_t value_; }; -// Forward declaration for decode_to_message and encode_to_writer -class ProtoMessage; +// Forward declarations for decode_to_message, encode_message and encode_packed_sint32 class ProtoDecodableMessage; +class ProtoMessage; +class ProtoSize; class ProtoLengthDelimited { public: @@ -334,15 +353,71 @@ class ProtoWriteBuffer { void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { this->encode_uint64(field_id, encode_zigzag64(value), force); } - void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false); + /// Encode a packed repeated sint32 field (zero-copy from vector) + void encode_packed_sint32(uint32_t field_id, const std::vector &values); + void encode_message(uint32_t field_id, const ProtoMessage &value); std::vector *get_buffer() const { return buffer_; } protected: std::vector *buffer_; }; -// Forward declaration -class ProtoSize; +#ifdef HAS_PROTO_MESSAGE_DUMP +/** + * Fixed-size buffer for message dumps - avoids heap allocation. + * Sized to match the logger's default tx_buffer_size (512 bytes) + * since anything larger gets truncated anyway. + */ +class DumpBuffer { + public: + // Matches default tx_buffer_size in logger component + static constexpr size_t CAPACITY = 512; + + DumpBuffer() : pos_(0) { buf_[0] = '\0'; } + + DumpBuffer &append(const char *str) { + if (str) { + append_impl_(str, strlen(str)); + } + return *this; + } + + DumpBuffer &append(const char *str, size_t len) { + append_impl_(str, len); + return *this; + } + + DumpBuffer &append(size_t n, char c) { + size_t space = CAPACITY - 1 - pos_; + if (n > space) + n = space; + if (n > 0) { + memset(buf_ + pos_, c, n); + pos_ += n; + buf_[pos_] = '\0'; + } + return *this; + } + + const char *c_str() const { return buf_; } + size_t size() const { return pos_; } + + private: + void append_impl_(const char *str, size_t len) { + size_t space = CAPACITY - 1 - pos_; + if (len > space) + len = space; + if (len > 0) { + memcpy(buf_ + pos_, str, len); + pos_ += len; + buf_[pos_] = '\0'; + } + } + + char buf_[CAPACITY]; + size_t pos_; +}; +#endif class ProtoMessage { public: @@ -352,8 +427,7 @@ class ProtoMessage { // Default implementation for messages with no fields virtual void calculate_size(ProtoSize &size) const {} #ifdef HAS_PROTO_MESSAGE_DUMP - std::string dump() const; - virtual void dump_to(std::string &out) const = 0; + virtual const char *dump_to(DumpBuffer &out) const = 0; virtual const char *message_name() const { return "unknown"; } #endif }; @@ -792,10 +866,45 @@ class ProtoSize { } } } + + /** + * @brief Calculate size of a packed repeated sint32 field + */ + inline void add_packed_sint32(uint32_t field_id_size, const std::vector &values) { + if (values.empty()) + return; + + size_t packed_size = 0; + for (int value : values) { + packed_size += varint(encode_zigzag32(value)); + } + + // field_id + length varint + packed data + total_size_ += field_id_size + varint(static_cast(packed_size)) + static_cast(packed_size); + } }; +// Implementation of encode_packed_sint32 - must be after ProtoSize is defined +inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std::vector &values) { + if (values.empty()) + return; + + // Calculate packed size + size_t packed_size = 0; + for (int value : values) { + packed_size += ProtoSize::varint(encode_zigzag32(value)); + } + + // Write tag (LENGTH_DELIMITED) + length + all zigzag-encoded values + this->encode_field_raw(field_id, WIRE_TYPE_LENGTH_DELIMITED); + this->encode_varint_raw(packed_size); + for (int value : values) { + this->encode_varint_raw(encode_zigzag32(value)); + } +} + // Implementation of encode_message - must be after ProtoMessage is defined -inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) { +inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) { this->encode_field_raw(field_id, 2); // type 2: Length-delimited message // Calculate the message size first @@ -833,9 +942,6 @@ class ProtoService { virtual bool is_authenticated() = 0; virtual bool is_connection_setup() = 0; virtual void on_fatal_error() = 0; -#ifdef USE_API_PASSWORD - virtual void on_unauthenticated_access() = 0; -#endif virtual void on_no_setup_connection() = 0; /** * Create a buffer with a reserved size. @@ -873,20 +979,7 @@ class ProtoService { return true; } - inline bool check_authenticated_() { -#ifdef USE_API_PASSWORD - if (!this->check_connection_setup_()) { - return false; - } - if (!this->is_authenticated()) { - this->on_unauthenticated_access(); - return false; - } - return true; -#else - return this->check_connection_setup_(); -#endif - } + inline bool check_authenticated_() { return this->check_connection_setup_(); } }; } // namespace esphome::api diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 3a563f2221..4bbc17018e 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -60,6 +60,9 @@ INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer) #ifdef USE_ALARM_CONTROL_PANEL INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel) #endif +#ifdef USE_WATER_HEATER +INITIAL_STATE_HANDLER(water_heater, water_heater::WaterHeater) +#endif #ifdef USE_UPDATE INITIAL_STATE_HANDLER(update, update::UpdateEntity) #endif diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 2c22c322ec..3c9f33835a 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -76,6 +76,12 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; #endif +#ifdef USE_WATER_HEATER + bool on_water_heater(water_heater::WaterHeater *entity) override; +#endif +#ifdef USE_INFRARED + bool on_infrared(infrared::Infrared *infrared) override { return true; }; +#endif #ifdef USE_EVENT bool on_event(event::Event *event) override { return true; }; #endif diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 001add626f..85fba2a435 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -46,7 +46,7 @@ template class UserServiceBase : public UserServiceDescriptor { ListEntitiesServicesResponse encode_list_service_response() override { ListEntitiesServicesResponse msg; - msg.set_name(StringRef(this->name_)); + msg.name = StringRef(this->name_); msg.key = this->key_; msg.supports_response = this->supports_response_; std::array arg_types = {to_service_arg_type()...}; @@ -54,7 +54,7 @@ template class UserServiceBase : public UserServiceDescriptor { for (size_t i = 0; i < sizeof...(Ts); i++) { auto &arg = msg.args.emplace_back(); arg.type = arg_types[i]; - arg.set_name(StringRef(this->arg_names_[i])); + arg.name = StringRef(this->arg_names_[i]); } return msg; } @@ -108,7 +108,7 @@ template class UserServiceDynamic : public UserServiceDescriptor ListEntitiesServicesResponse encode_list_service_response() override { ListEntitiesServicesResponse msg; - msg.set_name(StringRef(this->name_)); + msg.name = StringRef(this->name_); msg.key = this->key_; msg.supports_response = enums::SUPPORTS_RESPONSE_NONE; // Dynamic services don't support responses yet std::array arg_types = {to_service_arg_type()...}; @@ -116,7 +116,7 @@ template class UserServiceDynamic : public UserServiceDescriptor for (size_t i = 0; i < sizeof...(Ts); i++) { auto &arg = msg.args.emplace_back(); arg.type = arg_types[i]; - arg.set_name(StringRef(this->arg_names_[i])); + arg.name = StringRef(this->arg_names_[i]); } return msg; } @@ -255,7 +255,7 @@ template class APIRespondAction : public Action { bool return_response = std::get<1>(args); if (!return_response) { // Client doesn't want response data, just send success/error - this->parent_->send_action_response(call_id, success, error_message); + this->parent_->send_action_response(call_id, success, StringRef(error_message)); return; } } @@ -265,12 +265,12 @@ template class APIRespondAction : public Action { json::JsonBuilder builder; this->json_builder_(x..., builder.root()); std::string json_str = builder.serialize(); - this->parent_->send_action_response(call_id, success, error_message, + this->parent_->send_action_response(call_id, success, StringRef(error_message), reinterpret_cast(json_str.data()), json_str.size()); return; } #endif - this->parent_->send_action_response(call_id, success, error_message); + this->parent_->send_action_response(call_id, success, StringRef(error_message)); } protected: diff --git a/esphome/components/aqi/__init__.py b/esphome/components/aqi/__init__.py new file mode 100644 index 0000000000..4b979ab406 --- /dev/null +++ b/esphome/components/aqi/__init__.py @@ -0,0 +1,14 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@jasstrong", "@ximex", "@freekode"] + +aqi_ns = cg.esphome_ns.namespace("aqi") +AQICalculatorType = aqi_ns.enum("AQICalculatorType") + +CONF_AQI = "aqi" +CONF_CALCULATION_TYPE = "calculation_type" + +AQI_CALCULATION_TYPE = { + "CAQI": AQICalculatorType.CAQI_TYPE, + "AQI": AQICalculatorType.AQI_TYPE, +} diff --git a/esphome/components/aqi/abstract_aqi_calculator.h b/esphome/components/aqi/abstract_aqi_calculator.h new file mode 100644 index 0000000000..299962fa17 --- /dev/null +++ b/esphome/components/aqi/abstract_aqi_calculator.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace esphome::aqi { + +class AbstractAQICalculator { + public: + virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0; +}; + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/aqi_calculator.h b/esphome/components/aqi/aqi_calculator.h new file mode 100644 index 0000000000..993504c1e9 --- /dev/null +++ b/esphome/components/aqi/aqi_calculator.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include "abstract_aqi_calculator.h" + +// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf + +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); + + return static_cast(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index)); + } + + protected: + static constexpr int NUM_LEVELS = 6; + + static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; + + static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {{0.0f, 9.0f}, {9.1f, 35.4f}, + {35.5f, 55.4f}, {55.5f, 125.4f}, + {125.5f, 225.4f}, {225.5f, std::numeric_limits::max()}}; + + static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {{0.0f, 54.0f}, {55.0f, 154.0f}, + {155.0f, 254.0f}, {255.0f, 354.0f}, + {355.0f, 424.0f}, {425.0f, std::numeric_limits::max()}}; + + static float calculate_index(float value, const float array[NUM_LEVELS][2]) { + int grid_index = get_grid_index(value, array); + if (grid_index == -1) { + return -1.0f; + } + float aqi_lo = INDEX_GRID[grid_index][0]; + float aqi_hi = INDEX_GRID[grid_index][1]; + 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; + } + + static int get_grid_index(float value, const float array[NUM_LEVELS][2]) { + for (int i = 0; i < NUM_LEVELS; i++) { + if (value >= array[i][0] && value <= array[i][1]) { + return i; + } + } + return -1; + } +}; + +} // namespace esphome::aqi diff --git a/esphome/components/hm3301/aqi_calculator_factory.h b/esphome/components/aqi/aqi_calculator_factory.h similarity index 55% rename from esphome/components/hm3301/aqi_calculator_factory.h rename to esphome/components/aqi/aqi_calculator_factory.h index 55608b6e51..db7eaab1bb 100644 --- a/esphome/components/hm3301/aqi_calculator_factory.h +++ b/esphome/components/aqi/aqi_calculator_factory.h @@ -3,8 +3,7 @@ #include "caqi_calculator.h" #include "aqi_calculator.h" -namespace esphome { -namespace hm3301 { +namespace esphome::aqi { enum AQICalculatorType { CAQI_TYPE = 0, AQI_TYPE = 1 }; @@ -12,18 +11,17 @@ class AQICalculatorFactory { public: AbstractAQICalculator *get_calculator(AQICalculatorType type) { if (type == 0) { - return caqi_calculator_; + return &this->caqi_calculator_; } else if (type == 1) { - return aqi_calculator_; + return &this->aqi_calculator_; } return nullptr; } protected: - CAQICalculator *caqi_calculator_ = new CAQICalculator(); - AQICalculator *aqi_calculator_ = new AQICalculator(); + CAQICalculator caqi_calculator_; + AQICalculator aqi_calculator_; }; -} // namespace hm3301 -} // namespace esphome +} // namespace esphome::aqi diff --git a/esphome/components/aqi/aqi_sensor.cpp b/esphome/components/aqi/aqi_sensor.cpp new file mode 100644 index 0000000000..2d8a780cc7 --- /dev/null +++ b/esphome/components/aqi/aqi_sensor.cpp @@ -0,0 +1,51 @@ +#include "aqi_sensor.h" +#include "esphome/core/log.h" + +namespace esphome::aqi { + +static const char *const TAG = "aqi"; + +void AQISensor::setup() { + if (this->pm_2_5_sensor_ != nullptr) { + this->pm_2_5_sensor_->add_on_state_callback([this](float value) { + this->pm_2_5_value_ = value; + // Defer calculation to avoid double-publishing if both sensors update in the same loop + this->defer("update", [this]() { this->calculate_aqi_(); }); + }); + } + if (this->pm_10_0_sensor_ != nullptr) { + this->pm_10_0_sensor_->add_on_state_callback([this](float value) { + this->pm_10_0_value_ = value; + this->defer("update", [this]() { this->calculate_aqi_(); }); + }); + } +} + +void AQISensor::dump_config() { + ESP_LOGCONFIG(TAG, "AQI Sensor:"); + ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI"); + if (this->pm_2_5_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str()); + } + if (this->pm_10_0_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, " PM10 Sensor: '%s'", this->pm_10_0_sensor_->get_name().c_str()); + } + LOG_SENSOR(" ", "AQI", this); +} + +void AQISensor::calculate_aqi_() { + if (std::isnan(this->pm_2_5_value_) || std::isnan(this->pm_10_0_value_)) { + return; + } + + AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); + if (calculator == nullptr) { + ESP_LOGW(TAG, "Unknown AQI calculator type"); + return; + } + + uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_); + this->publish_state(aqi); +} + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/aqi_sensor.h b/esphome/components/aqi/aqi_sensor.h new file mode 100644 index 0000000000..a990f815fe --- /dev/null +++ b/esphome/components/aqi/aqi_sensor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "aqi_calculator_factory.h" + +namespace esphome::aqi { + +class AQISensor : public sensor::Sensor, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + 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; } + + protected: + void calculate_aqi_(); + + sensor::Sensor *pm_2_5_sensor_{nullptr}; + sensor::Sensor *pm_10_0_sensor_{nullptr}; + AQICalculatorType aqi_calc_type_{AQI_TYPE}; + AQICalculatorFactory aqi_calculator_factory_; + + float pm_2_5_value_{NAN}; + float pm_10_0_value_{NAN}; +}; + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/caqi_calculator.h b/esphome/components/aqi/caqi_calculator.h new file mode 100644 index 0000000000..d2ec4bb98f --- /dev/null +++ b/esphome/components/aqi/caqi_calculator.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include "abstract_aqi_calculator.h" + +namespace esphome::aqi { + +class CAQICalculator : 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); + + return static_cast(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index)); + } + + protected: + static constexpr int NUM_LEVELS = 5; + + static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; + + static constexpr float PM2_5_GRID[NUM_LEVELS][2] = { + {0.0f, 15.0f}, {15.1f, 30.0f}, {30.1f, 55.0f}, {55.1f, 110.0f}, {110.1f, std::numeric_limits::max()}}; + + static constexpr float PM10_0_GRID[NUM_LEVELS][2] = { + {0.0f, 25.0f}, {25.1f, 50.0f}, {50.1f, 90.0f}, {90.1f, 180.0f}, {180.1f, std::numeric_limits::max()}}; + + static float calculate_index(float value, const float array[NUM_LEVELS][2]) { + int grid_index = get_grid_index(value, array); + if (grid_index == -1) { + return -1.0f; + } + + float aqi_lo = INDEX_GRID[grid_index][0]; + float aqi_hi = INDEX_GRID[grid_index][1]; + 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; + } + + static int get_grid_index(float value, const float array[NUM_LEVELS][2]) { + for (int i = 0; i < NUM_LEVELS; i++) { + if (value >= array[i][0] && value <= array[i][1]) { + return i; + } + } + return -1; + } +}; + +} // namespace esphome::aqi diff --git a/esphome/components/aqi/sensor.py b/esphome/components/aqi/sensor.py new file mode 100644 index 0000000000..0b5ee8d75a --- /dev/null +++ b/esphome/components/aqi/sensor.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_PM_2_5, + CONF_PM_10_0, + DEVICE_CLASS_AQI, + STATE_CLASS_MEASUREMENT, +) + +from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns + +CODEOWNERS = ["@jasstrong"] +DEPENDENCIES = ["sensor"] + +UNIT_INDEX = "index" + +AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + AQISensor, + unit_of_measurement=UNIT_INDEX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_AQI, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Required(CONF_PM_2_5): cv.use_id(sensor.Sensor), + cv.Required(CONF_PM_10_0): cv.use_id(sensor.Sensor), + cv.Required(CONF_CALCULATION_TYPE): cv.enum( + AQI_CALCULATION_TYPE, upper=True + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + pm_2_5_sensor = await cg.get_variable(config[CONF_PM_2_5]) + cg.add(var.set_pm_2_5_sensor(pm_2_5_sensor)) + + pm_10_0_sensor = await cg.get_variable(config[CONF_PM_10_0]) + cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor)) + + cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE])) diff --git a/esphome/components/as3935/as3935.cpp b/esphome/components/as3935/as3935.cpp index 2609af07d3..93a0bff5b3 100644 --- a/esphome/components/as3935/as3935.cpp +++ b/esphome/components/as3935/as3935.cpp @@ -305,12 +305,14 @@ bool AS3935Component::calibrate_oscillator() { } void AS3935Component::tune_antenna() { - ESP_LOGI(TAG, "Starting antenna tuning"); uint8_t div_ratio = this->read_div_ratio(); uint8_t tune_val = this->read_capacitance(); - ESP_LOGI(TAG, "Division Ratio is set to: %d", div_ratio); - ESP_LOGI(TAG, "Internal Capacitor is set to: %d", tune_val); - ESP_LOGI(TAG, "Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio"); + ESP_LOGI(TAG, + "Starting antenna tuning\n" + "Division Ratio is set to: %d\n" + "Internal Capacitor is set to: %d\n" + "Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio", + div_ratio, tune_val); this->display_oscillator(true, ANTFREQ); } diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index f2d8895b39..1ff4805f03 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -1,37 +1,50 @@ -# Dummy integration to allow relying on AsyncTCP +# Async TCP client support for all platforms import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import ( - PLATFORM_BK72XX, - PLATFORM_ESP32, - PLATFORM_ESP8266, - PLATFORM_LN882X, - PLATFORM_RTL87XX, -) from esphome.core import CORE, CoroPriority, coroutine_with_priority CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network"] -CONFIG_SCHEMA = cv.All( - cv.Schema({}), - cv.only_with_arduino, - cv.only_on( - [ - PLATFORM_ESP32, - PLATFORM_ESP8266, - PLATFORM_BK72XX, - PLATFORM_LN882X, - PLATFORM_RTL87XX, - ] - ), -) + +def AUTO_LOAD() -> list[str]: + # Socket component needed for platforms using socket-based implementation + # ESP32, ESP8266, RP2040, and LibreTiny use AsyncTCP libraries, others use sockets + if ( + not CORE.is_esp32 + and not CORE.is_esp8266 + and not CORE.is_rp2040 + and not CORE.is_libretiny + ): + return ["socket"] + return [] + + +# Support all platforms - Arduino/ESP-IDF get libraries, other platforms use socket implementation +CONFIG_SCHEMA = cv.Schema({}) @coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT) async def to_code(config): - if CORE.is_esp32 or CORE.is_libretiny: + if CORE.is_esp32: + # https://github.com/ESP32Async/AsyncTCP + from esphome.components.esp32 import add_idf_component + + add_idf_component(name="esp32async/asynctcp", ref="3.4.91") + elif CORE.is_libretiny: # https://github.com/ESP32Async/AsyncTCP cg.add_library("ESP32Async/AsyncTCP", "3.4.5") elif CORE.is_esp8266: # https://github.com/ESP32Async/ESPAsyncTCP cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") + elif CORE.is_rp2040: + # https://github.com/khoih-prog/AsyncTCP_RP2040W + cg.add_library("khoih-prog/AsyncTCP_RP2040W", "1.2.0") + # Other platforms (host, etc) use socket-based implementation + + +def FILTER_SOURCE_FILES() -> list[str]: + # Exclude socket implementation for platforms that use AsyncTCP libraries + if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny: + return ["async_tcp_socket.cpp"] + return [] diff --git a/esphome/components/async_tcp/async_tcp.h b/esphome/components/async_tcp/async_tcp.h new file mode 100644 index 0000000000..6d9211f023 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp.h @@ -0,0 +1,16 @@ +#pragma once +#include "esphome/core/defines.h" + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) +// Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny +#include +#elif defined(USE_ESP8266) +// Use ESPAsyncTCP library for ESP8266 (always Arduino) +#include +#elif defined(USE_RP2040) +// Use AsyncTCP_RP2040W library for RP2040 +#include +#else +// Use socket-based implementation for other platforms +#include "async_tcp_socket.h" +#endif diff --git a/esphome/components/async_tcp/async_tcp_socket.cpp b/esphome/components/async_tcp/async_tcp_socket.cpp new file mode 100644 index 0000000000..f64e494f5f --- /dev/null +++ b/esphome/components/async_tcp/async_tcp_socket.cpp @@ -0,0 +1,162 @@ +#include "async_tcp_socket.h" + +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + (defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)) + +#include "esphome/components/network/util.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome::async_tcp { + +static const char *const TAG = "async_tcp"; + +// Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers). +// This implementation only runs on ESP-IDF and host which have ample stack. +static constexpr size_t READ_BUFFER_SIZE = 1460; + +bool AsyncClient::connect(const char *host, uint16_t port) { + if (connected_ || connecting_) { + ESP_LOGW(TAG, "Already connected/connecting"); + return false; + } + + // Resolve address + struct sockaddr_storage addr; + socklen_t addrlen = esphome::socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, port); + if (addrlen == 0) { + ESP_LOGE(TAG, "Invalid address: %s", host); + if (error_cb_) + error_cb_(error_arg_, this, -1); + return false; + } + + // Create socket with loop monitoring + int family = ((struct sockaddr *) &addr)->sa_family; + socket_ = esphome::socket::socket_loop_monitored(family, SOCK_STREAM, IPPROTO_TCP); + if (!socket_) { + ESP_LOGE(TAG, "Failed to create socket"); + if (error_cb_) + error_cb_(error_arg_, this, -1); + return false; + } + + socket_->setblocking(false); + + int err = socket_->connect((struct sockaddr *) &addr, addrlen); + if (err == 0) { + // Connection succeeded immediately (rare, but possible for localhost) + connected_ = true; + if (connect_cb_) + connect_cb_(connect_arg_, this); + return true; + } + if (errno != EINPROGRESS) { + ESP_LOGE(TAG, "Connect failed: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + return false; + } + + connecting_ = true; + return true; +} + +void AsyncClient::close() { + socket_.reset(); + bool was_connected = connected_; + connected_ = false; + connecting_ = false; + if (was_connected && disconnect_cb_) + disconnect_cb_(disconnect_arg_, this); +} + +size_t AsyncClient::write(const char *data, size_t len) { + if (!socket_ || !connected_) + return 0; + + ssize_t sent = socket_->write(data, len); + if (sent < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + ESP_LOGE(TAG, "Write error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + return 0; + } + return sent; +} + +void AsyncClient::loop() { + if (!socket_) + return; + + if (connecting_) { + // For connecting, we need to check writability, not readability + // The Application's select() only monitors read FDs, so we do our own check here + // For ESP platforms lwip_select() might be faster, but this code isn't used + // on those platforms anyway. If it was, we'd fix the Application select() + // to report writability instead of doing it this way. + int fd = socket_->get_fd(); + if (fd < 0) { + ESP_LOGW(TAG, "Invalid socket fd"); + close(); + return; + } + + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(fd, &writefds); + + struct timeval tv = {0, 0}; + int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv); + + if (ret > 0 && FD_ISSET(fd, &writefds)) { + int error = 0; + socklen_t len = sizeof(error); + if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) { + connecting_ = false; + connected_ = true; + if (connect_cb_) + connect_cb_(connect_arg_, this); + } else { + ESP_LOGW(TAG, "Connection failed: %d", error); + close(); + if (error_cb_) + error_cb_(error_arg_, this, error); + } + } else if (ret < 0) { + ESP_LOGE(TAG, "Select error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + } else if (connected_) { + // For connected sockets, use the Application's select() results + if (!socket_->ready()) + return; + + uint8_t buf[READ_BUFFER_SIZE]; + ssize_t len = socket_->read(buf, READ_BUFFER_SIZE); + + if (len == 0) { + ESP_LOGI(TAG, "Connection closed by peer"); + close(); + } else if (len > 0) { + if (data_cb_) + data_cb_(data_arg_, this, buf, len); + } else if (errno != EAGAIN && errno != EWOULDBLOCK) { + ESP_LOGW(TAG, "Read error: %d", errno); + close(); + if (error_cb_) + error_cb_(error_arg_, this, errno); + } + } +} + +} // namespace esphome::async_tcp + +#endif diff --git a/esphome/components/async_tcp/async_tcp_socket.h b/esphome/components/async_tcp/async_tcp_socket.h new file mode 100644 index 0000000000..28714a7752 --- /dev/null +++ b/esphome/components/async_tcp/async_tcp_socket.h @@ -0,0 +1,73 @@ +#pragma once + +#include "esphome/core/defines.h" + +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + (defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)) + +#include "esphome/components/socket/socket.h" +#include +#include +#include +#include + +namespace esphome::async_tcp { + +/// AsyncClient API for platforms using sockets (ESP-IDF, host, etc.) +/// NOTE: This class is NOT thread-safe. All methods must be called from the main loop. +class AsyncClient { + public: + using AcConnectHandler = std::function; + using AcDataHandler = std::function; + using AcErrorHandler = std::function; + + AsyncClient() = default; + ~AsyncClient() = default; + + [[nodiscard]] bool connect(const char *host, uint16_t port); + void close(); + [[nodiscard]] bool connected() const { return connected_; } + size_t write(const char *data, size_t len); + + void onConnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + connect_cb_ = std::move(cb); + connect_arg_ = arg; + } + void onDisconnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + disconnect_cb_ = std::move(cb); + disconnect_arg_ = arg; + } + /// Set data callback. NOTE: data pointer is only valid during callback execution. + void onData(AcDataHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + data_cb_ = std::move(cb); + data_arg_ = arg; + } + void onError(AcErrorHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming) + error_cb_ = std::move(cb); + error_arg_ = arg; + } + + // Must be called from loop() + void loop(); + + private: + std::unique_ptr socket_; + + AcConnectHandler connect_cb_{nullptr}; + void *connect_arg_{nullptr}; + AcConnectHandler disconnect_cb_{nullptr}; + void *disconnect_arg_{nullptr}; + AcDataHandler data_cb_{nullptr}; + void *data_arg_{nullptr}; + AcErrorHandler error_cb_{nullptr}; + void *error_arg_{nullptr}; + + bool connected_{false}; + bool connecting_{false}; +}; + +} // namespace esphome::async_tcp + +// Expose AsyncClient in global namespace to match library behavior +using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers) +#endif diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 9d550fcf8c..b4d2929742 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -21,7 +21,9 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -32,7 +34,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results_(res, device.address_str()))) { + if (!(report_results_(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -103,13 +105,13 @@ bool ATCMiThermometer::parse_message_(const std::vector &message, Parse return true; } -bool ATCMiThermometer::report_results_(const optional &result, const std::string &address) { +bool ATCMiThermometer::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str()); + ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature); diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.h b/esphome/components/atc_mithermometer/atc_mithermometer.h index d22e3f069b..e37b5f4350 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.h +++ b/esphome/components/atc_mithermometer/atc_mithermometer.h @@ -41,7 +41,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace atc_mithermometer diff --git a/esphome/components/audio/audio_reader.cpp b/esphome/components/audio/audio_reader.cpp index 6966c95db7..7794187a69 100644 --- a/esphome/components/audio/audio_reader.cpp +++ b/esphome/components/audio/audio_reader.cpp @@ -1,6 +1,6 @@ #include "audio_reader.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/defines.h" #include "esphome/core/hal.h" diff --git a/esphome/components/audio/audio_reader.h b/esphome/components/audio/audio_reader.h index 3fdc3c3ff2..0b73923e84 100644 --- a/esphome/components/audio/audio_reader.h +++ b/esphome/components/audio/audio_reader.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "audio.h" #include "audio_transfer_buffer.h" diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index 2e548a8072..356f396476 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -22,7 +22,8 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); const auto &service_datas = device.get_service_datas(); if (service_datas.size() != 1) { ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size()); diff --git a/esphome/components/bedjet/bedjet_hub.cpp b/esphome/components/bedjet/bedjet_hub.cpp index 38fcf29b3b..fec34c5b2a 100644 --- a/esphome/components/bedjet/bedjet_hub.cpp +++ b/esphome/components/bedjet/bedjet_hub.cpp @@ -193,8 +193,9 @@ bool BedJetHub::discover_characteristics_() { result = false; } else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 || descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + char uuid_buf[espbt::UUID_STR_LEN]; ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_, - descr->uuid.to_string().c_str()); + descr->uuid.to_str(uuid_buf)); result = false; } else { this->config_descr_status_ = descr->handle; @@ -216,11 +217,14 @@ bool BedJetHub::discover_characteristics_() { } } - ESP_LOGI(TAG, "[%s] Discovered service characteristics: ", this->get_name().c_str()); - ESP_LOGI(TAG, " - Command char: 0x%x", this->char_handle_cmd_); - ESP_LOGI(TAG, " - Status char: 0x%x", this->char_handle_status_); - ESP_LOGI(TAG, " - config descriptor: 0x%x", this->config_descr_status_); - ESP_LOGI(TAG, " - Name char: 0x%x", this->char_handle_name_); + ESP_LOGI(TAG, + "[%s] Discovered service characteristics:\n" + " - Command char: 0x%x\n" + " - Status char: 0x%x\n" + " - config descriptor: 0x%x\n" + " - Name char: 0x%x", + this->get_name().c_str(), this->char_handle_cmd_, this->char_handle_status_, this->config_descr_status_, + this->char_handle_name_); return result; } diff --git a/esphome/components/bedjet/climate/__init__.py b/esphome/components/bedjet/climate/__init__.py index 0da2107d43..4de9dcca0b 100644 --- a/esphome/components/bedjet/climate/__init__.py +++ b/esphome/components/bedjet/climate/__init__.py @@ -1,12 +1,7 @@ import esphome.codegen as cg -from esphome.components import ble_client, climate +from esphome.components import climate import esphome.config_validation as cv -from esphome.const import ( - CONF_HEAT_MODE, - CONF_RECEIVE_TIMEOUT, - CONF_TEMPERATURE_SOURCE, - CONF_TIME_ID, -) +from esphome.const import CONF_HEAT_MODE, CONF_TEMPERATURE_SOURCE from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child @@ -38,22 +33,6 @@ CONFIG_SCHEMA = ( } ) .extend(cv.polling_component_schema("60s")) - .extend( - # TODO: remove compat layer. - { - cv.Optional(ble_client.CONF_BLE_CLIENT_ID): cv.invalid( - "The 'ble_client_id' option has been removed. Please migrate " - "to the new `bedjet_id` option in the `bedjet` component.\n" - "See https://esphome.io/components/climate/bedjet/" - ), - cv.Optional(CONF_TIME_ID): cv.invalid( - "The 'time_id' option has been moved to the `bedjet` component." - ), - cv.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid( - "The 'receive_timeout' option has been moved to the `bedjet` component." - ), - } - ) .extend(BEDJET_CLIENT_SCHEMA) ) diff --git a/esphome/components/bedjet/climate/bedjet_climate.cpp b/esphome/components/bedjet/climate/bedjet_climate.cpp index 716d4d4241..68a0342873 100644 --- a/esphome/components/bedjet/climate/bedjet_climate.cpp +++ b/esphome/components/bedjet/climate/bedjet_climate.cpp @@ -164,21 +164,21 @@ void BedJetClimate::control(const ClimateCall &call) { return; } } else if (call.has_custom_preset()) { - const char *preset = call.get_custom_preset(); + auto preset = call.get_custom_preset(); bool result; - if (strcmp(preset, "M1") == 0) { + if (preset == "M1") { result = this->parent_->button_memory1(); - } else if (strcmp(preset, "M2") == 0) { + } else if (preset == "M2") { result = this->parent_->button_memory2(); - } else if (strcmp(preset, "M3") == 0) { + } else if (preset == "M3") { result = this->parent_->button_memory3(); - } else if (strcmp(preset, "LTD HT") == 0) { + } else if (preset == "LTD HT") { result = this->parent_->button_heat(); - } else if (strcmp(preset, "EXT HT") == 0) { + } else if (preset == "EXT HT") { result = this->parent_->button_ext_heat(); } else { - ESP_LOGW(TAG, "Unsupported preset: %s", preset); + ESP_LOGW(TAG, "Unsupported preset: %.*s", (int) preset.size(), preset.c_str()); return; } @@ -208,10 +208,11 @@ void BedJetClimate::control(const ClimateCall &call) { this->set_fan_mode_(fan_mode); } } else if (call.has_custom_fan_mode()) { - const char *fan_mode = call.get_custom_fan_mode(); - auto fan_index = bedjet_fan_speed_to_step(fan_mode); + auto fan_mode = call.get_custom_fan_mode(); + auto fan_index = bedjet_fan_speed_to_step(fan_mode.c_str()); if (fan_index <= 19) { - ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode, fan_index); + ESP_LOGV(TAG, "[%s] Converted fan mode %.*s to bedjet fan step %d", this->get_name().c_str(), + (int) fan_mode.size(), fan_mode.c_str(), fan_index); bool result = this->parent_->set_fan_index(fan_index); if (result) { this->set_custom_fan_mode_(fan_mode); diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 2fc476c17d..bd7c667c25 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -1,8 +1,8 @@ #include "bh1750.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" -namespace esphome { -namespace bh1750 { +namespace esphome::bh1750 { static const char *const TAG = "bh1750.sensor"; @@ -13,6 +13,31 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011; static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000; static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001; +static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000; +static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f; + +// Measurement time constants (datasheet values) +static constexpr uint16_t MTREG_DEFAULT = 69; +static constexpr uint16_t MTREG_MIN = 31; +static constexpr uint16_t MTREG_MAX = 254; +static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69 +static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69 + +// Conversion constants (datasheet formulas) +static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor +static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution + +// MTreg calculation constants +static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation) +static constexpr int COUNTS_NUMERATOR = 10; +static constexpr int COUNTS_DENOMINATOR = 12; + +// MTreg register bit manipulation constants +static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5 +static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits +static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0 +static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits + /* bh1750 properties: @@ -43,74 +68,7 @@ void BH1750Sensor::setup() { this->mark_failed(); return; } -} - -void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f) { - // turn on (after one-shot sensor automatically powers down) - uint8_t turn_on = BH1750_COMMAND_POWER_ON; - if (this->write(&turn_on, 1) != i2c::ERROR_OK) { - ESP_LOGW(TAG, "Power on failed"); - f(NAN); - return; - } - - if (active_mtreg_ != mtreg) { - // set mtreg - uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111); - uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111); - if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { - ESP_LOGW(TAG, "Set measurement time failed"); - active_mtreg_ = 0; - f(NAN); - return; - } - active_mtreg_ = mtreg; - } - - uint8_t cmd; - uint16_t meas_time; - switch (mode) { - case BH1750_MODE_L: - cmd = BH1750_COMMAND_ONE_TIME_L; - meas_time = 24 * mtreg / 69; - break; - case BH1750_MODE_H: - cmd = BH1750_COMMAND_ONE_TIME_H; - meas_time = 180 * mtreg / 69; - break; - case BH1750_MODE_H2: - cmd = BH1750_COMMAND_ONE_TIME_H2; - meas_time = 180 * mtreg / 69; - break; - default: - f(NAN); - return; - } - if (this->write(&cmd, 1) != i2c::ERROR_OK) { - ESP_LOGW(TAG, "Start measurement failed"); - f(NAN); - return; - } - - // probably not needed, but adjust for rounding - meas_time++; - - this->set_timeout("read", meas_time, [this, mode, mtreg, f]() { - uint16_t raw_value; - if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { - ESP_LOGW(TAG, "Read data failed"); - f(NAN); - return; - } - raw_value = i2c::i2ctohs(raw_value); - - float lx = float(raw_value) / 1.2f; - lx *= 69.0f / mtreg; - if (mode == BH1750_MODE_H2) - lx /= 2.0f; - - f(lx); - }); + this->state_ = IDLE; } void BH1750Sensor::dump_config() { @@ -124,45 +82,189 @@ void BH1750Sensor::dump_config() { } void BH1750Sensor::update() { - // first do a quick measurement in L-mode with full range - // to find right range - this->read_lx_(BH1750_MODE_L, 31, [this](float val) { - if (std::isnan(val)) { - this->status_set_warning(); - this->publish_state(NAN); + const uint32_t now = millis(); + + // Start coarse measurement to determine optimal mode/mtreg + if (this->state_ != IDLE) { + // Safety timeout: reset if stuck + if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) { + ESP_LOGW(TAG, "Measurement timeout, resetting state"); + this->state_ = IDLE; + } else { + ESP_LOGW(TAG, "Previous measurement not complete, skipping update"); return; } + } - BH1750Mode use_mode; - uint8_t use_mtreg; - if (val <= 7000) { - use_mode = BH1750_MODE_H2; - use_mtreg = 254; - } else { - use_mode = BH1750_MODE_H; - // lx = counts / 1.2 * (69 / mtreg) - // -> mtreg = counts / 1.2 * (69 / lx) - // calculate for counts=50000 (allow some range to not saturate, but maximize mtreg) - // -> mtreg = 50000*(10/12)*(69/lx) - int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val); - use_mtreg = std::min(254, std::max(31, ideal_mtreg)); - } - ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg); + if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) { + this->status_set_warning(); + this->publish_state(NAN); + return; + } - this->read_lx_(use_mode, use_mtreg, [this](float val) { - if (std::isnan(val)) { - this->status_set_warning(); - this->publish_state(NAN); - return; + this->state_ = WAITING_COARSE_MEASUREMENT; + this->enable_loop(); // Enable loop while measurement in progress +} + +void BH1750Sensor::loop() { + const uint32_t now = App.get_loop_component_start_time(); + + switch (this->state_) { + case IDLE: + // Disable loop when idle to save cycles + this->disable_loop(); + break; + + case WAITING_COARSE_MEASUREMENT: + if (now - this->measurement_start_time_ >= this->measurement_duration_) { + this->state_ = READING_COARSE_RESULT; } - ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val); + break; + + case READING_COARSE_RESULT: { + float lx; + if (!this->read_measurement_(lx)) { + this->fail_and_reset_(); + break; + } + + this->process_coarse_result_(lx); + + // Start fine measurement with optimal settings + // fetch millis() again since the read can take a bit + if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) { + this->fail_and_reset_(); + break; + } + + this->state_ = WAITING_FINE_MEASUREMENT; + break; + } + + case WAITING_FINE_MEASUREMENT: + if (now - this->measurement_start_time_ >= this->measurement_duration_) { + this->state_ = READING_FINE_RESULT; + } + break; + + case READING_FINE_RESULT: { + float lx; + if (!this->read_measurement_(lx)) { + this->fail_and_reset_(); + break; + } + + ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx); this->status_clear_warning(); - this->publish_state(val); - }); - }); + this->publish_state(lx); + this->state_ = IDLE; + break; + } + } +} + +bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) { + // Power on + uint8_t turn_on = BH1750_COMMAND_POWER_ON; + if (this->write(&turn_on, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Power on failed"); + return false; + } + + // Set MTreg if changed + if (this->active_mtreg_ != mtreg) { + uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK); + uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK); + if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Set measurement time failed"); + this->active_mtreg_ = 0; + return false; + } + this->active_mtreg_ = mtreg; + } + + // Start measurement + uint8_t cmd; + uint16_t meas_time; + switch (mode) { + case BH1750_MODE_L: + cmd = BH1750_COMMAND_ONE_TIME_L; + meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT; + break; + case BH1750_MODE_H: + cmd = BH1750_COMMAND_ONE_TIME_H; + meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT; + break; + case BH1750_MODE_H2: + cmd = BH1750_COMMAND_ONE_TIME_H2; + meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT; + break; + default: + return false; + } + + if (this->write(&cmd, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Start measurement failed"); + return false; + } + + // Store current measurement parameters + this->current_mode_ = mode; + this->current_mtreg_ = mtreg; + this->measurement_start_time_ = now; + this->measurement_duration_ = meas_time + 1; // Add 1ms for safety + + return true; +} + +bool BH1750Sensor::read_measurement_(float &lx_out) { + uint16_t raw_value; + if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Read data failed"); + return false; + } + raw_value = i2c::i2ctohs(raw_value); + + float lx = float(raw_value) / RESOLUTION_DIVISOR; + lx *= float(MTREG_DEFAULT) / this->current_mtreg_; + if (this->current_mode_ == BH1750_MODE_H2) { + lx /= MODE_H2_DIVISOR; + } + + lx_out = lx; + return true; +} + +void BH1750Sensor::process_coarse_result_(float lx) { + if (std::isnan(lx)) { + // Use defaults if coarse measurement failed + this->fine_mode_ = BH1750_MODE_H2; + this->fine_mtreg_ = MTREG_MAX; + return; + } + + if (lx <= HIGH_LIGHT_THRESHOLD_LX) { + this->fine_mode_ = BH1750_MODE_H2; + this->fine_mtreg_ = MTREG_MAX; + } else { + this->fine_mode_ = BH1750_MODE_H; + // lx = counts / 1.2 * (69 / mtreg) + // -> mtreg = counts / 1.2 * (69 / lx) + // calculate for counts=50000 (allow some range to not saturate, but maximize mtreg) + // -> mtreg = 50000*(10/12)*(69/lx) + int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx); + this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg)); + } + + ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_); +} + +void BH1750Sensor::fail_and_reset_() { + this->status_set_warning(); + this->publish_state(NAN); + this->state_ = IDLE; } float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } -} // namespace bh1750 -} // namespace esphome +} // namespace esphome::bh1750 diff --git a/esphome/components/bh1750/bh1750.h b/esphome/components/bh1750/bh1750.h index a31eb33609..0460427954 100644 --- a/esphome/components/bh1750/bh1750.h +++ b/esphome/components/bh1750/bh1750.h @@ -4,10 +4,9 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" -namespace esphome { -namespace bh1750 { +namespace esphome::bh1750 { -enum BH1750Mode { +enum BH1750Mode : uint8_t { BH1750_MODE_L, BH1750_MODE_H, BH1750_MODE_H2, @@ -21,13 +20,36 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c: void setup() override; void dump_config() override; void update() override; + void loop() override; float get_setup_priority() const override; protected: - void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f); + // State machine states + enum State : uint8_t { + IDLE, + WAITING_COARSE_MEASUREMENT, + READING_COARSE_RESULT, + WAITING_FINE_MEASUREMENT, + READING_FINE_RESULT, + }; + // 4-byte aligned members + uint32_t measurement_start_time_{0}; + uint32_t measurement_duration_{0}; + + // 1-byte members grouped together to minimize padding + State state_{IDLE}; + BH1750Mode current_mode_{BH1750_MODE_L}; + uint8_t current_mtreg_{31}; + BH1750Mode fine_mode_{BH1750_MODE_H2}; + uint8_t fine_mtreg_{254}; uint8_t active_mtreg_{0}; + + // Helper methods + bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now); + bool read_measurement_(float &lx_out); + void process_coarse_result_(float lx); + void fail_and_reset_(); }; -} // namespace bh1750 -} // namespace esphome +} // namespace esphome::bh1750 diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 7c7eecb88c..36af5aeef9 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -20,16 +20,6 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.Optional("resolution"): cv.invalid( - "The 'resolution' option has been removed. The optimal value is now dynamically calculated." - ), - cv.Optional("measurement_duration"): cv.invalid( - "The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated." - ), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x23)) ) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index cbf935a501..c38d6b78d3 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -3,7 +3,7 @@ from logging import getLogger from esphome import automation, core from esphome.automation import Condition, maybe_simple_id import esphome.codegen as cg -from esphome.components import mqtt, web_server +from esphome.components import mqtt, web_server, zigbee from esphome.components.const import CONF_ON_STATE_CHANGE import esphome.config_validation as cv from esphome.const import ( @@ -439,6 +439,7 @@ def validate_publish_initial_state(value): _BINARY_SENSOR_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend(zigbee.BINARY_SENSOR_SCHEMA) .extend( { cv.GenerateID(): cv.declare_id(BinarySensor), @@ -520,6 +521,7 @@ _BINARY_SENSOR_SCHEMA = ( _BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor")) +_BINARY_SENSOR_SCHEMA.add_extra(zigbee.validate_binary_sensor) def binary_sensor_schema( @@ -621,6 +623,8 @@ async def setup_binary_sensor_core_(var, config): if web_server_config := config.get(CONF_WEB_SERVER): await web_server.add_entity_config(var, web_server_config) + await zigbee.setup_binary_sensor(var, config) + async def register_binary_sensor(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/binary_sensor/automation.cpp b/esphome/components/binary_sensor/automation.cpp index 66d8d6e90f..dfe911a2f8 100644 --- a/esphome/components/binary_sensor/automation.cpp +++ b/esphome/components/binary_sensor/automation.cpp @@ -21,8 +21,10 @@ void MultiClickTrigger::on_state_(bool state) { // Start matching MultiClickTriggerEvent evt = this->timing_[0]; if (evt.state == state) { - ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length); - ESP_LOGV(TAG, "Multi Click: Starting multi click action!"); + ESP_LOGV(TAG, + "START min=%" PRIu32 " max=%" PRIu32 "\n" + "Multi Click: Starting multi click action!", + evt.min_length, evt.max_length); this->at_index_ = 1; if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) { this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); }); diff --git a/esphome/components/bk72xx/__init__.py b/esphome/components/bk72xx/__init__.py index 5b14d0529d..7fed742d2e 100644 --- a/esphome/components/bk72xx/__init__.py +++ b/esphome/components/bk72xx/__init__.py @@ -1,9 +1,23 @@ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. -# For custom pin validators, put validate_pin() or validate_usage() -# in gpio.py file in this directory. -# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA -# in schema.py file in this directory. +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. + +To customize this component: + - Pin validators: Create gpio.py with validate_pin() or validate_usage() + - Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA + +Platform-specific code should be added to the main libretiny component +(__init__.py in esphome/components/libretiny/) rather than here. +""" from esphome import pins from esphome.components import libretiny @@ -27,6 +41,7 @@ COMPONENT_DATA = LibreTinyComponent( board_pins=BK72XX_BOARD_PINS, pin_validation=None, usage_validation=None, + supports_atomics=False, ) diff --git a/esphome/components/bk72xx/boards.py b/esphome/components/bk72xx/boards.py index 8e3e8a97a2..3850dbe266 100644 --- a/esphome/components/bk72xx/boards.py +++ b/esphome/components/bk72xx/boards.py @@ -1,5 +1,16 @@ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. +""" from esphome.components.libretiny.const import ( FAMILY_BK7231N, @@ -9,6 +20,22 @@ from esphome.components.libretiny.const import ( ) BK72XX_BOARDS = { + "wb2l-m1": { + "name": "WB2L_M1 Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "cbu": { + "name": "CBU Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "generic-bk7231t-qfn32-tuya": { + "name": "Generic - BK7231T (Tuya QFN32)", + "family": FAMILY_BK7231T, + }, + "generic-bk7231n-qfn32-tuya": { + "name": "Generic - BK7231N (Tuya QFN32)", + "family": FAMILY_BK7231N, + }, "cb1s": { "name": "CB1S Wi-Fi Module", "family": FAMILY_BK7231N, @@ -17,85 +44,324 @@ BK72XX_BOARDS = { "name": "CB2L Wi-Fi Module", "family": FAMILY_BK7231N, }, - "cb2s": { - "name": "CB2S Wi-Fi Module", - "family": FAMILY_BK7231N, - }, - "cb3l": { - "name": "CB3L Wi-Fi Module", + "cblc5": { + "name": "CBLC5 Wi-Fi Module", "family": FAMILY_BK7231N, }, "cb3s": { "name": "CB3S Wi-Fi Module", "family": FAMILY_BK7231N, }, - "cb3se": { - "name": "CB3SE Wi-Fi Module", - "family": FAMILY_BK7231N, - }, - "cblc5": { - "name": "CBLC5 Wi-Fi Module", - "family": FAMILY_BK7231N, - }, - "cbu": { - "name": "CBU Wi-Fi Module", - "family": FAMILY_BK7231N, - }, - "generic-bk7231n-qfn32-tuya": { - "name": "Generic - BK7231N (Tuya QFN32)", - "family": FAMILY_BK7231N, - }, - "generic-bk7231t-qfn32-tuya": { - "name": "Generic - BK7231T (Tuya QFN32)", - "family": FAMILY_BK7231T, - }, - "generic-bk7252": { - "name": "Generic - BK7252", - "family": FAMILY_BK7251, - }, - "lsc-lma35-t": { - "name": "LSC LMA35 BK7231T", + "wb3s": { + "name": "WB3S Wi-Fi Module", "family": FAMILY_BK7231T, }, "lsc-lma35": { "name": "LSC LMA35 BK7231N", "family": FAMILY_BK7231N, }, - "wa2": { - "name": "WA2 Wi-Fi Module", - "family": FAMILY_BK7231Q, - }, - "wb1s": { - "name": "WB1S Wi-Fi Module", - "family": FAMILY_BK7231T, - }, - "wb2l-m1": { - "name": "WB2L_M1 Wi-Fi Module", - "family": FAMILY_BK7231N, + "generic-bk7252": { + "name": "Generic - BK7252", + "family": FAMILY_BK7251, }, "wb2l": { "name": "WB2L Wi-Fi Module", "family": FAMILY_BK7231T, }, - "wb2s": { - "name": "WB2S Wi-Fi Module", - "family": FAMILY_BK7231T, - }, - "wb3l": { - "name": "WB3L Wi-Fi Module", - "family": FAMILY_BK7231T, - }, - "wb3s": { - "name": "WB3S Wi-Fi Module", + "wb1s": { + "name": "WB1S Wi-Fi Module", "family": FAMILY_BK7231T, }, "wblc5": { "name": "WBLC5 Wi-Fi Module", "family": FAMILY_BK7231T, }, + "cb2s": { + "name": "CB2S Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "wa2": { + "name": "WA2 Wi-Fi Module", + "family": FAMILY_BK7231Q, + }, + "cb3l": { + "name": "CB3L Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "lsc-lma35-t": { + "name": "LSC LMA35 BK7231T", + "family": FAMILY_BK7231T, + }, + "cb3se": { + "name": "CB3SE Wi-Fi Module", + "family": FAMILY_BK7231N, + }, + "wb3l": { + "name": "WB3L Wi-Fi Module", + "family": FAMILY_BK7231T, + }, + "wb2s": { + "name": "WB2S Wi-Fi Module", + "family": FAMILY_BK7231T, + }, } BK72XX_BOARD_PINS = { + "wb2l-m1": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 26, + "D4": 24, + "D5": 10, + "D6": 11, + "D7": 1, + "D8": 0, + "D9": 20, + "D10": 21, + "D11": 23, + "D12": 22, + "A0": 23, + }, + "cbu": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "CS": 15, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "P28": 28, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 14, + "D1": 16, + "D2": 20, + "D3": 22, + "D4": 23, + "D5": 1, + "D6": 0, + "D7": 8, + "D8": 7, + "D9": 6, + "D10": 26, + "D11": 24, + "D12": 11, + "D13": 10, + "D14": 28, + "D15": 9, + "D16": 17, + "D17": 15, + "D18": 21, + "A0": 23, + }, + "generic-bk7231t-qfn32-tuya": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "CS": 15, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "P28": 28, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 0, + "D1": 1, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 14, + "D9": 15, + "D10": 16, + "D11": 17, + "D12": 20, + "D13": 21, + "D14": 22, + "D15": 23, + "D16": 24, + "D17": 26, + "D18": 28, + "A0": 23, + }, + "generic-bk7231n-qfn32-tuya": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "CS": 15, + "MISO": 17, + "MOSI": 16, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P15": 15, + "P16": 16, + "P17": 17, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "P28": 28, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCK": 14, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 0, + "D1": 1, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 14, + "D9": 15, + "D10": 16, + "D11": 17, + "D12": 20, + "D13": 21, + "D14": 22, + "D15": 23, + "D16": 24, + "D17": 26, + "D18": 28, + "A0": 23, + }, "cb1s": { "WIRE1_SCL": 20, "WIRE1_SDA": 21, @@ -183,28 +449,22 @@ BK72XX_BOARD_PINS = { "D7": 11, "D8": 21, }, - "cb2s": { + "cblc5": { "WIRE2_SCL": 0, "WIRE2_SDA": 1, "SERIAL1_RX": 10, "SERIAL1_TX": 11, "SERIAL2_RX": 1, "SERIAL2_TX": 0, - "ADC3": 23, "P0": 0, "P1": 1, "P6": 6, - "P7": 7, - "P8": 8, "P10": 10, "P11": 11, "P21": 21, - "P23": 23, "P24": 24, "P26": 26, "PWM0": 6, - "PWM1": 7, - "PWM2": 8, "PWM4": 24, "PWM5": 26, "RX1": 10, @@ -214,61 +474,14 @@ BK72XX_BOARD_PINS = { "SDA2": 1, "TX1": 11, "TX2": 0, - "D0": 6, - "D1": 7, - "D2": 8, - "D3": 23, - "D4": 10, - "D5": 11, - "D6": 24, - "D7": 26, - "D8": 0, - "D9": 1, - "D10": 21, - "A0": 23, - }, - "cb3l": { - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_TX": 0, - "ADC3": 23, - "P0": 0, - "P6": 6, - "P7": 7, - "P8": 8, - "P9": 9, - "P10": 10, - "P11": 11, - "P14": 14, - "P21": 21, - "P23": 23, - "P24": 24, - "P26": 26, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM3": 9, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "SCK": 14, - "SCL2": 0, - "SDA1": 21, - "TX1": 11, - "TX2": 0, - "D0": 23, - "D1": 14, + "D0": 24, + "D1": 6, "D2": 26, - "D3": 24, - "D4": 6, - "D5": 9, + "D3": 11, + "D4": 10, + "D5": 1, "D6": 0, "D7": 21, - "D8": 8, - "D9": 7, - "D10": 10, - "D11": 11, - "A0": 23, }, "cb3s": { "WIRE1_SCL": 20, @@ -321,7 +534,9 @@ BK72XX_BOARD_PINS = { "D13": 20, "A0": 23, }, - "cb3se": { + "wb3s": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, "WIRE2_SCL": 0, "WIRE2_SDA": 1, "SERIAL1_RX": 10, @@ -329,9 +544,6 @@ BK72XX_BOARD_PINS = { "SERIAL2_RX": 1, "SERIAL2_TX": 0, "ADC3": 23, - "CS": 15, - "MISO": 17, - "MOSI": 16, "P0": 0, "P1": 1, "P6": 6, @@ -341,10 +553,8 @@ BK72XX_BOARD_PINS = { "P10": 10, "P11": 11, "P14": 14, - "P15": 15, - "P16": 16, - "P17": 17, "P20": 20, + "P21": 21, "P22": 22, "P23": 23, "P24": 24, @@ -360,6 +570,7 @@ BK72XX_BOARD_PINS = { "SCK": 14, "SCL1": 20, "SCL2": 0, + "SDA1": 21, "SDA2": 1, "TX1": 11, "TX2": 0, @@ -368,57 +579,19 @@ BK72XX_BOARD_PINS = { "D2": 26, "D3": 24, "D4": 6, - "D5": 9, + "D5": 7, "D6": 0, "D7": 1, - "D8": 8, - "D9": 7, + "D8": 9, + "D9": 8, "D10": 10, "D11": 11, - "D12": 15, - "D13": 22, + "D12": 22, + "D13": 21, "D14": 20, - "D15": 17, - "D16": 16, "A0": 23, }, - "cblc5": { - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "P0": 0, - "P1": 1, - "P6": 6, - "P10": 10, - "P11": 11, - "P21": 21, - "P24": 24, - "P26": 26, - "PWM0": 6, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 24, - "D1": 6, - "D2": 26, - "D3": 11, - "D4": 10, - "D5": 1, - "D6": 0, - "D7": 21, - }, - "cbu": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, + "lsc-lma35": { "WIRE2_SCL": 0, "WIRE2_SDA": 1, "SERIAL1_RX": 10, @@ -426,8 +599,6 @@ BK72XX_BOARD_PINS = { "SERIAL2_RX": 1, "SERIAL2_TX": 0, "ADC3": 23, - "CS": 15, - "MISO": 17, "MOSI": 16, "P0": 0, "P1": 1, @@ -438,16 +609,12 @@ BK72XX_BOARD_PINS = { "P10": 10, "P11": 11, "P14": 14, - "P15": 15, "P16": 16, - "P17": 17, - "P20": 20, "P21": 21, "P22": 22, "P23": 23, "P24": 24, "P26": 26, - "P28": 28, "PWM0": 6, "PWM1": 7, "PWM2": 8, @@ -457,167 +624,26 @@ BK72XX_BOARD_PINS = { "RX1": 10, "RX2": 1, "SCK": 14, - "SCL1": 20, "SCL2": 0, "SDA1": 21, "SDA2": 1, "TX1": 11, "TX2": 0, - "D0": 14, - "D1": 16, - "D2": 20, - "D3": 22, - "D4": 23, - "D5": 1, - "D6": 0, + "D0": 26, + "D1": 14, + "D2": 16, + "D3": 24, + "D4": 22, + "D5": 0, + "D6": 23, "D7": 8, - "D8": 7, - "D9": 6, - "D10": 26, - "D11": 24, - "D12": 11, - "D13": 10, - "D14": 28, - "D15": 9, - "D16": 17, - "D17": 15, - "D18": 21, - "A0": 23, - }, - "generic-bk7231n-qfn32-tuya": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "ADC3": 23, - "CS": 15, - "MISO": 17, - "MOSI": 16, - "P0": 0, - "P1": 1, - "P6": 6, - "P7": 7, - "P8": 8, - "P9": 9, - "P10": 10, - "P11": 11, - "P14": 14, - "P15": 15, - "P16": 16, - "P17": 17, - "P20": 20, - "P21": 21, - "P22": 22, - "P23": 23, - "P24": 24, - "P26": 26, - "P28": 28, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM3": 9, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCK": 14, - "SCL1": 20, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 0, - "D1": 1, - "D2": 6, - "D3": 7, - "D4": 8, - "D5": 9, - "D6": 10, - "D7": 11, - "D8": 14, - "D9": 15, - "D10": 16, - "D11": 17, - "D12": 20, - "D13": 21, - "D14": 22, - "D15": 23, - "D16": 24, - "D17": 26, - "D18": 28, - "A0": 23, - }, - "generic-bk7231t-qfn32-tuya": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "ADC3": 23, - "CS": 15, - "MISO": 17, - "MOSI": 16, - "P0": 0, - "P1": 1, - "P6": 6, - "P7": 7, - "P8": 8, - "P9": 9, - "P10": 10, - "P11": 11, - "P14": 14, - "P15": 15, - "P16": 16, - "P17": 17, - "P20": 20, - "P21": 21, - "P22": 22, - "P23": 23, - "P24": 24, - "P26": 26, - "P28": 28, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM3": 9, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCK": 14, - "SCL1": 20, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 0, - "D1": 1, - "D2": 6, - "D3": 7, - "D4": 8, - "D5": 9, - "D6": 10, - "D7": 11, - "D8": 14, - "D9": 15, - "D10": 16, - "D11": 17, - "D12": 20, - "D13": 21, - "D14": 22, - "D15": 23, - "D16": 24, - "D17": 26, - "D18": 28, + "D8": 9, + "D9": 21, + "D10": 6, + "D11": 7, + "D12": 10, + "D13": 11, + "D14": 1, "A0": 23, }, "generic-bk7252": { @@ -740,6 +766,280 @@ BK72XX_BOARD_PINS = { "A6": 12, "A7": 13, }, + "wb2l": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 26, + "D4": 24, + "D5": 10, + "D6": 11, + "D7": 1, + "D8": 0, + "D9": 20, + "D10": 21, + "D11": 23, + "D12": 22, + "A0": 23, + }, + "wb1s": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL2": 0, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 11, + "D1": 10, + "D2": 26, + "D3": 24, + "D4": 0, + "D5": 8, + "D6": 7, + "D7": 1, + "D8": 9, + "D9": 6, + "D10": 23, + "A0": 23, + }, + "wblc5": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P10": 10, + "P11": 11, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 24, + "D1": 6, + "D2": 26, + "D3": 10, + "D4": 11, + "D5": 1, + "D6": 0, + "D7": 20, + "D8": 21, + "D9": 22, + "D10": 23, + "A0": 23, + }, + "cb2s": { + "WIRE2_SCL": 0, + "WIRE2_SDA": 1, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_RX": 1, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P1": 1, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P21": 21, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "RX2": 1, + "SCL2": 0, + "SDA1": 21, + "SDA2": 1, + "TX1": 11, + "TX2": 0, + "D0": 6, + "D1": 7, + "D2": 8, + "D3": 23, + "D4": 10, + "D5": 11, + "D6": 24, + "D7": 26, + "D8": 0, + "D9": 1, + "D10": 21, + "A0": 23, + }, + "wa2": { + "WIRE1_SCL": 20, + "WIRE1_SDA": 21, + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_TX": 0, + "ADC1": 4, + "ADC3": 23, + "P0": 0, + "P4": 4, + "P6": 6, + "P7": 7, + "P8": 8, + "P10": 10, + "P11": 11, + "P18": 18, + "P19": 19, + "P20": 20, + "P21": 21, + "P22": 22, + "P23": 23, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM4": 18, + "PWM5": 19, + "RX1": 10, + "SCL1": 20, + "SCL2": 0, + "SDA1": 21, + "TX1": 11, + "TX2": 0, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 23, + "D4": 10, + "D5": 11, + "D6": 18, + "D7": 19, + "D8": 20, + "D9": 4, + "D10": 0, + "D11": 21, + "D12": 22, + "A0": 23, + }, + "cb3l": { + "SERIAL1_RX": 10, + "SERIAL1_TX": 11, + "SERIAL2_TX": 0, + "ADC3": 23, + "P0": 0, + "P6": 6, + "P7": 7, + "P8": 8, + "P9": 9, + "P10": 10, + "P11": 11, + "P14": 14, + "P21": 21, + "P23": 23, + "P24": 24, + "P26": 26, + "PWM0": 6, + "PWM1": 7, + "PWM2": 8, + "PWM3": 9, + "PWM4": 24, + "PWM5": 26, + "RX1": 10, + "SCK": 14, + "SCL2": 0, + "SDA1": 21, + "TX1": 11, + "TX2": 0, + "D0": 23, + "D1": 14, + "D2": 26, + "D3": 24, + "D4": 6, + "D5": 9, + "D6": 0, + "D7": 21, + "D8": 8, + "D9": 7, + "D10": 10, + "D11": 11, + "A0": 23, + }, "lsc-lma35-t": { "WIRE2_SCL": 0, "WIRE2_SDA": 1, @@ -795,7 +1095,7 @@ BK72XX_BOARD_PINS = { "D14": 1, "A0": 23, }, - "lsc-lma35": { + "cb3se": { "WIRE2_SCL": 0, "WIRE2_SDA": 1, "SERIAL1_RX": 10, @@ -803,6 +1103,8 @@ BK72XX_BOARD_PINS = { "SERIAL2_RX": 1, "SERIAL2_TX": 0, "ADC3": 23, + "CS": 15, + "MISO": 17, "MOSI": 16, "P0": 0, "P1": 1, @@ -813,8 +1115,10 @@ BK72XX_BOARD_PINS = { "P10": 10, "P11": 11, "P14": 14, + "P15": 15, "P16": 16, - "P21": 21, + "P17": 17, + "P20": 20, "P22": 22, "P23": 23, "P24": 24, @@ -828,273 +1132,28 @@ BK72XX_BOARD_PINS = { "RX1": 10, "RX2": 1, "SCK": 14, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 26, - "D1": 14, - "D2": 16, - "D3": 24, - "D4": 22, - "D5": 0, - "D6": 23, - "D7": 8, - "D8": 9, - "D9": 21, - "D10": 6, - "D11": 7, - "D12": 10, - "D13": 11, - "D14": 1, - "A0": 23, - }, - "wa2": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_TX": 0, - "ADC1": 4, - "ADC3": 23, - "P0": 0, - "P4": 4, - "P6": 6, - "P7": 7, - "P8": 8, - "P10": 10, - "P11": 11, - "P18": 18, - "P19": 19, - "P20": 20, - "P21": 21, - "P22": 22, - "P23": 23, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM4": 18, - "PWM5": 19, - "RX1": 10, "SCL1": 20, "SCL2": 0, - "SDA1": 21, - "TX1": 11, - "TX2": 0, - "D0": 8, - "D1": 7, - "D2": 6, - "D3": 23, - "D4": 10, - "D5": 11, - "D6": 18, - "D7": 19, - "D8": 20, - "D9": 4, - "D10": 0, - "D11": 21, - "D12": 22, - "A0": 23, - }, - "wb1s": { - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "ADC3": 23, - "P0": 0, - "P1": 1, - "P6": 6, - "P7": 7, - "P8": 8, - "P9": 9, - "P10": 10, - "P11": 11, - "P23": 23, - "P24": 24, - "P26": 26, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM3": 9, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCL2": 0, "SDA2": 1, "TX1": 11, "TX2": 0, - "D0": 11, - "D1": 10, + "D0": 23, + "D1": 14, "D2": 26, "D3": 24, - "D4": 0, - "D5": 8, - "D6": 7, + "D4": 6, + "D5": 9, + "D6": 0, "D7": 1, - "D8": 9, - "D9": 6, - "D10": 23, - "A0": 23, - }, - "wb2l-m1": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "ADC3": 23, - "P0": 0, - "P1": 1, - "P6": 6, - "P7": 7, - "P8": 8, - "P10": 10, - "P11": 11, - "P20": 20, - "P21": 21, - "P22": 22, - "P23": 23, - "P24": 24, - "P26": 26, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCL1": 20, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 8, - "D1": 7, - "D2": 6, - "D3": 26, - "D4": 24, - "D5": 10, - "D6": 11, - "D7": 1, - "D8": 0, - "D9": 20, - "D10": 21, - "D11": 23, - "D12": 22, - "A0": 23, - }, - "wb2l": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "ADC3": 23, - "P0": 0, - "P1": 1, - "P6": 6, - "P7": 7, - "P8": 8, - "P10": 10, - "P11": 11, - "P20": 20, - "P21": 21, - "P22": 22, - "P23": 23, - "P24": 24, - "P26": 26, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCL1": 20, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 8, - "D1": 7, - "D2": 6, - "D3": 26, - "D4": 24, - "D5": 10, - "D6": 11, - "D7": 1, - "D8": 0, - "D9": 20, - "D10": 21, - "D11": 23, - "D12": 22, - "A0": 23, - }, - "wb2s": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "ADC3": 23, - "P0": 0, - "P1": 1, - "P6": 6, - "P7": 7, - "P8": 8, - "P9": 9, - "P10": 10, - "P11": 11, - "P20": 20, - "P21": 21, - "P22": 22, - "P23": 23, - "P24": 24, - "P26": 26, - "PWM0": 6, - "PWM1": 7, - "PWM2": 8, - "PWM3": 9, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCL1": 20, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 8, - "D1": 7, - "D2": 6, - "D3": 23, - "D4": 10, - "D5": 11, - "D6": 24, - "D7": 26, - "D8": 20, - "D9": 9, - "D10": 1, - "D11": 0, - "D12": 21, + "D8": 8, + "D9": 7, + "D10": 10, + "D11": 11, + "D12": 15, "D13": 22, + "D14": 20, + "D15": 17, + "D16": 16, "A0": 23, }, "wb3l": { @@ -1157,7 +1216,7 @@ BK72XX_BOARD_PINS = { "D15": 1, "A0": 23, }, - "wb3s": { + "wb2s": { "WIRE1_SCL": 20, "WIRE1_SDA": 21, "WIRE2_SCL": 0, @@ -1175,7 +1234,6 @@ BK72XX_BOARD_PINS = { "P9": 9, "P10": 10, "P11": 11, - "P14": 14, "P20": 20, "P21": 21, "P22": 22, @@ -1190,73 +1248,26 @@ BK72XX_BOARD_PINS = { "PWM5": 26, "RX1": 10, "RX2": 1, - "SCK": 14, "SCL1": 20, "SCL2": 0, "SDA1": 21, "SDA2": 1, "TX1": 11, "TX2": 0, - "D0": 23, - "D1": 14, - "D2": 26, - "D3": 24, - "D4": 6, - "D5": 7, - "D6": 0, - "D7": 1, - "D8": 9, - "D9": 8, - "D10": 10, - "D11": 11, - "D12": 22, - "D13": 21, - "D14": 20, - "A0": 23, - }, - "wblc5": { - "WIRE1_SCL": 20, - "WIRE1_SDA": 21, - "WIRE2_SCL": 0, - "WIRE2_SDA": 1, - "SERIAL1_RX": 10, - "SERIAL1_TX": 11, - "SERIAL2_RX": 1, - "SERIAL2_TX": 0, - "ADC3": 23, - "P0": 0, - "P1": 1, - "P6": 6, - "P10": 10, - "P11": 11, - "P20": 20, - "P21": 21, - "P22": 22, - "P23": 23, - "P24": 24, - "P26": 26, - "PWM0": 6, - "PWM4": 24, - "PWM5": 26, - "RX1": 10, - "RX2": 1, - "SCL1": 20, - "SCL2": 0, - "SDA1": 21, - "SDA2": 1, - "TX1": 11, - "TX2": 0, - "D0": 24, - "D1": 6, - "D2": 26, - "D3": 10, - "D4": 11, - "D5": 1, - "D6": 0, - "D7": 20, - "D8": 21, - "D9": 22, - "D10": 23, + "D0": 8, + "D1": 7, + "D2": 6, + "D3": 23, + "D4": 10, + "D5": 11, + "D6": 24, + "D7": 26, + "D8": 20, + "D9": 9, + "D10": 1, + "D11": 0, + "D12": 21, + "D13": 22, "A0": 23, }, } diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index ccda894509..01590d1d53 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -7,8 +7,12 @@ #include "esphome/core/automation.h" #include "esphome/components/ble_client/ble_client.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" +// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars) +static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64; + namespace esphome::ble_client { // placeholder class for static TAG . @@ -151,7 +155,10 @@ template class BLEClientWriteAction : public Action, publ esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected"); return false; } - esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)]; + esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len)); +#endif esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len, const_cast(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE); @@ -179,8 +186,10 @@ template class BLEClientWriteAction : public Action, publ case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { + char char_buf[esp32_ble::UUID_STR_LEN]; + char service_buf[esp32_ble::UUID_STR_LEN]; esph_log_w("ble_write_action", "Characteristic %s was not found in service %s", - this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str()); + this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf)); break; } this->char_handle_ = chr->handle; @@ -192,11 +201,13 @@ template class BLEClientWriteAction : public Action, publ this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); } else { - esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf)); break; } this->node_state = espbt::ClientState::ESTABLISHED; - esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf), ble_client_->address_str()); break; } diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index 1d874a65e4..1cb83b9d8b 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -9,12 +9,15 @@ static const char *const TAG = "ble_binary_output"; void BLEBinaryOutput::dump_config() { ESP_LOGCONFIG(TAG, "BLE Binary Output:"); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + this->service_uuid_.to_str(service_buf); + this->char_uuid_.to_str(char_buf); ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s", - this->parent_->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + this->parent_->address_str(), service_buf, char_buf); LOG_BINARY_OUTPUT(this); } @@ -24,8 +27,10 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SEARCH_CMPL_EVT: { auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(), - this->service_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + char service_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_str(char_buf), + this->service_uuid_.to_str(service_buf)); break; } this->char_handle_ = chr->handle; @@ -37,20 +42,24 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP; ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP"); } else { - ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_str(char_buf), this->require_response_ ? "" : "out"); break; } this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(), + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf), this->parent()->address_str()); this->node_state = espbt::ClientState::ESTABLISHED; break; } case ESP_GATTC_WRITE_CHAR_EVT: { if (param->write.handle == this->char_handle_) { - if (param->write.status != 0) - ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); + if (param->write.status != 0) { + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_str(char_buf), param->write.status); + } } break; } @@ -60,18 +69,19 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i } void BLEBinaryOutput::write_state(bool state) { + char char_buf[esp32_ble::UUID_STR_LEN]; if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.", - this->char_uuid_.to_string().c_str()); + this->char_uuid_.to_str(char_buf)); return; } uint8_t state_as_uint = (uint8_t) state; - ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); + ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_str(char_buf), state_as_uint); esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE); if (err != ESP_GATT_OK) - ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err); + ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_str(char_buf), err); } } // namespace esphome::ble_client diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 38d90faff0..fe5f11bbc2 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -18,14 +18,17 @@ void BLESensor::loop() { void BLESensor::dump_config() { LOG_SENSOR("", "BLE Sensor", this); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); + this->parent()->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } @@ -51,8 +54,10 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (chr == nullptr) { this->status_set_warning(); this->publish_state(NAN); - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf)); break; } this->handle = chr->handle; @@ -61,9 +66,12 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga if (descr == nullptr) { this->status_set_warning(); this->publish_state(NAN); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", - this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), - this->descr_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf), + this->descr_uuid_.to_str(descr_buf)); break; } this->handle = descr->handle; @@ -109,7 +117,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } this->node_state = espbt::ClientState::ESTABLISHED; - ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str()); + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_str(char_buf)); } break; } diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h index f7b077926b..d4114cd1ba 100644 --- a/esphome/components/ble_client/text_sensor/automation.h +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -21,7 +21,7 @@ class BLETextSensorNotifyTrigger : public Trigger, public BLETextSe if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() || param->notify.handle != this->sensor_->handle) break; - this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); + this->trigger(std::string(reinterpret_cast(param->notify.value), param->notify.value_len)); } default: break; diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index 415981a1ba..cacf1b4835 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -11,8 +11,6 @@ namespace esphome::ble_client { static const char *const TAG = "ble_text_sensor"; -static const std::string EMPTY = ""; - void BLETextSensor::loop() { // Parent BLEClientNode has a loop() method, but this component uses // polling via update() and BLE callbacks so loop isn't needed @@ -21,14 +19,17 @@ void BLETextSensor::loop() { void BLETextSensor::dump_config() { LOG_TEXT_SENSOR("", "BLE Text Sensor", this); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID: %s\n" " Descriptor UUID : %s\n" " Notifications : %s", - this->parent()->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_)); + this->parent()->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_)); LOG_UPDATE_INTERVAL(this); } @@ -44,7 +45,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_CLOSE_EVT: { this->status_set_warning(); - this->publish_state(EMPTY); + this->publish_state(""); break; } case ESP_GATTC_SEARCH_CMPL_EVT: { @@ -52,9 +53,11 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { this->status_set_warning(); - this->publish_state(EMPTY); - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str()); + this->publish_state(""); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf)); break; } this->handle = chr->handle; @@ -62,10 +65,13 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ auto *descr = chr->get_descriptor(this->descr_uuid_); if (descr == nullptr) { this->status_set_warning(); - this->publish_state(EMPTY); + this->publish_state(""); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + char descr_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", - this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), - this->descr_uuid_.to_string().c_str()); + this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf), + this->descr_uuid_.to_str(descr_buf)); break; } this->handle = descr->handle; @@ -91,7 +97,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } this->status_clear_warning(); - this->publish_state(this->parse_data(param->read.value, param->read.value_len)); + this->publish_state(reinterpret_cast(param->read.value), param->read.value_len); } break; } @@ -100,7 +106,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); - this->publish_state(this->parse_data(param->notify.value, param->notify.value_len)); + this->publish_state(reinterpret_cast(param->notify.value), param->notify.value_len); break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { @@ -113,11 +119,6 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } } -std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) { - std::string text(value, value + value_len); - return text; -} - void BLETextSensor::update() { if (this->node_state != espbt::ClientState::ESTABLISHED) { ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); @@ -132,7 +133,7 @@ void BLETextSensor::update() { ESP_GATT_AUTH_REQ_NONE); if (status) { this->status_set_warning(); - this->publish_state(EMPTY); + this->publish_state(""); ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status); } } diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.h b/esphome/components/ble_client/text_sensor/ble_text_sensor.h index 3fbd64389c..b4374e4016 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.h +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.h @@ -29,7 +29,6 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_enable_notify(bool notify) { this->notify_ = notify; } - std::string parse_data(uint8_t *value, uint16_t value_len); uint16_t handle; protected: diff --git a/esphome/components/ble_nus/__init__.py b/esphome/components/ble_nus/__init__.py index 9570005902..6581ce1cfa 100644 --- a/esphome/components/ble_nus/__init__.py +++ b/esphome/components/ble_nus/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg +from esphome.components.logger import request_log_listener from esphome.components.zephyr import zephyr_add_prj_conf import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE @@ -25,5 +26,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) zephyr_add_prj_conf("BT_NUS", True) - cg.add(var.set_expose_log(config[CONF_TYPE] == CONF_LOGS)) + expose_log = config[CONF_TYPE] == CONF_LOGS + cg.add(var.set_expose_log(expose_log)) + if expose_log: + request_log_listener() # Request a log listener slot for BLE NUS log streaming await cg.register_component(var, config) diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index bd80592d89..0de65b623f 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -103,8 +103,10 @@ void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t #endif void BLENUS::dump_config() { - ESP_LOGCONFIG(TAG, "ble nus:"); - ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_)); + ESP_LOGCONFIG(TAG, + "ble nus:\n" + " log: %s", + YESNO(this->expose_log_)); uint32_t mtu = 0; bt_conn *conn = this->conn_.load(); if (conn) { diff --git a/esphome/components/ble_scanner/ble_scanner.h b/esphome/components/ble_scanner/ble_scanner.h index 8bb51fcff2..7061b6d336 100644 --- a/esphome/components/ble_scanner/ble_scanner.h +++ b/esphome/components/ble_scanner/ble_scanner.h @@ -1,7 +1,8 @@ #pragma once +#include +#include #include -#include #include "esphome/core/component.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" @@ -15,17 +16,13 @@ namespace ble_scanner { class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { public: bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { - this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) + - "," - "\"address\":\"" + - device.address_str() + - "\"," - "\"rssi\":" + - to_string(device.get_rssi()) + - "," - "\"name\":\"" + - device.get_name() + "\"}"); - + // Format JSON using stack buffer to avoid heap allocations from string concatenation + char buf[128]; + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + snprintf(buf, sizeof(buf), "{\"timestamp\":%" PRId64 ",\"address\":\"%s\",\"rssi\":%d,\"name\":\"%s\"}", + static_cast(::time(nullptr)), device.address_str_to(addr_buf), device.get_rssi(), + device.get_name().c_str()); + this->publish_state(buf); return true; } void dump_config() override; diff --git a/esphome/components/bme68x_bsec2/sensor.py b/esphome/components/bme68x_bsec2/sensor.py index c7dca437d7..f21a9b8138 100644 --- a/esphome/components/bme68x_bsec2/sensor.py +++ b/esphome/components/bme68x_bsec2/sensor.py @@ -50,6 +50,7 @@ TYPES = [ CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp index 50eaf33add..2d74ba6b12 100644 --- a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp +++ b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp @@ -31,7 +31,11 @@ void BME68xBSEC2I2CComponent::dump_config() { BME68xBSEC2Component::dump_config(); } -uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); } +uint32_t BME68xBSEC2I2CComponent::get_hash() { + char buf[22]; // "bme68x_bsec_state_" (18) + uint8_t max (3) + null + snprintf(buf, sizeof(buf), "bme68x_bsec_state_%u", this->address_); + return fnv1_hash(buf); +} int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) { ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register); diff --git a/esphome/components/bp1658cj/bp1658cj.cpp b/esphome/components/bp1658cj/bp1658cj.cpp index b8ad5dc3d2..d5516384ff 100644 --- a/esphome/components/bp1658cj/bp1658cj.cpp +++ b/esphome/components/bp1658cj/bp1658cj.cpp @@ -22,13 +22,13 @@ void BP1658CJ::setup() { this->pwm_amounts_.resize(5, 0); } void BP1658CJ::dump_config() { - ESP_LOGCONFIG(TAG, "BP1658CJ:"); - LOG_PIN(" Data Pin: ", this->data_pin_); - LOG_PIN(" Clock Pin: ", this->clock_pin_); ESP_LOGCONFIG(TAG, + "BP1658CJ:\n" " Color Channels Max Power: %u\n" " White Channels Max Power: %u", this->max_power_color_channels_, this->max_power_white_channels_); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); } void BP1658CJ::loop() { diff --git a/esphome/components/bthome_mithermometer/__init__.py b/esphome/components/bthome_mithermometer/__init__.py new file mode 100644 index 0000000000..0e84278afa --- /dev/null +++ b/esphome/components/bthome_mithermometer/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +from esphome.components import esp32_ble_tracker +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_MAC_ADDRESS + +CODEOWNERS = ["@nagyrobi"] +DEPENDENCIES = ["esp32_ble_tracker"] + +BLE_DEVICE_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA + +bthome_mithermometer_ns = cg.esphome_ns.namespace("bthome_mithermometer") +BTHomeMiThermometer = bthome_mithermometer_ns.class_( + "BTHomeMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + + +def bthome_mithermometer_base_schema(extra_schema=None): + if extra_schema is None: + extra_schema = {} + return ( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + } + ) + .extend(BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) + .extend(extra_schema) + ) + + +async def setup_bthome_mithermometer(var, config): + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/bthome_mithermometer/bthome_ble.cpp b/esphome/components/bthome_mithermometer/bthome_ble.cpp new file mode 100644 index 0000000000..d1c5165896 --- /dev/null +++ b/esphome/components/bthome_mithermometer/bthome_ble.cpp @@ -0,0 +1,300 @@ +#include "bthome_ble.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include + +#ifdef USE_ESP32 + +namespace esphome { +namespace bthome_mithermometer { + +static const char *const TAG = "bthome_mithermometer"; + +static const char *format_mac_address(std::span buffer, uint64_t address) { + std::array mac{}; + for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) { + mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF; + } + + format_mac_addr_upper(mac.data(), buffer.data()); + return buffer.data(); +} + +static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) { + switch (obj_type) { + case 0x00: // packet id + case 0x01: // battery + case 0x09: // count (uint8) + case 0x0F: // generic boolean + case 0x10: // power (bool) + case 0x11: // opening + case 0x15: // battery low + case 0x16: // battery charging + case 0x17: // carbon monoxide + case 0x18: // cold + case 0x19: // connectivity + case 0x1A: // door + case 0x1B: // garage door + case 0x1C: // gas + case 0x1D: // heat + case 0x1E: // light + case 0x1F: // lock + case 0x20: // moisture + case 0x21: // motion + case 0x22: // moving + case 0x23: // occupancy + case 0x24: // plug + case 0x25: // presence + case 0x26: // problem + case 0x27: // running + case 0x28: // safety + case 0x29: // smoke + case 0x2A: // sound + case 0x2B: // tamper + case 0x2C: // vibration + case 0x2D: // water leak + case 0x2E: // humidity (uint8) + case 0x2F: // moisture (uint8) + case 0x46: // UV index + case 0x57: // temperature (sint8) + case 0x58: // temperature (0.35C step) + case 0x59: // count (sint8) + case 0x60: // channel + value_length = 1; + return true; + case 0x02: // temperature (0.01C) + case 0x03: // humidity + case 0x06: // mass (kg) + case 0x07: // mass (lb) + case 0x08: // dewpoint + case 0x0C: // voltage (mV) + case 0x0D: // pm2.5 + case 0x0E: // pm10 + case 0x12: // CO2 + case 0x13: // TVOC + case 0x14: // moisture + case 0x3D: // count (uint16) + case 0x3F: // rotation + case 0x40: // distance (mm) + case 0x41: // distance (m) + case 0x43: // current (A) + case 0x44: // speed + case 0x45: // temperature (0.1C) + case 0x47: // volume (L) + case 0x48: // volume (mL) + case 0x49: // volume flow rate + case 0x4A: // voltage (0.1V) + case 0x51: // acceleration + case 0x52: // gyroscope + case 0x56: // conductivity + case 0x5A: // count (sint16) + case 0x5D: // current (sint16) + case 0x5E: // direction + case 0x5F: // precipitation + case 0x61: // rotational speed + case 0xF0: // button event + value_length = 2; + return true; + case 0x04: // pressure + case 0x05: // illuminance + case 0x0A: // energy + case 0x0B: // power + case 0x42: // duration + case 0x4B: // gas (uint24) + case 0xF2: // firmware version (uint24) + value_length = 3; + return true; + case 0x3E: // count (uint32) + case 0x4C: // gas (uint32) + case 0x4D: // energy (uint32) + case 0x4E: // volume (uint32) + case 0x4F: // water (uint32) + case 0x50: // timestamp + case 0x55: // volume storage + case 0x5B: // count (sint32) + case 0x5C: // power (sint32) + case 0x62: // speed (sint32) + case 0x63: // acceleration (sint32) + case 0xF1: // firmware version (uint32) + value_length = 4; + return true; + default: + return false; + } +} + +void BTHomeMiThermometer::dump_config() { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGCONFIG(TAG, "BTHome MiThermometer"); + ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(addr_buf, this->address_)); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); + LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_); + LOG_SENSOR(" ", "Signal Strength", this->signal_strength_); +} + +bool BTHomeMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + bool matched = false; + for (auto &service_data : device.get_service_datas()) { + if (this->handle_service_data_(service_data, device)) { + matched = true; + } + } + if (matched && this->signal_strength_ != nullptr) { + this->signal_strength_->publish_state(device.get_rssi()); + } + return matched; +} + +bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceData &service_data, + const esp32_ble_tracker::ESPBTDevice &device) { + if (!service_data.uuid.contains(0xD2, 0xFC)) { + return false; + } + + const auto &data = service_data.data; + if (data.size() < 2) { + ESP_LOGVV(TAG, "BTHome data too short: %zu", data.size()); + return false; + } + + const uint8_t adv_info = data[0]; + const bool is_encrypted = adv_info & 0x01; + const bool mac_included = adv_info & 0x02; + const bool is_trigger_based = adv_info & 0x04; + const uint8_t version = (adv_info >> 5) & 0x07; + + if (version != 0x02) { + ESP_LOGVV(TAG, "Unsupported BTHome version %u", version); + return false; + } + + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + if (is_encrypted) { + ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str_to(addr_buf)); + return false; + } + + size_t payload_index = 1; + uint64_t source_address = device.address_uint64(); + + if (mac_included) { + if (data.size() < 7) { + ESP_LOGVV(TAG, "BTHome payload missing MAC address"); + return false; + } + source_address = 0; + for (int i = 5; i >= 0; i--) { + source_address = (source_address << 8) | data[1 + i]; + } + payload_index = 7; + } + + if (source_address != this->address_) { + ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(addr_buf, source_address)); + return false; + } + + if (payload_index >= data.size()) { + ESP_LOGVV(TAG, "BTHome payload empty after header"); + return false; + } + + bool reported = false; + size_t offset = payload_index; + uint8_t last_type = 0; + + while (offset < data.size()) { + const uint8_t obj_type = data[offset++]; + size_t value_length = 0; + bool has_length_byte = obj_type == 0x53; // text objects include explicit length + + if (has_length_byte) { + if (offset >= data.size()) { + break; + } + value_length = data[offset++]; + } else { + if (!get_bthome_value_length(obj_type, value_length)) { + ESP_LOGVV(TAG, "Unknown BTHome object 0x%02X", obj_type); + break; + } + } + + if (value_length == 0) { + break; + } + + if (offset + value_length > data.size()) { + ESP_LOGVV(TAG, "BTHome object length exceeds payload"); + break; + } + + const uint8_t *value = &data[offset]; + offset += value_length; + + if (obj_type < last_type) { + ESP_LOGVV(TAG, "BTHome objects not in ascending order"); + } + last_type = obj_type; + + switch (obj_type) { + case 0x00: { // packet id + const uint8_t packet_id = value[0]; + if (this->last_packet_id_.has_value() && *this->last_packet_id_ == packet_id) { + return reported; + } + this->last_packet_id_ = packet_id; + break; + } + case 0x01: { // battery percentage + if (this->battery_level_ != nullptr) { + this->battery_level_->publish_state(value[0]); + reported = true; + } + break; + } + case 0x0C: { // battery voltage (mV) + if (this->battery_voltage_ != nullptr) { + const uint16_t raw = encode_uint16(value[1], value[0]); + this->battery_voltage_->publish_state(raw * 0.001f); + reported = true; + } + break; + } + case 0x02: { // temperature + if (this->temperature_ != nullptr) { + const int16_t raw = encode_uint16(value[1], value[0]); + this->temperature_->publish_state(raw * 0.01f); + reported = true; + } + break; + } + case 0x03: { // humidity + if (this->humidity_ != nullptr) { + const uint16_t raw = encode_uint16(value[1], value[0]); + this->humidity_->publish_state(raw * 0.01f); + reported = true; + } + break; + } + default: + break; + } + } + + if (reported) { + ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str_to(addr_buf)); + } + + return reported; +} + +} // namespace bthome_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/bthome_mithermometer/bthome_ble.h b/esphome/components/bthome_mithermometer/bthome_ble.h new file mode 100644 index 0000000000..3d2380b48d --- /dev/null +++ b/esphome/components/bthome_mithermometer/bthome_ble.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +#include + +#ifdef USE_ESP32 + +namespace esphome { +namespace bthome_mithermometer { + +class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component { + public: + void set_address(uint64_t address) { this->address_ = address; } + + void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; } + void set_battery_voltage(sensor::Sensor *battery_voltage) { this->battery_voltage_ = battery_voltage; } + void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; } + + void dump_config() override; + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + protected: + bool handle_service_data_(const esp32_ble_tracker::ServiceData &service_data, + const esp32_ble_tracker::ESPBTDevice &device); + + uint64_t address_{0}; + optional last_packet_id_{}; + + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; + sensor::Sensor *battery_voltage_{nullptr}; + sensor::Sensor *signal_strength_{nullptr}; +}; + +} // namespace bthome_mithermometer +} // namespace esphome + +#endif diff --git a/esphome/components/bthome_mithermometer/sensor.py b/esphome/components/bthome_mithermometer/sensor.py new file mode 100644 index 0000000000..9b50866db0 --- /dev/null +++ b/esphome/components/bthome_mithermometer/sensor.py @@ -0,0 +1,88 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_BATTERY_VOLTAGE, + CONF_HUMIDITY, + CONF_ID, + CONF_SIGNAL_STRENGTH, + CONF_TEMPERATURE, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_DECIBEL_MILLIWATT, + UNIT_PERCENT, + UNIT_VOLT, +) + +from . import bthome_mithermometer_base_schema, setup_bthome_mithermometer + +CODEOWNERS = ["@nagyrobi"] + +DEPENDENCIES = ["esp32_ble_tracker"] + +CONFIG_SCHEMA = bthome_mithermometer_base_schema( + { + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + icon="mdi:battery-plus", + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema( + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await setup_bthome_mithermometer(var, config) + + if temp_sens := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temp_sens) + cg.add(var.set_temperature(sens)) + if humi_sens := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humi_sens) + cg.add(var.set_humidity(sens)) + if batl_sens := config.get(CONF_BATTERY_LEVEL): + sens = await sensor.new_sensor(batl_sens) + cg.add(var.set_battery_level(sens)) + if batv_sens := config.get(CONF_BATTERY_VOLTAGE): + sens = await sensor.new_sensor(batv_sens) + cg.add(var.set_battery_voltage(sens)) + if sgnl_sens := config.get(CONF_SIGNAL_STRENGTH): + sens = await sensor.new_sensor(sgnl_sens) + cg.add(var.set_signal_strength(sens)) diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 18122f6f2f..be6e080917 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -41,7 +41,7 @@ class Button : public EntityBase, public EntityBase_DeviceClass { */ virtual void press_action() = 0; - CallbackManager press_callback_{}; + LazyCallbackManager press_callback_{}; }; } // namespace esphome::button diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp index 683e5cf487..9e8c87d147 100644 --- a/esphome/components/cap1188/cap1188.cpp +++ b/esphome/components/cap1188/cap1188.cpp @@ -63,14 +63,14 @@ void CAP1188Component::finish_setup_() { } void CAP1188Component::dump_config() { - ESP_LOGCONFIG(TAG, "CAP1188:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + "CAP1188:\n" " Product ID: 0x%x\n" " Manufacture ID: 0x%x\n" " Revision ID: 0x%x", this->cap1188_product_id_, this->cap1188_manufacture_id_, this->cap1188_revision_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); switch (this->error_code_) { case COMMUNICATION_FAILED: diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 25d0a22083..4b30dc5d16 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -7,6 +7,7 @@ from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_AP, + CONF_COMPRESSION, CONF_ID, PLATFORM_BK72XX, PLATFORM_ESP32, @@ -25,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) def AUTO_LOAD() -> list[str]: auto_load = ["web_server_base", "ota.web_server"] - if CORE.using_esp_idf: + if CORE.is_esp32: auto_load.append("socket") return auto_load @@ -43,6 +44,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"), } ).extend(cv.COMPONENT_SCHEMA), cv.only_on( @@ -96,11 +98,10 @@ async def to_code(config): await cg.register_component(var, config) cg.add_define("USE_CAPTIVE_PORTAL") + if config[CONF_COMPRESSION] == "gzip": + cg.add_define("USE_CAPTIVE_PORTAL_GZIP") + if CORE.using_arduino: - if CORE.is_esp32: - cg.add_library("ESP32 Async UDP", None) - cg.add_library("DNSServer", None) - cg.add_library("WiFi", None) if CORE.is_esp8266: cg.add_library("DNSServer", None) if CORE.is_libretiny: @@ -110,6 +111,9 @@ async def to_code(config): # Only compile the ESP-IDF DNS server when using ESP-IDF framework FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF}, + "dns_server_esp32_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, } ) diff --git a/esphome/components/captive_portal/captive_index.h b/esphome/components/captive_portal/captive_index.h index 3122f27558..645ebb7a2f 100644 --- a/esphome/components/captive_portal/captive_index.h +++ b/esphome/components/captive_portal/captive_index.h @@ -3,9 +3,9 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace captive_portal { +namespace esphome::captive_portal { +#ifdef USE_CAPTIVE_PORTAL_GZIP const uint8_t INDEX_GZ[] PROGMEM = { 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, @@ -85,5 +85,71 @@ const uint8_t INDEX_GZ[] PROGMEM = { 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}; -} // namespace captive_portal -} // namespace esphome +#else // Brotli (default, smaller) +const uint8_t INDEX_BR[] PROGMEM = { + 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 + +#endif // USE_CAPTIVE_PORTAL_GZIP + +} // namespace esphome::captive_portal diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index e1f92d2d2b..bf65ae67c0 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -49,11 +49,18 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) { void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { std::string ssid = request->arg("ssid").c_str(); // NOLINT(readability-redundant-string-cstr) std::string psk = request->arg("psk").c_str(); // NOLINT(readability-redundant-string-cstr) - ESP_LOGI(TAG, "Requested WiFi Settings Change:"); - ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); - ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); + ESP_LOGI(TAG, + "Requested WiFi Settings Change:\n" + " SSID='%s'\n" + " Password=" LOG_SECRET("'%s'"), + ssid.c_str(), psk.c_str()); +#ifdef USE_ESP8266 + // ESP8266 is single-threaded, call directly + wifi::global_wifi_component->save_wifi_sta(ssid, psk); +#else // Defer save to main loop thread to avoid NVS operations from HTTP thread this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); }); +#endif request->redirect(ESPHOME_F("/?save")); } @@ -69,12 +76,11 @@ void CaptivePortal::start() { network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) // Create DNS server instance for ESP-IDF this->dns_server_ = make_unique(); this->dns_server_->start(ip); -#endif -#ifdef USE_ARDUINO +#elif defined(USE_ARDUINO) this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->start(53, ESPHOME_F("*"), ip); @@ -106,7 +112,11 @@ void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { #else auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); #endif +#ifdef USE_CAPTIVE_PORTAL_GZIP response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); +#else + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("br")); +#endif req->send(response); } diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index f48c286f0c..0c63a3670a 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -2,11 +2,10 @@ #include "esphome/core/defines.h" #ifdef USE_CAPTIVE_PORTAL #include -#ifdef USE_ARDUINO -#include -#endif -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) #include "dns_server_esp32_idf.h" +#elif defined(USE_ARDUINO) +#include #endif #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -23,15 +22,14 @@ class CaptivePortal : public AsyncWebHandler, public Component { void setup() override; void dump_config() override; void loop() override { -#ifdef USE_ARDUINO - if (this->dns_server_ != nullptr) { - this->dns_server_->processNextRequest(); - } -#endif -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) if (this->dns_server_ != nullptr) { this->dns_server_->process_next_request(); } +#elif defined(USE_ARDUINO) + if (this->dns_server_ != nullptr) { + this->dns_server_->processNextRequest(); + } #endif } float get_setup_priority() const override; @@ -64,7 +62,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { web_server_base::WebServerBase *base_; bool initialized_{false}; bool active_{false}; -#if defined(USE_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ARDUINO) || defined(USE_ESP32) std::unique_ptr dns_server_{nullptr}; #endif }; diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.cpp b/esphome/components/captive_portal/dns_server_esp32_idf.cpp index 740107400a..5743cbd671 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.cpp +++ b/esphome/components/captive_portal/dns_server_esp32_idf.cpp @@ -1,5 +1,5 @@ #include "dns_server_esp32_idf.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -47,7 +47,10 @@ struct DNSAnswer { void DNSServer::start(const network::IPAddress &ip) { this->server_ip_ = ip; - ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf)); +#endif // Create loop-monitored UDP socket this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP); @@ -202,4 +205,4 @@ void DNSServer::process_next_request() { } // namespace esphome::captive_portal -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.h b/esphome/components/captive_portal/dns_server_esp32_idf.h index 13d9def8e3..3e0ac07373 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.h +++ b/esphome/components/captive_portal/dns_server_esp32_idf.h @@ -1,5 +1,5 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include "esphome/core/helpers.h" @@ -24,4 +24,4 @@ class DNSServer { } // namespace esphome::captive_portal -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index c205ff2f69..fbdd7010b4 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -264,6 +264,7 @@ async def to_code(config): var.get_packet_trigger(), [ (cg.std_vector.template(cg.uint8), "x"), + (cg.float_, "freq_offset"), (cg.float_, "rssi"), (cg.uint8, "lqi"), ], diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index fe9238b141..c4507a54e5 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -190,13 +190,15 @@ void CC1101Component::loop() { this->read_(Register::FIFO, this->packet_.data(), payload_length); // Read status from registers (more reliable than FIFO status bytes due to timing issues) + this->read_(Register::FREQEST); this->read_(Register::RSSI); this->read_(Register::LQI); + float freq_offset = static_cast(this->state_.FREQEST) * (XTAL_FREQUENCY / (1 << 14)); float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET; bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0; uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK; if (this->state_.CRC_EN == 0 || crc_ok) { - this->packet_trigger_->trigger(this->packet_, rssi, lqi); + this->packet_trigger_->trigger(this->packet_, freq_offset, rssi, lqi); } // Return to rx @@ -212,9 +214,8 @@ void CC1101Component::dump_config() { XTAL_FREQUENCY / (1 << 16); float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY; float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E)); - ESP_LOGCONFIG(TAG, "CC1101:"); - LOG_PIN(" CS Pin: ", this->cs_); ESP_LOGCONFIG(TAG, + "CC1101:\n" " Chip ID: 0x%04X\n" " Frequency: %" PRId32 " Hz\n" " Channel: %u\n" @@ -224,6 +225,7 @@ void CC1101Component::dump_config() { " Output Power: %.1f dBm", this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07], symbol_rate, bw, this->output_power_effective_); + LOG_PIN(" CS Pin: ", this->cs_); } void CC1101Component::begin_tx() { diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index fe4898660e..43ae5b3612 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -73,7 +73,7 @@ class CC1101Component : public Component, // Packet mode operations CC1101Error transmit_packet(const std::vector &packet); - Trigger, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } + Trigger, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } protected: uint16_t chip_id_{0}; @@ -89,7 +89,8 @@ class CC1101Component : public Component, InternalGPIOPin *gdo0_pin_{nullptr}; // Packet handling - Trigger, float, uint8_t> *packet_trigger_{new Trigger, float, uint8_t>()}; + Trigger, float, float, uint8_t> *packet_trigger_{ + new Trigger, float, float, uint8_t>()}; std::vector packet_; // Low-level Helpers diff --git a/esphome/components/cd74hc4067/cd74hc4067.cpp b/esphome/components/cd74hc4067/cd74hc4067.cpp index 174dc676f9..4293d7af07 100644 --- a/esphome/components/cd74hc4067/cd74hc4067.cpp +++ b/esphome/components/cd74hc4067/cd74hc4067.cpp @@ -21,12 +21,14 @@ void CD74HC4067Component::setup() { } void CD74HC4067Component::dump_config() { - ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:"); + ESP_LOGCONFIG(TAG, + "CD74HC4067 Multiplexer:\n" + " switch delay: %" PRIu32, + this->switch_delay_); LOG_PIN(" S0 Pin: ", this->pin_s0_); LOG_PIN(" S1 Pin: ", this->pin_s1_); LOG_PIN(" S2 Pin: ", this->pin_s2_); LOG_PIN(" S3 Pin: ", this->pin_s3_); - ESP_LOGCONFIG(TAG, "switch delay: %" PRIu32, this->switch_delay_); } void CD74HC4067Component::activate_pin(uint8_t pin) { diff --git a/esphome/components/ch422g/ch422g.cpp b/esphome/components/ch422g/ch422g.cpp index 9a4e342525..d031c31294 100644 --- a/esphome/components/ch422g/ch422g.cpp +++ b/esphome/components/ch422g/ch422g.cpp @@ -93,7 +93,9 @@ bool CH422GComponent::read_inputs_() { bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) { auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0); if (err != i2c::ERROR_OK) { - this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str()); + char buf[64]; + snprintf(buf, sizeof(buf), "write failed for register 0x%X, error %d", reg, err); + this->status_set_warning(buf); return false; } this->status_clear_warning(); @@ -104,7 +106,9 @@ uint8_t CH422GComponent::read_reg_(uint8_t reg) { uint8_t value; auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1); if (err != i2c::ERROR_OK) { - this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str()); + char buf[64]; + snprintf(buf, sizeof(buf), "read failed for register 0x%X, error %d", reg, err); + this->status_set_warning(buf); return 0; } this->status_clear_warning(); @@ -128,7 +132,9 @@ void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this-> bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; } void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); } -std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); } +size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "EXIO%u via CH422G", this->pin_); +} void CH422GGPIOPin::set_flags(gpio::Flags flags) { flags_ = flags; this->parent_->pin_mode(this->pin_, flags); diff --git a/esphome/components/ch422g/ch422g.h b/esphome/components/ch422g/ch422g.h index 1193a3db27..8ed63db90a 100644 --- a/esphome/components/ch422g/ch422g.h +++ b/esphome/components/ch422g/ch422g.h @@ -50,7 +50,7 @@ class CH422GGPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(CH422GComponent *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/chsc6x/chsc6x_touchscreen.cpp b/esphome/components/chsc6x/chsc6x_touchscreen.cpp index 31c9466691..941144e451 100644 --- a/esphome/components/chsc6x/chsc6x_touchscreen.cpp +++ b/esphome/components/chsc6x/chsc6x_touchscreen.cpp @@ -32,14 +32,14 @@ void CHSC6XTouchscreen::update_touches() { } void CHSC6XTouchscreen::dump_config() { - ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); ESP_LOGCONFIG(TAG, + "CHSC6X Touchscreen:\n" " Touch timeout: %d\n" " x_raw_max_: %d\n" " y_raw_max_: %d", this->touch_timeout_, this->x_raw_max_, this->y_raw_max_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); } } // namespace chsc6x diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 5824e68141..2150a30c3e 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -117,9 +117,7 @@ CONF_MIN_HUMIDITY = "min_humidity" CONF_MAX_HUMIDITY = "max_humidity" CONF_TARGET_HUMIDITY = "target_humidity" -visual_temperature = cv.float_with_unit( - "visual_temperature", "(°C|° C|°|C|°K|° K|K|°F|° F|F)?" -) +visual_temperature = cv.float_with_unit("visual_temperature", "(°|(° ?)?[CKF])?") VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema( @@ -275,10 +273,13 @@ async def setup_climate_core_(var, config): visual = config[CONF_VISUAL] if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_min_temperature_override(min_temp)) if (max_temp := visual.get(CONF_MAX_TEMPERATURE)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_max_temperature_override(max_temp)) if (temp_step := visual.get(CONF_TEMPERATURE_STEP)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add( var.set_visual_temperature_step_override( temp_step[CONF_TARGET_TEMPERATURE], @@ -286,8 +287,10 @@ async def setup_climate_core_(var, config): ) ) if (min_humidity := visual.get(CONF_MIN_HUMIDITY)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_min_humidity_override(min_humidity)) if (max_humidity := visual.get(CONF_MAX_HUMIDITY)) is not None: + cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_max_humidity_override(max_humidity)) if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index b0fba6aa62..7611d33cbf 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/macros.h" +#include namespace esphome::climate { @@ -190,24 +191,30 @@ ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) { } ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) { + return this->set_fan_mode(custom_fan_mode, strlen(custom_fan_mode)); +} + +ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { + return this->set_fan_mode(fan_mode.data(), fan_mode.size()); +} + +ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode, size_t len) { // Check if it's a standard enum mode first for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) { - if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) { + if (strncasecmp(custom_fan_mode, mode_entry.str, len) == 0 && mode_entry.str[len] == '\0') { return this->set_fan_mode(static_cast(mode_entry.value)); } } // Find the matching pointer from parent climate device - if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) { + if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode, len)) { this->custom_fan_mode_ = mode_ptr; this->fan_mode_.reset(); return *this; } - ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), custom_fan_mode); + ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %.*s", this->parent_->get_name().c_str(), (int) len, custom_fan_mode); return *this; } -ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { return this->set_fan_mode(fan_mode.c_str()); } - ClimateCall &ClimateCall::set_fan_mode(optional fan_mode) { if (fan_mode.has_value()) { this->set_fan_mode(fan_mode.value()); @@ -222,24 +229,30 @@ ClimateCall &ClimateCall::set_preset(ClimatePreset preset) { } ClimateCall &ClimateCall::set_preset(const char *custom_preset) { + return this->set_preset(custom_preset, strlen(custom_preset)); +} + +ClimateCall &ClimateCall::set_preset(const std::string &preset) { + return this->set_preset(preset.data(), preset.size()); +} + +ClimateCall &ClimateCall::set_preset(const char *custom_preset, size_t len) { // Check if it's a standard enum preset first for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) { - if (str_equals_case_insensitive(custom_preset, preset_entry.str)) { + if (strncasecmp(custom_preset, preset_entry.str, len) == 0 && preset_entry.str[len] == '\0') { return this->set_preset(static_cast(preset_entry.value)); } } // Find the matching pointer from parent climate device - if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) { + if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset, len)) { this->custom_preset_ = preset_ptr; this->preset_.reset(); return *this; } - ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), custom_preset); + ESP_LOGW(TAG, "'%s' - Unrecognized preset %.*s", this->parent_->get_name().c_str(), (int) len, custom_preset); return *this; } -ClimateCall &ClimateCall::set_preset(const std::string &preset) { return this->set_preset(preset.c_str()); } - ClimateCall &ClimateCall::set_preset(optional preset) { if (preset.has_value()) { this->set_preset(preset.value()); @@ -356,7 +369,7 @@ optional Climate::restore_state_() { } void Climate::save_state_() { -#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ +#if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ !defined(CLANG_TIDY) #pragma GCC diagnostic ignored "-Wclass-memaccess" #define TEMP_IGNORE_MEMACCESS @@ -473,26 +486,28 @@ void Climate::publish_state() { ClimateTraits Climate::get_traits() { auto traits = this->traits(); - if (this->visual_min_temperature_override_.has_value()) { - traits.set_visual_min_temperature(*this->visual_min_temperature_override_); +#ifdef USE_CLIMATE_VISUAL_OVERRIDES + if (!std::isnan(this->visual_min_temperature_override_)) { + traits.set_visual_min_temperature(this->visual_min_temperature_override_); } - if (this->visual_max_temperature_override_.has_value()) { - traits.set_visual_max_temperature(*this->visual_max_temperature_override_); + if (!std::isnan(this->visual_max_temperature_override_)) { + traits.set_visual_max_temperature(this->visual_max_temperature_override_); } - if (this->visual_target_temperature_step_override_.has_value()) { - traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_); - traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_); + if (!std::isnan(this->visual_target_temperature_step_override_)) { + traits.set_visual_target_temperature_step(this->visual_target_temperature_step_override_); + traits.set_visual_current_temperature_step(this->visual_current_temperature_step_override_); } - if (this->visual_min_humidity_override_.has_value()) { - traits.set_visual_min_humidity(*this->visual_min_humidity_override_); + if (!std::isnan(this->visual_min_humidity_override_)) { + traits.set_visual_min_humidity(this->visual_min_humidity_override_); } - if (this->visual_max_humidity_override_.has_value()) { - traits.set_visual_max_humidity(*this->visual_max_humidity_override_); + if (!std::isnan(this->visual_max_humidity_override_)) { + traits.set_visual_max_humidity(this->visual_max_humidity_override_); } - +#endif return traits; } +#ifdef USE_CLIMATE_VISUAL_OVERRIDES void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) { this->visual_min_temperature_override_ = visual_min_temperature_override; } @@ -513,6 +528,7 @@ void Climate::set_visual_min_humidity_override(float visual_min_humidity_overrid void Climate::set_visual_max_humidity_override(float visual_max_humidity_override) { this->visual_max_humidity_override_ = visual_max_humidity_override; } +#endif ClimateCall Climate::make_call() { return ClimateCall(this); } @@ -666,30 +682,38 @@ bool Climate::set_fan_mode_(ClimateFanMode mode) { return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode); } -bool Climate::set_custom_fan_mode_(const char *mode) { +bool Climate::set_custom_fan_mode_(const char *mode, size_t len) { auto traits = this->get_traits(); - return set_custom_mode(this->custom_fan_mode_, this->fan_mode, traits.find_custom_fan_mode_(mode), - this->has_custom_fan_mode()); + return set_custom_mode(this->custom_fan_mode_, this->fan_mode, + traits.find_custom_fan_mode_(mode, len), this->has_custom_fan_mode()); } void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; } bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); } -bool Climate::set_custom_preset_(const char *preset) { +bool Climate::set_custom_preset_(const char *preset, size_t len) { auto traits = this->get_traits(); - return set_custom_mode(this->custom_preset_, this->preset, traits.find_custom_preset_(preset), + return set_custom_mode(this->custom_preset_, this->preset, traits.find_custom_preset_(preset, len), this->has_custom_preset()); } void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; } const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) { - return this->get_traits().find_custom_fan_mode_(custom_fan_mode); + return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode)); +} + +const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) { + return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len); } const char *Climate::find_custom_preset_(const char *custom_preset) { - return this->get_traits().find_custom_preset_(custom_preset); + return this->find_custom_preset_(custom_preset, strlen(custom_preset)); +} + +const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) { + return this->get_traits().find_custom_preset_(custom_preset, len); } void Climate::dump_traits_(const char *tag) { diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 28a73d8c05..6fac254502 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -5,6 +5,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/preferences.h" +#include "esphome/core/string_ref.h" #include "climate_mode.h" #include "climate_traits.h" @@ -78,6 +79,8 @@ class ClimateCall { ClimateCall &set_fan_mode(optional fan_mode); /// Set the custom fan mode of the climate device. ClimateCall &set_fan_mode(const char *custom_fan_mode); + /// Set the custom fan mode of the climate device (zero-copy API path). + ClimateCall &set_fan_mode(const char *custom_fan_mode, size_t len); /// Set the swing mode of the climate device. ClimateCall &set_swing_mode(ClimateSwingMode swing_mode); /// Set the swing mode of the climate device. @@ -94,6 +97,8 @@ class ClimateCall { ClimateCall &set_preset(optional preset); /// Set the custom preset of the climate device. ClimateCall &set_preset(const char *custom_preset); + /// Set the custom preset of the climate device (zero-copy API path). + ClimateCall &set_preset(const char *custom_preset, size_t len); void perform(); @@ -106,8 +111,8 @@ class ClimateCall { const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_preset() const; - const char *get_custom_fan_mode() const { return this->custom_fan_mode_; } - const char *get_custom_preset() const { return this->custom_preset_; } + StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); } + StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); } bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } bool has_custom_preset() const { return this->custom_preset_ != nullptr; } @@ -213,11 +218,13 @@ class Climate : public EntityBase { */ ClimateTraits get_traits(); +#ifdef USE_CLIMATE_VISUAL_OVERRIDES void set_visual_min_temperature_override(float visual_min_temperature_override); void set_visual_max_temperature_override(float visual_max_temperature_override); void set_visual_temperature_step_override(float target, float current); void set_visual_min_humidity_override(float visual_min_humidity_override); void set_visual_max_humidity_override(float visual_max_humidity_override); +#endif /// Check if a custom fan mode is currently active. bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } @@ -260,11 +267,11 @@ class Climate : public EntityBase { /// The active swing mode of the climate device. ClimateSwingMode swing_mode{CLIMATE_SWING_OFF}; - /// Get the active custom fan mode (read-only access). - const char *get_custom_fan_mode() const { return this->custom_fan_mode_; } + /// Get the active custom fan mode (read-only access). Returns StringRef. + StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); } - /// Get the active custom preset (read-only access). - const char *get_custom_preset() const { return this->custom_preset_; } + /// Get the active custom preset (read-only access). Returns StringRef. + StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); } protected: friend ClimateCall; @@ -274,7 +281,9 @@ class Climate : public EntityBase { bool set_fan_mode_(ClimateFanMode mode); /// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed. - bool set_custom_fan_mode_(const char *mode); + bool set_custom_fan_mode_(const char *mode) { return this->set_custom_fan_mode_(mode, strlen(mode)); } + bool set_custom_fan_mode_(const char *mode, size_t len); + bool set_custom_fan_mode_(StringRef mode) { return this->set_custom_fan_mode_(mode.c_str(), mode.size()); } /// Clear custom fan mode. void clear_custom_fan_mode_(); @@ -282,15 +291,19 @@ class Climate : public EntityBase { bool set_preset_(ClimatePreset preset); /// Set custom preset. Reset primary preset. Return true if preset has been changed. - bool set_custom_preset_(const char *preset); + bool set_custom_preset_(const char *preset) { return this->set_custom_preset_(preset, strlen(preset)); } + bool set_custom_preset_(const char *preset, size_t len); + bool set_custom_preset_(StringRef preset) { return this->set_custom_preset_(preset.c_str(), preset.size()); } /// Clear custom preset. void clear_custom_preset_(); /// Find and return the matching custom fan mode pointer from traits, or nullptr if not found. const char *find_custom_fan_mode_(const char *custom_fan_mode); + const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len); /// Find and return the matching custom preset pointer from traits, or nullptr if not found. const char *find_custom_preset_(const char *custom_preset); + const char *find_custom_preset_(const char *custom_preset, size_t len); /** Get the default traits of this climate device. * @@ -318,15 +331,17 @@ class Climate : public EntityBase { void dump_traits_(const char *tag); - CallbackManager state_callback_{}; - CallbackManager control_callback_{}; + LazyCallbackManager state_callback_{}; + LazyCallbackManager control_callback_{}; ESPPreferenceObject rtc_; - optional visual_min_temperature_override_{}; - optional visual_max_temperature_override_{}; - optional visual_target_temperature_step_override_{}; - optional visual_current_temperature_step_override_{}; - optional visual_min_humidity_override_{}; - optional visual_max_humidity_override_{}; +#ifdef USE_CLIMATE_VISUAL_OVERRIDES + float visual_min_temperature_override_{NAN}; + float visual_max_temperature_override_{NAN}; + float visual_target_temperature_step_override_{NAN}; + float visual_current_temperature_step_override_{NAN}; + float visual_min_humidity_override_{NAN}; + float visual_max_humidity_override_{NAN}; +#endif private: /** The active custom fan mode (private - enforces use of safe setters). diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index d358293475..80ef0854d5 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -20,18 +20,22 @@ using ClimatePresetMask = FiniteSetMask &vec, const char *value) { +inline bool vector_contains(const std::vector &vec, const char *value, size_t len) { for (const char *item : vec) { - if (strcmp(item, value) == 0) + if (strncmp(item, value, len) == 0 && item[len] == '\0') return true; } return false; } +inline bool vector_contains(const std::vector &vec, const char *value) { + return vector_contains(vec, value, strlen(value)); +} + // Find and return matching pointer from vector, or nullptr if not found -inline const char *vector_find(const std::vector &vec, const char *value) { +inline const char *vector_find(const std::vector &vec, const char *value, size_t len) { for (const char *item : vec) { - if (strcmp(item, value) == 0) + if (strncmp(item, value, len) == 0 && item[len] == '\0') return item; } return nullptr; @@ -257,13 +261,19 @@ class ClimateTraits { /// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found /// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead const char *find_custom_fan_mode_(const char *custom_fan_mode) const { - return vector_find(this->supported_custom_fan_modes_, custom_fan_mode); + return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode)); + } + const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const { + return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len); } /// Find and return the matching custom preset pointer from supported presets, or nullptr if not found /// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead const char *find_custom_preset_(const char *custom_preset) const { - return vector_find(this->supported_custom_presets_, custom_preset); + return this->find_custom_preset_(custom_preset, strlen(custom_preset)); + } + const char *find_custom_preset_(const char *custom_preset, size_t len) const { + return vector_find(this->supported_custom_presets_, custom_preset, len); } uint32_t feature_flags_{0}; diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp index d35ece950b..b4a43cf2f1 100644 --- a/esphome/components/copy/fan/copy_fan.cpp +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -8,20 +8,24 @@ static const char *const TAG = "copy.fan"; void CopyFan::setup() { source_->add_on_state_callback([this]() { - this->state = source_->state; - this->oscillating = source_->oscillating; - this->speed = source_->speed; - this->direction = source_->direction; - this->set_preset_mode_(source_->get_preset_mode()); + this->copy_state_from_source_(); this->publish_state(); }); + this->copy_state_from_source_(); + this->publish_state(); +} + +void CopyFan::copy_state_from_source_() { this->state = source_->state; this->oscillating = source_->oscillating; this->speed = source_->speed; this->direction = source_->direction; - this->set_preset_mode_(source_->get_preset_mode()); - this->publish_state(); + if (source_->has_preset_mode()) { + this->set_preset_mode_(source_->get_preset_mode()); + } else { + this->clear_preset_mode_(); + } } void CopyFan::dump_config() { LOG_FAN("", "Copy Fan", this); } diff --git a/esphome/components/copy/fan/copy_fan.h b/esphome/components/copy/fan/copy_fan.h index b474975bc4..988129f07b 100644 --- a/esphome/components/copy/fan/copy_fan.h +++ b/esphome/components/copy/fan/copy_fan.h @@ -16,7 +16,7 @@ class CopyFan : public fan::Fan, public Component { protected: void control(const fan::FanCall &call) override; - ; + void copy_state_from_source_(); fan::Fan *source_; }; diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp index e45338e785..e85e08e353 100644 --- a/esphome/components/copy/select/copy_select.cpp +++ b/esphome/components/copy/select/copy_select.cpp @@ -7,7 +7,7 @@ namespace copy { static const char *const TAG = "copy.select"; void CopySelect::setup() { - source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); }); + source_->add_on_state_callback([this](size_t index) { this->publish_state(index); }); traits.set_options(source_->traits.get_options()); diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index d8c45ab2bd..e710915a0e 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -152,7 +152,7 @@ class Cover : public EntityBase, public EntityBase_DeviceClass { optional restore_state_(); - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index fe81ae91fe..71fe15f0ae 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -1,11 +1,13 @@ #include "cse7766.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" namespace esphome { namespace cse7766 { static const char *const TAG = "cse7766"; +static constexpr size_t CSE7766_RAW_DATA_SIZE = 24; void CSE7766Component::loop() { const uint32_t now = App.get_loop_component_start_time(); @@ -70,8 +72,8 @@ bool CSE7766Component::check_byte_() { void CSE7766Component::parse_data_() { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE { - std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_)); - ESP_LOGVV(TAG, "Raw data: %s", s.c_str()); + char hex_buf[format_hex_pretty_size(CSE7766_RAW_DATA_SIZE)]; + ESP_LOGVV(TAG, "Raw data: %s", format_hex_pretty_to(hex_buf, this->raw_data_, sizeof(this->raw_data_))); } #endif diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index 5be93692c0..d18d4e7c94 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -83,14 +83,14 @@ void CST816Touchscreen::update_touches() { } void CST816Touchscreen::dump_config() { - ESP_LOGCONFIG(TAG, "CST816 Touchscreen:"); - LOG_I2C_DEVICE(this); - LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + "CST816 Touchscreen:\n" " X Raw Min: %d, X Raw Max: %d\n" " Y Raw Min: %d, Y Raw Max: %d", this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); const char *name; switch (this->chip_id_) { case CST716_CHIP_ID: diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index 895b5515cb..cb3f65c9cd 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -146,8 +146,10 @@ void CurrentBasedCover::dump_config() { if (this->close_obstacle_current_threshold_ != FLT_MAX) { ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_); } - ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); - ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100); + ESP_LOGCONFIG(TAG, + " Close Duration: %.1fs\n" + "Obstacle Rollback: %.1f%%", + this->close_duration_ / 1e3f, this->obstacle_rollback_ * 100); if (this->max_duration_ != UINT32_MAX) { ESP_LOGCONFIG(TAG, "Maximum duration: %.1fs", this->max_duration_ / 1e3f); } diff --git a/esphome/components/dallas_temp/dallas_temp.cpp b/esphome/components/dallas_temp/dallas_temp.cpp index a3969e081e..a1b684abbf 100644 --- a/esphome/components/dallas_temp/dallas_temp.cpp +++ b/esphome/components/dallas_temp/dallas_temp.cpp @@ -51,7 +51,7 @@ void DallasTemperatureSensor::update() { } float tempc = this->get_temp_c_(); - ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc); + ESP_LOGD(TAG, "'%s': Got Temperature=%f°C", this->get_name().c_str(), tempc); this->publish_state(tempc); }); } diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h index 7b9b281ea4..1b0b3d5463 100644 --- a/esphome/components/datetime/datetime_base.h +++ b/esphome/components/datetime/datetime_base.h @@ -22,7 +22,7 @@ class DateTimeBase : public EntityBase { #endif protected: - CallbackManager state_callback_; + LazyCallbackManager state_callback_; #ifdef USE_TIME time::RealTimeClock *rtc_; diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index f54bf82eae..ae38fb2ccd 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -28,24 +28,23 @@ void DebugComponent::dump_config() { #endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) #endif // USE_SENSOR - std::string device_info; - device_info.reserve(256); + char device_info_buffer[DEVICE_INFO_BUFFER_SIZE]; ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); - device_info += ESPHOME_VERSION; + size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION); this->free_heap_ = get_free_heap_(); ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); - get_device_info_(device_info); + pos = get_device_info_(std::span(device_info_buffer), pos); #ifdef USE_TEXT_SENSOR if (this->device_info_ != nullptr) { - if (device_info.length() > 255) - device_info.resize(255); - this->device_info_->publish_state(device_info); + this->device_info_->publish_state(device_info_buffer, pos); } if (this->reset_reason_ != nullptr) { - this->reset_reason_->publish_state(get_reset_reason_()); + char reset_reason_buffer[RESET_REASON_BUFFER_SIZE]; + this->reset_reason_->publish_state( + get_reset_reason_(std::span(reset_reason_buffer))); } #endif // USE_TEXT_SENSOR diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index 96306f7cdf..5783bc5418 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -4,6 +4,13 @@ #include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/core/macros.h" +#include +#include +#include +#include +#ifdef USE_ESP8266 +#include +#endif #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" @@ -15,6 +22,44 @@ namespace esphome { namespace debug { +static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256; +static constexpr size_t RESET_REASON_BUFFER_SIZE = 128; + +#ifdef USE_ESP8266 +// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM) +// Format strings must be wrapped with PSTR() macro +inline size_t buf_append_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) { + if (pos >= size) { + return size; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf_P(buf + pos, size - pos, fmt, args); + va_end(args); + if (written < 0) { + return pos; // encoding error + } + return std::min(pos + static_cast(written), size); +} +#define buf_append(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__) +#else +/// Safely append formatted string to buffer, returning new position (capped at size) +__attribute__((format(printf, 4, 5))) inline size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt, + ...) { + if (pos >= size) { + return size; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf(buf + pos, size - pos, fmt, args); + va_end(args); + if (written < 0) { + return pos; // encoding error + } + return std::min(pos + static_cast(written), size); +} +#endif + class DebugComponent : public PollingComponent { public: void loop() override; @@ -81,10 +126,10 @@ class DebugComponent : public PollingComponent { text_sensor::TextSensor *reset_reason_{nullptr}; #endif // USE_TEXT_SENSOR - std::string get_reset_reason_(); - std::string get_wakeup_cause_(); + const char *get_reset_reason_(std::span buffer); + const char *get_wakeup_cause_(std::span buffer); uint32_t get_free_heap_(); - void get_device_info_(std::string &device_info); + size_t get_device_info_(std::span buffer, size_t pos); void update_platform_(); }; diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 1c3dc3699b..ebb6abf4da 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -58,24 +58,29 @@ void DebugComponent::on_shutdown() { global_preferences->sync(); } -std::string DebugComponent::get_reset_reason_() { - std::string reset_reason; +const char *DebugComponent::get_reset_reason_(std::span buffer) { + char *buf = buffer.data(); + const size_t size = RESET_REASON_BUFFER_SIZE; + unsigned reason = esp_reset_reason(); if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) { - reset_reason = RESET_REASONS[reason]; if (reason == ESP_RST_SW) { auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name())); - char buffer[REBOOT_MAX_LEN]{}; - if (pref.load(&buffer)) { - buffer[REBOOT_MAX_LEN - 1] = '\0'; - reset_reason = "Reboot request from " + std::string(buffer); + char reboot_source[REBOOT_MAX_LEN]{}; + if (pref.load(&reboot_source)) { + reboot_source[REBOOT_MAX_LEN - 1] = '\0'; + snprintf(buf, size, "Reboot request from %s", reboot_source); + } else { + snprintf(buf, size, "%s", RESET_REASONS[reason]); } + } else { + snprintf(buf, size, "%s", RESET_REASONS[reason]); } } else { - reset_reason = "unknown source"; + snprintf(buf, size, "unknown source"); } - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - return reset_reason; + ESP_LOGD(TAG, "Reset Reason: %s", buf); + return buf; } static const char *const WAKEUP_CAUSES[] = { @@ -94,7 +99,7 @@ static const char *const WAKEUP_CAUSES[] = { "BT", }; -std::string DebugComponent::get_wakeup_cause_() { +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { const char *wake_reason; unsigned reason = esp_sleep_get_wakeup_cause(); if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) { @@ -103,6 +108,7 @@ std::string DebugComponent::get_wakeup_cause_() { wake_reason = "unknown source"; } ESP_LOGD(TAG, "Wakeup Reason: %s", wake_reason); + // Return the static string directly - no need to copy to buffer return wake_reason; } @@ -136,7 +142,10 @@ static constexpr ChipFeature CHIP_FEATURES[] = { {CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"}, }; -void DebugComponent::get_device_info_(std::string &device_info) { +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); + #if defined(USE_ARDUINO) const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) @@ -161,68 +170,66 @@ void DebugComponent::get_device_info_(std::string &device_info) { default: flash_mode = "UNKNOWN"; } - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", - ESP.getFlashChipSize() / 1024, // NOLINT - ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT - device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT - "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT - device_info += flash_mode; + uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT + uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT + ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); + pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, + flash_mode); #endif esp_chip_info_t info; esp_chip_info(&info); const char *model = ESPHOME_VARIANT; - std::string features; - // Check each known feature bit + // Build features string + pos = buf_append(buf, size, pos, "|Chip: %s Features:", model); + bool first_feature = true; for (const auto &feature : CHIP_FEATURES) { if (info.features & feature.bit) { - features += feature.name; - features += ", "; + pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name); + first_feature = false; info.features &= ~feature.bit; } } - if (info.features != 0) - features += "Other:" + format_hex(info.features); - ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores, - info.revision); - device_info += "|Chip: "; - device_info += model; - device_info += " Features:"; - device_info += features; - device_info += " Cores:" + to_string(info.cores); - device_info += " Revision:" + to_string(info.revision); - device_info += str_sprintf("|CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000); - ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000); + if (info.features != 0) { + pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features); + } + ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision); + pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision); + + uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000; + ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); + pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz); // Framework detection - device_info += "|Framework: "; #ifdef USE_ARDUINO ESP_LOGD(TAG, "Framework: Arduino"); - device_info += "Arduino"; -#elif defined(USE_ESP_IDF) + pos = buf_append(buf, size, pos, "|Framework: Arduino"); +#elif defined(USE_ESP32) ESP_LOGD(TAG, "Framework: ESP-IDF"); - device_info += "ESP-IDF"; + pos = buf_append(buf, size, pos, "|Framework: ESP-IDF"); #else ESP_LOGW(TAG, "Framework: UNKNOWN"); - device_info += "UNKNOWN"; + pos = buf_append(buf, size, pos, "|Framework: UNKNOWN"); #endif ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); - device_info += "|ESP-IDF: "; - device_info += esp_get_idf_version(); + pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version()); - std::string mac = get_mac_address_pretty(); - ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); - device_info += "|EFuse MAC: "; - device_info += mac; + uint8_t mac[6]; + get_mac_address_raw(mac); + ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], + mac[5]); - device_info += "|Reset: "; - device_info += get_reset_reason_(); + char reason_buffer[RESET_REASON_BUFFER_SIZE]; + const char *reset_reason = get_reset_reason_(std::span(reason_buffer)); + pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); - std::string wakeup_reason = this->get_wakeup_cause_(); - device_info += "|Wakeup: "; - device_info += wakeup_reason; + const char *wakeup_cause = get_wakeup_cause_(std::span(reason_buffer)); + pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause); + + return pos; } void DebugComponent::update_platform_() { diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp index 3395d9db12..274f77e20d 100644 --- a/esphome/components/debug/debug_esp8266.cpp +++ b/esphome/components/debug/debug_esp8266.cpp @@ -8,19 +8,31 @@ namespace debug { static const char *const TAG = "debug"; -std::string DebugComponent::get_reset_reason_() { +const char *DebugComponent::get_reset_reason_(std::span buffer) { + char *buf = buffer.data(); #if !defined(CLANG_TIDY) - return ESP.getResetReason().c_str(); + String reason = ESP.getResetReason(); // NOLINT + snprintf_P(buf, RESET_REASON_BUFFER_SIZE, PSTR("%s"), reason.c_str()); + return buf; #else - return ""; + buf[0] = '\0'; + return buf; #endif } +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { + // ESP8266 doesn't have detailed wakeup cause like ESP32 + return ""; +} + uint32_t DebugComponent::get_free_heap_() { return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) } -void DebugComponent::get_device_info_(std::string &device_info) { +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); + const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: @@ -38,39 +50,45 @@ void DebugComponent::get_device_info_(std::string &device_info) { default: flash_mode = "UNKNOWN"; } - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", - ESP.getFlashChipSize() / 1024, // NOLINT - ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT - device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT - "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT - device_info += flash_mode; + uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT + uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT + ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode); + pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed, + flash_mode); #if !defined(CLANG_TIDY) - auto reset_reason = get_reset_reason_(); - ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId()); - ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion()); - ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str()); - ESP_LOGD(TAG, "Boot Version=%u Mode=%u", ESP.getBootVersion(), ESP.getBootMode()); - ESP_LOGD(TAG, "CPU Frequency: %u", ESP.getCpuFreqMHz()); - ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str()); + char reason_buffer[RESET_REASON_BUFFER_SIZE]; + const char *reset_reason = get_reset_reason_(std::span(reason_buffer)); + uint32_t chip_id = ESP.getChipId(); + uint8_t boot_version = ESP.getBootVersion(); + uint8_t boot_mode = ESP.getBootMode(); + uint8_t cpu_freq = ESP.getCpuFreqMHz(); + uint32_t flash_chip_id = ESP.getFlashChipId(); - device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); - device_info += "|SDK: "; - device_info += ESP.getSdkVersion(); - device_info += "|Core: "; - device_info += ESP.getCoreVersion().c_str(); - device_info += "|Boot: "; - device_info += to_string(ESP.getBootVersion()); - device_info += "|Mode: " + to_string(ESP.getBootMode()); - device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz()); - device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId()); - device_info += "|Reset: "; - device_info += reset_reason; - device_info += "|"; - device_info += ESP.getResetInfo().c_str(); + ESP_LOGD(TAG, + "Chip ID: 0x%08" PRIX32 "\n" + "SDK Version: %s\n" + "Core Version: %s\n" + "Boot Version=%u Mode=%u\n" + "CPU Frequency: %u\n" + "Flash Chip ID=0x%08" PRIX32 "\n" + "Reset Reason: %s\n" + "Reset Info: %s", + chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id, + reset_reason, ESP.getResetInfo().c_str()); + + pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id); + pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion()); + pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str()); + pos = buf_append(buf, size, pos, "|Boot: %u", boot_version); + pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode); + pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq); + pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id); + pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason); + pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str()); #endif + + return pos; } void DebugComponent::update_platform_() { diff --git a/esphome/components/debug/debug_host.cpp b/esphome/components/debug/debug_host.cpp index 09ad34ef88..2fa88f0909 100644 --- a/esphome/components/debug/debug_host.cpp +++ b/esphome/components/debug/debug_host.cpp @@ -5,11 +5,13 @@ namespace esphome { namespace debug { -std::string DebugComponent::get_reset_reason_() { return ""; } +const char *DebugComponent::get_reset_reason_(std::span buffer) { return ""; } + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { return ""; } uint32_t DebugComponent::get_free_heap_() { return INT_MAX; } -void DebugComponent::get_device_info_(std::string &device_info) {} +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { return pos; } void DebugComponent::update_platform_() {} diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index b5e2a5b310..4f07a4cc17 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -7,28 +7,43 @@ namespace debug { static const char *const TAG = "debug"; -std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_name(lt_get_reboot_reason()); } +const char *DebugComponent::get_reset_reason_(std::span buffer) { + // Return the static string directly + return lt_get_reboot_reason_name(lt_get_reboot_reason()); +} + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { return ""; } uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); } -void DebugComponent::get_device_info_(std::string &device_info) { - std::string reset_reason = get_reset_reason_(); - ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version()); - ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz()); - ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id()); - ESP_LOGD(TAG, "Board: %s", lt_get_board_code()); - ESP_LOGD(TAG, "Flash: %u KiB / RAM: %u KiB", lt_flash_get_size() / 1024, lt_ram_get_size() / 1024); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); - device_info += "|Version: "; - device_info += LT_BANNER_STR + 10; - device_info += "|Reset Reason: "; - device_info += reset_reason; - device_info += "|Chip Name: "; - device_info += lt_cpu_get_model_name(); - device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id()); - device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB"; - device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB"; + char reason_buffer[RESET_REASON_BUFFER_SIZE]; + const char *reset_reason = get_reset_reason_(std::span(reason_buffer)); + uint32_t flash_kib = lt_flash_get_size() / 1024; + uint32_t ram_kib = lt_ram_get_size() / 1024; + uint32_t mac_id = lt_cpu_get_mac_id(); + + ESP_LOGD(TAG, + "LibreTiny Version: %s\n" + "Chip: %s (%04x) @ %u MHz\n" + "Chip ID: 0x%06" PRIX32 "\n" + "Board: %s\n" + "Flash: %" PRIu32 " KiB / RAM: %" PRIu32 " KiB\n" + "Reset Reason: %s", + lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id, + lt_get_board_code(), flash_kib, ram_kib, reset_reason); + + pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10); + pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason); + pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name()); + pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id); + pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib); + pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib); + + return pos; } void DebugComponent::update_platform_() { diff --git a/esphome/components/debug/debug_rp2040.cpp b/esphome/components/debug/debug_rp2040.cpp index 497547e30d..a426a73bc2 100644 --- a/esphome/components/debug/debug_rp2040.cpp +++ b/esphome/components/debug/debug_rp2040.cpp @@ -7,13 +7,21 @@ namespace debug { static const char *const TAG = "debug"; -std::string DebugComponent::get_reset_reason_() { return ""; } +const char *DebugComponent::get_reset_reason_(std::span buffer) { return ""; } + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { return ""; } uint32_t DebugComponent::get_free_heap_() { return rp2040.getFreeHeap(); } -void DebugComponent::get_device_info_(std::string &device_info) { - ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu()); - device_info += "CPU Frequency: " + to_string(rp2040.f_cpu()); +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); + + uint32_t cpu_freq = rp2040.f_cpu(); + ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq); + pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq); + + return pos; } void DebugComponent::update_platform_() {} diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index c888c41a78..3f9af03b2b 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -15,14 +15,14 @@ static const char *const TAG = "debug"; constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC; constexpr std::uintptr_t MBR_BOOTLOADER_ADDR = 0xFF8; -static void show_reset_reason(std::string &reset_reason, bool set, const char *reason) { +static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set, const char *reason) { if (!set) { - return; + return pos; } - if (!reset_reason.empty()) { - reset_reason += ", "; + if (pos > 0) { + pos = buf_append(buf, size, pos, ", "); } - reset_reason += reason; + return buf_append(buf, size, pos, "%s", reason); } static inline uint32_t read_mem_u32(uintptr_t addr) { @@ -56,33 +56,47 @@ static inline uint32_t sd_version_get() { return 0; } -std::string DebugComponent::get_reset_reason_() { +const char *DebugComponent::get_reset_reason_(std::span buffer) { + char *buf = buffer.data(); + const size_t size = RESET_REASON_BUFFER_SIZE; + uint32_t cause; auto ret = hwinfo_get_reset_cause(&cause); if (ret) { ESP_LOGE(TAG, "Unable to get reset cause: %d", ret); - return ""; + buf[0] = '\0'; + return buf; } - std::string reset_reason; + size_t pos = 0; - show_reset_reason(reset_reason, cause & RESET_PIN, "External pin"); - show_reset_reason(reset_reason, cause & RESET_SOFTWARE, "Software reset"); - show_reset_reason(reset_reason, cause & RESET_BROWNOUT, "Brownout (drop in voltage)"); - show_reset_reason(reset_reason, cause & RESET_POR, "Power-on reset (POR)"); - show_reset_reason(reset_reason, cause & RESET_WATCHDOG, "Watchdog timer expiration"); - show_reset_reason(reset_reason, cause & RESET_DEBUG, "Debug event"); - show_reset_reason(reset_reason, cause & RESET_SECURITY, "Security violation"); - show_reset_reason(reset_reason, cause & RESET_LOW_POWER_WAKE, "Waking up from low power mode"); - show_reset_reason(reset_reason, cause & RESET_CPU_LOCKUP, "CPU lock-up detected"); - show_reset_reason(reset_reason, cause & RESET_PARITY, "Parity error"); - show_reset_reason(reset_reason, cause & RESET_PLL, "PLL error"); - show_reset_reason(reset_reason, cause & RESET_CLOCK, "Clock error"); - show_reset_reason(reset_reason, cause & RESET_HARDWARE, "Hardware reset"); - show_reset_reason(reset_reason, cause & RESET_USER, "User reset"); - show_reset_reason(reset_reason, cause & RESET_TEMPERATURE, "Temperature reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_PIN, "External pin"); + pos = append_reset_reason(buf, size, pos, cause & RESET_SOFTWARE, "Software reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_BROWNOUT, "Brownout (drop in voltage)"); + pos = append_reset_reason(buf, size, pos, cause & RESET_POR, "Power-on reset (POR)"); + pos = append_reset_reason(buf, size, pos, cause & RESET_WATCHDOG, "Watchdog timer expiration"); + pos = append_reset_reason(buf, size, pos, cause & RESET_DEBUG, "Debug event"); + pos = append_reset_reason(buf, size, pos, cause & RESET_SECURITY, "Security violation"); + pos = append_reset_reason(buf, size, pos, cause & RESET_LOW_POWER_WAKE, "Waking up from low power mode"); + pos = append_reset_reason(buf, size, pos, cause & RESET_CPU_LOCKUP, "CPU lock-up detected"); + pos = append_reset_reason(buf, size, pos, cause & RESET_PARITY, "Parity error"); + pos = append_reset_reason(buf, size, pos, cause & RESET_PLL, "PLL error"); + pos = append_reset_reason(buf, size, pos, cause & RESET_CLOCK, "Clock error"); + pos = append_reset_reason(buf, size, pos, cause & RESET_HARDWARE, "Hardware reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_USER, "User reset"); + pos = append_reset_reason(buf, size, pos, cause & RESET_TEMPERATURE, "Temperature reset"); - ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); - return reset_reason; + // Ensure null termination if nothing was written + if (pos == 0) { + buf[0] = '\0'; + } + + ESP_LOGD(TAG, "Reset Reason: %s", buf); + return buf; +} + +const char *DebugComponent::get_wakeup_cause_(std::span buffer) { + // Zephyr doesn't have detailed wakeup cause like ESP32 + return ""; } uint32_t DebugComponent::get_free_heap_() { return INT_MAX; } @@ -106,187 +120,195 @@ static void fa_cb(const struct flash_area *fa, void *user_data) { void DebugComponent::log_partition_info_() { #if CONFIG_FLASH_MAP_LABELS ESP_LOGCONFIG(TAG, "ID | Device | Device Name " - "| Label | Offset | Size"); - ESP_LOGCONFIG(TAG, "--------------------------------------------" + "| Label | Offset | Size\n" + "--------------------------------------------" "-----------------------------------------------"); #else ESP_LOGCONFIG(TAG, "ID | Device | Device Name " - "| Offset | Size"); - ESP_LOGCONFIG(TAG, "-----------------------------------------" + "| Offset | Size\n" + "-----------------------------------------" "------------------------------"); #endif flash_area_foreach(fa_cb, nullptr); } -void DebugComponent::get_device_info_(std::string &device_info) { - std::string supply = "Main supply status: "; - if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) { - supply += "Normal voltage."; - } else { - supply += "High voltage."; - } - ESP_LOGD(TAG, "%s", supply.c_str()); - device_info += "|" + supply; +size_t DebugComponent::get_device_info_(std::span buffer, size_t pos) { + constexpr size_t size = DEVICE_INFO_BUFFER_SIZE; + char *buf = buffer.data(); - std::string reg0 = "Regulator stage 0: "; + // Main supply status + const char *supply_status = + (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage."; + ESP_LOGD(TAG, "Main supply status: %s", supply_status); + pos = buf_append(buf, size, pos, "|Main supply status: %s", supply_status); + + // Regulator stage 0 if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) { - reg0 += nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO"; - reg0 += ", "; + const char *reg0_type = nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO"; + const char *reg0_voltage; switch (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) { case (UICR_REGOUT0_VOUT_DEFAULT << UICR_REGOUT0_VOUT_Pos): - reg0 += "1.8V (default)"; + reg0_voltage = "1.8V (default)"; break; case (UICR_REGOUT0_VOUT_1V8 << UICR_REGOUT0_VOUT_Pos): - reg0 += "1.8V"; + reg0_voltage = "1.8V"; break; case (UICR_REGOUT0_VOUT_2V1 << UICR_REGOUT0_VOUT_Pos): - reg0 += "2.1V"; + reg0_voltage = "2.1V"; break; case (UICR_REGOUT0_VOUT_2V4 << UICR_REGOUT0_VOUT_Pos): - reg0 += "2.4V"; + reg0_voltage = "2.4V"; break; case (UICR_REGOUT0_VOUT_2V7 << UICR_REGOUT0_VOUT_Pos): - reg0 += "2.7V"; + reg0_voltage = "2.7V"; break; case (UICR_REGOUT0_VOUT_3V0 << UICR_REGOUT0_VOUT_Pos): - reg0 += "3.0V"; + reg0_voltage = "3.0V"; break; case (UICR_REGOUT0_VOUT_3V3 << UICR_REGOUT0_VOUT_Pos): - reg0 += "3.3V"; + reg0_voltage = "3.3V"; break; default: - reg0 += "???V"; + reg0_voltage = "???V"; } + ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage); + pos = buf_append(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage); } else { - reg0 += "disabled"; + ESP_LOGD(TAG, "Regulator stage 0: disabled"); + pos = buf_append(buf, size, pos, "|Regulator stage 0: disabled"); } - ESP_LOGD(TAG, "%s", reg0.c_str()); - device_info += "|" + reg0; - std::string reg1 = "Regulator stage 1: "; - reg1 += nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO"; - ESP_LOGD(TAG, "%s", reg1.c_str()); - device_info += "|" + reg1; + // Regulator stage 1 + const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO"; + ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type); + pos = buf_append(buf, size, pos, "|Regulator stage 1: %s", reg1_type); - std::string usb_power = "USB power state: "; + // USB power state + const char *usb_state; if (nrf_power_usbregstatus_vbusdet_get(NRF_POWER)) { if (nrf_power_usbregstatus_outrdy_get(NRF_POWER)) { - /**< From the power viewpoint, USB is ready for working. */ - usb_power += "ready"; + usb_state = "ready"; } else { - /**< The USB power is detected, but USB power regulator is not ready. */ - usb_power += "connected (regulator is not ready)"; + usb_state = "connected (regulator is not ready)"; } } else { - /**< No power on USB lines detected. */ - usb_power += "disconected"; + usb_state = "disconnected"; } - ESP_LOGD(TAG, "%s", usb_power.c_str()); - device_info += "|" + usb_power; + ESP_LOGD(TAG, "USB power state: %s", usb_state); + pos = buf_append(buf, size, pos, "|USB power state: %s", usb_state); + // Power-fail comparator bool enabled; - nrf_power_pof_thr_t pof_thr; - - pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled); - std::string pof = "Power-fail comparator: "; + nrf_power_pof_thr_t pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled); if (enabled) { + const char *pof_voltage; switch (pof_thr) { case POWER_POFCON_THRESHOLD_V17: - pof += "1.7V"; + pof_voltage = "1.7V"; break; case POWER_POFCON_THRESHOLD_V18: - pof += "1.8V"; + pof_voltage = "1.8V"; break; case POWER_POFCON_THRESHOLD_V19: - pof += "1.9V"; + pof_voltage = "1.9V"; break; case POWER_POFCON_THRESHOLD_V20: - pof += "2.0V"; + pof_voltage = "2.0V"; break; case POWER_POFCON_THRESHOLD_V21: - pof += "2.1V"; + pof_voltage = "2.1V"; break; case POWER_POFCON_THRESHOLD_V22: - pof += "2.2V"; + pof_voltage = "2.2V"; break; case POWER_POFCON_THRESHOLD_V23: - pof += "2.3V"; + pof_voltage = "2.3V"; break; case POWER_POFCON_THRESHOLD_V24: - pof += "2.4V"; + pof_voltage = "2.4V"; break; case POWER_POFCON_THRESHOLD_V25: - pof += "2.5V"; + pof_voltage = "2.5V"; break; case POWER_POFCON_THRESHOLD_V26: - pof += "2.6V"; + pof_voltage = "2.6V"; break; case POWER_POFCON_THRESHOLD_V27: - pof += "2.7V"; + pof_voltage = "2.7V"; break; case POWER_POFCON_THRESHOLD_V28: - pof += "2.8V"; + pof_voltage = "2.8V"; + break; + default: + pof_voltage = "???V"; break; } if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) { - pof += ", VDDH: "; + const char *vddh_voltage; switch (nrf_power_pofcon_vddh_get(NRF_POWER)) { case NRF_POWER_POFTHRVDDH_V27: - pof += "2.7V"; + vddh_voltage = "2.7V"; break; case NRF_POWER_POFTHRVDDH_V28: - pof += "2.8V"; + vddh_voltage = "2.8V"; break; case NRF_POWER_POFTHRVDDH_V29: - pof += "2.9V"; + vddh_voltage = "2.9V"; break; case NRF_POWER_POFTHRVDDH_V30: - pof += "3.0V"; + vddh_voltage = "3.0V"; break; case NRF_POWER_POFTHRVDDH_V31: - pof += "3.1V"; + vddh_voltage = "3.1V"; break; case NRF_POWER_POFTHRVDDH_V32: - pof += "3.2V"; + vddh_voltage = "3.2V"; break; case NRF_POWER_POFTHRVDDH_V33: - pof += "3.3V"; + vddh_voltage = "3.3V"; break; case NRF_POWER_POFTHRVDDH_V34: - pof += "3.4V"; + vddh_voltage = "3.4V"; break; case NRF_POWER_POFTHRVDDH_V35: - pof += "3.5V"; + vddh_voltage = "3.5V"; break; case NRF_POWER_POFTHRVDDH_V36: - pof += "3.6V"; + vddh_voltage = "3.6V"; break; case NRF_POWER_POFTHRVDDH_V37: - pof += "3.7V"; + vddh_voltage = "3.7V"; break; case NRF_POWER_POFTHRVDDH_V38: - pof += "3.8V"; + vddh_voltage = "3.8V"; break; case NRF_POWER_POFTHRVDDH_V39: - pof += "3.9V"; + vddh_voltage = "3.9V"; break; case NRF_POWER_POFTHRVDDH_V40: - pof += "4.0V"; + vddh_voltage = "4.0V"; break; case NRF_POWER_POFTHRVDDH_V41: - pof += "4.1V"; + vddh_voltage = "4.1V"; break; case NRF_POWER_POFTHRVDDH_V42: - pof += "4.2V"; + vddh_voltage = "4.2V"; + break; + default: + vddh_voltage = "???V"; break; } + ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); + pos = buf_append(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage); + } else { + ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage); + pos = buf_append(buf, size, pos, "|Power-fail comparator: %s", pof_voltage); } } else { - pof += "disabled"; + ESP_LOGD(TAG, "Power-fail comparator: disabled"); + pos = buf_append(buf, size, pos, "|Power-fail comparator: disabled"); } - ESP_LOGD(TAG, "%s", pof.c_str()); - device_info += "|" + pof; auto package = [](uint32_t value) { switch (value) { @@ -300,17 +322,19 @@ void DebugComponent::get_device_info_(std::string &device_info) { return "Unspecified"; }; - ESP_LOGD(TAG, "Code page size: %u, code size: %u, device id: 0x%08x%08x", NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, - NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0]); - ESP_LOGD(TAG, "Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x", NRF_FICR->ER[0], + char mac_pretty[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + get_mac_address_pretty_into_buffer(mac_pretty); + ESP_LOGD(TAG, + "Code page size: %u, code size: %u, device id: 0x%08x%08x\n" + "Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x\n" + "Device address type: %s, address: %s\n" + "Part code: nRF%x, version: %c%c%c%c, package: %s\n" + "RAM: %ukB, Flash: %ukB, production test: %sdone", + NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0], NRF_FICR->ER[0], NRF_FICR->ER[1], NRF_FICR->ER[2], NRF_FICR->ER[3], NRF_FICR->IR[0], NRF_FICR->IR[1], NRF_FICR->IR[2], - NRF_FICR->IR[3]); - ESP_LOGD(TAG, "Device address type: %s, address: %s", (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), - get_mac_address_pretty().c_str()); - ESP_LOGD(TAG, "Part code: nRF%x, version: %c%c%c%c, package: %s", NRF_FICR->INFO.PART, + NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), mac_pretty, NRF_FICR->INFO.PART, NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, NRF_FICR->INFO.VARIANT >> 8 & 0xFF, - NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE)); - ESP_LOGD(TAG, "RAM: %ukB, Flash: %ukB, production test: %sdone", NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, + NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE), NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not ")); bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] && (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected @@ -329,9 +353,10 @@ void DebugComponent::get_device_info_(std::string &device_info) { #else ESP_LOGD(TAG, "bootloader: Adafruit, version %u.%u.%u", (BOOTLOADER_VERSION_REGISTER >> 16) & 0xFF, (BOOTLOADER_VERSION_REGISTER >> 8) & 0xFF, BOOTLOADER_VERSION_REGISTER & 0xFF); - ESP_LOGD(TAG, "MBR bootloader addr 0x%08x, UICR bootloader addr 0x%08x", read_mem_u32(MBR_BOOTLOADER_ADDR), - NRF_UICR->NRFFW[0]); - ESP_LOGD(TAG, "MBR param page addr 0x%08x, UICR param page addr 0x%08x", read_mem_u32(MBR_PARAM_PAGE_ADDR), + ESP_LOGD(TAG, + "MBR bootloader addr 0x%08x, UICR bootloader addr 0x%08x\n" + "MBR param page addr 0x%08x, UICR param page addr 0x%08x", + read_mem_u32(MBR_BOOTLOADER_ADDR), NRF_UICR->NRFFW[0], read_mem_u32(MBR_PARAM_PAGE_ADDR), NRF_UICR->NRFFW[1]); if (is_sd_present()) { uint32_t const sd_id = sd_id_get(); @@ -368,8 +393,12 @@ void DebugComponent::get_device_info_(std::string &device_info) { } return res; }; - ESP_LOGD(TAG, "NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str()); - ESP_LOGD(TAG, "NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str()); + ESP_LOGD(TAG, + "NRFFW %s\n" + "NRFHW %s", + uicr(NRF_UICR->NRFFW, 13).c_str(), uicr(NRF_UICR->NRFHW, 12).c_str()); + + return pos; } void DebugComponent::update_platform_() {} diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 8849fad7d6..3cfe7aa641 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -1,4 +1,4 @@ -from esphome import automation, pins +from esphome import automation, core, pins import esphome.codegen as cg from esphome.components import esp32, time from esphome.components.esp32 import ( @@ -23,16 +23,20 @@ from esphome.const import ( CONF_MINUTE, CONF_MODE, CONF_NUMBER, + CONF_PIN, CONF_PINS, CONF_RUN_DURATION, CONF_SECOND, CONF_SLEEP_DURATION, CONF_TIME_ID, CONF_WAKEUP_PIN, + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, PlatformFramework, ) +from esphome.core import CORE +from esphome.types import ConfigType WAKEUP_PINS = { VARIANT_ESP32: [ @@ -113,7 +117,7 @@ WAKEUP_PINS = { } -def validate_pin_number(value): +def validate_pin_number_esp32(value: ConfigType) -> ConfigType: valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32]) if value[CONF_NUMBER] not in valid_pins: raise cv.Invalid( @@ -122,6 +126,51 @@ def validate_pin_number(value): return value +def validate_pin_number(value: ConfigType) -> ConfigType: + if not CORE.is_esp32: + return value + return validate_pin_number_esp32(value) + + +def validate_wakeup_pin( + value: ConfigType | list[ConfigType], +) -> list[ConfigType]: + if not isinstance(value, list): + processed_pins: list[ConfigType] = [{CONF_PIN: value}] + else: + processed_pins = list(value) + + for i, pin_config in enumerate(processed_pins): + # now validate each item + validated_pin = WAKEUP_PIN_SCHEMA(pin_config) + validate_pin_number(validated_pin[CONF_PIN]) + processed_pins[i] = validated_pin + + return processed_pins + + +def validate_config(config: ConfigType) -> ConfigType: + # right now only BK72XX supports the list format for wakeup pins + if CORE.is_bk72xx: + if CONF_WAKEUP_PIN_MODE in config: + wakeup_pins = config.get(CONF_WAKEUP_PIN, []) + if len(wakeup_pins) > 1: + raise cv.Invalid( + "You need to remove the global wakeup_pin_mode and define it per pin" + ) + if wakeup_pins: + wakeup_pins[0][CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE) + elif ( + isinstance(config.get(CONF_WAKEUP_PIN), list) + and len(config[CONF_WAKEUP_PIN]) > 1 + ): + raise cv.Invalid( + "Your platform does not support providing multiple entries in wakeup_pin" + ) + + return config + + def _validate_ex1_wakeup_mode(value): if value == "ALL_LOW": esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value) @@ -141,6 +190,15 @@ def _validate_ex1_wakeup_mode(value): return value +def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod: + if not CORE.is_bk72xx: + return value + max_duration = core.TimePeriod(hours=36) + if value > max_duration: + raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX") + return value + + deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) @@ -186,6 +244,13 @@ WAKEUP_CAUSES_SCHEMA = cv.Schema( } ) +WAKEUP_PIN_SCHEMA = cv.Schema( + { + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_WAKEUP_PIN_MODE): cv.enum(WAKEUP_PIN_MODES, upper=True), + } +) + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -194,14 +259,15 @@ CONFIG_SCHEMA = cv.All( cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA), cv.positive_time_period_milliseconds, ), - cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, - cv.Optional(CONF_WAKEUP_PIN): cv.All( - cv.only_on_esp32, - pins.internal_gpio_input_pin_schema, - validate_pin_number, + cv.Optional(CONF_SLEEP_DURATION): cv.All( + cv.positive_time_period_milliseconds, + _validate_sleep_duration, ), + cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin, cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All( - cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True + cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]), + cv.enum(WAKEUP_PIN_MODES), + upper=True, ), cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( cv.only_on_esp32, @@ -212,7 +278,8 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_PINS): cv.ensure_list( - pins.internal_gpio_input_pin_schema, validate_pin_number + pins.internal_gpio_input_pin_schema, + validate_pin_number_esp32, ), cv.Required(CONF_MODE): cv.All( cv.enum(EXT1_WAKEUP_MODES, upper=True), @@ -238,7 +305,8 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]), + validate_config, ) @@ -249,8 +317,21 @@ async def to_code(config): if CONF_SLEEP_DURATION in config: cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION])) if CONF_WAKEUP_PIN in config: - pin = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN]) - cg.add(var.set_wakeup_pin(pin)) + pins_as_list = config.get(CONF_WAKEUP_PIN, []) + if CORE.is_bk72xx: + cg.add(var.init_wakeup_pins_(len(pins_as_list))) + for item in pins_as_list: + cg.add( + var.add_wakeup_pin( + await cg.gpio_pin_expression(item[CONF_PIN]), + item.get( + CONF_WAKEUP_PIN_MODE, WakeupPinMode.WAKEUP_PIN_MODE_IGNORE + ), + ) + ) + else: + pin = await cg.gpio_pin_expression(pins_as_list[0][CONF_PIN]) + cg.add(var.set_wakeup_pin(pin)) if CONF_WAKEUP_PIN_MODE in config: cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE])) if CONF_RUN_DURATION in config: @@ -305,7 +386,10 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All( cv.Schema( { cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( - cv.positive_time_period_milliseconds + cv.All( + cv.positive_time_period_milliseconds, + _validate_sleep_duration, + ) ), # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep cv.Exclusive(CONF_UNTIL, "time"): cv.All( @@ -363,5 +447,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.ESP32_IDF, }, "deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "deep_sleep_bk72xx.cpp": {PlatformFramework.BK72XX_ARDUINO}, } ) diff --git a/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp b/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp new file mode 100644 index 0000000000..b5fadd7230 --- /dev/null +++ b/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp @@ -0,0 +1,64 @@ +#ifdef USE_BK72XX + +#include "deep_sleep_component.h" +#include "esphome/core/log.h" + +namespace esphome::deep_sleep { + +static const char *const TAG = "deep_sleep.bk72xx"; + +optional DeepSleepComponent::get_run_duration_() const { return this->run_duration_; } + +void DeepSleepComponent::dump_config_platform_() { + for (const WakeUpPinItem &item : this->wakeup_pins_) { + LOG_PIN(" Wakeup Pin: ", item.wakeup_pin); + } +} + +bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const { + return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr && + !this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin))); +} + +bool DeepSleepComponent::prepare_to_sleep_() { + if (wakeup_pins_.size() > 0) { + for (WakeUpPinItem &item : this->wakeup_pins_) { + if (pin_prevents_sleep_(item)) { + // Defer deep sleep until inactive + if (!this->next_enter_deep_sleep_) { + this->status_set_warning(); + ESP_LOGV(TAG, "Waiting for pin to switch state to enter deep sleep..."); + } + this->next_enter_deep_sleep_ = true; + return false; + } + } + } + return true; +} + +void DeepSleepComponent::deep_sleep_() { + for (WakeUpPinItem &item : this->wakeup_pins_) { + if (item.wakeup_pin_mode == WAKEUP_PIN_MODE_INVERT_WAKEUP) { + if (item.wakeup_level == get_real_pin_state_(*item.wakeup_pin)) { + item.wakeup_level = !item.wakeup_level; + } + } + ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW", + static_cast(item.wakeup_pin_mode)); + } + + if (this->sleep_duration_.has_value()) + lt_deep_sleep_config_timer((*this->sleep_duration_ / 1000) & 0xFFFFFFFF); + + for (WakeUpPinItem &item : this->wakeup_pins_) { + lt_deep_sleep_config_gpio(1 << item.wakeup_pin->get_pin(), item.wakeup_level); + lt_deep_sleep_keep_floating_gpio(1 << item.wakeup_pin->get_pin(), true); + } + + lt_deep_sleep_enter(); +} + +} // namespace esphome::deep_sleep + +#endif // USE_BK72XX diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index bca3aa5e4d..3e6eda2257 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -19,7 +19,7 @@ namespace esphome { namespace deep_sleep { -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_BK72XX) /** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32 * and the scenario occurs that the wakeup pin is already in the wakeup state. @@ -33,7 +33,17 @@ enum WakeupPinMode { */ WAKEUP_PIN_MODE_INVERT_WAKEUP, }; +#endif +#if defined(USE_BK72XX) +struct WakeUpPinItem { + InternalGPIOPin *wakeup_pin; + WakeupPinMode wakeup_pin_mode; + bool wakeup_level; +}; +#endif // USE_BK72XX + +#ifdef USE_ESP32 #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) struct Ext1Wakeup { uint64_t mask; @@ -75,6 +85,13 @@ class DeepSleepComponent : public Component { void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); #endif // USE_ESP32 +#if defined(USE_BK72XX) + void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); } + void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) { + this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()}); + } +#endif // USE_BK72XX + #if defined(USE_ESP32) #if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); @@ -114,7 +131,17 @@ class DeepSleepComponent : public Component { bool prepare_to_sleep_(); void deep_sleep_(); +#ifdef USE_BK72XX + bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const; + bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); } +#endif // USE_BK72XX + optional sleep_duration_; + +#ifdef USE_BK72XX + FixedVector wakeup_pins_; +#endif // USE_BK72XX + #ifdef USE_ESP32 InternalGPIOPin *wakeup_pin_; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; @@ -124,8 +151,10 @@ class DeepSleepComponent : public Component { #endif optional touch_wakeup_; + optional wakeup_cause_to_run_duration_; #endif // USE_ESP32 + optional run_duration_; bool next_enter_deep_sleep_{false}; bool prevent_{false}; diff --git a/esphome/components/deep_sleep/deep_sleep_esp32.cpp b/esphome/components/deep_sleep/deep_sleep_esp32.cpp index 833be8e76c..79c34f627a 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp32.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp32.cpp @@ -26,7 +26,7 @@ namespace deep_sleep { // - ext0: Single pin wakeup using RTC GPIO (esp_sleep_enable_ext0_wakeup) // - ext1: Multiple pin wakeup (esp_sleep_enable_ext1_wakeup) // - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup) -// - GPIO wakeup: GPIO wakeup for non-RTC pins (esp_deep_sleep_enable_gpio_wakeup) +// - GPIO wakeup: GPIO wakeup for RTC pins (esp_deep_sleep_enable_gpio_wakeup) static const char *const TAG = "deep_sleep"; @@ -127,22 +127,14 @@ void DeepSleepComponent::deep_sleep_() { defined(USE_ESP32_VARIANT_ESP32C61) if (this->wakeup_pin_ != nullptr) { const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin()); - if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) { - gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY); - } else if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLDOWN) { - gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY); - } - gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT); - gpio_hold_en(gpio_pin); -#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP - // Some ESP32 variants support holding a single GPIO during deep sleep without this function - // For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep - gpio_deep_sleep_hold_en(); -#endif + // Make sure GPIO is in input mode, not all RTC GPIO pins are input by default + gpio_set_direction(gpio_pin, GPIO_MODE_INPUT); bool level = !this->wakeup_pin_->is_inverted(); if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { level = !level; } + // Internal pullup/pulldown resistors are enabled automatically, when + // ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS is set (by default it is) esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(), static_cast(level)); } diff --git a/esphome/components/dfrobot_sen0395/commands.cpp b/esphome/components/dfrobot_sen0395/commands.cpp index 42074c80cf..8bb6ddf942 100644 --- a/esphome/components/dfrobot_sen0395/commands.cpp +++ b/esphome/components/dfrobot_sen0395/commands.cpp @@ -179,8 +179,10 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) { ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!"); return 1; // Command done } else if (message == "Done") { - ESP_LOGI(TAG, "Updated detection area config:"); - ESP_LOGI(TAG, "Detection area 1 from %.02fm to %.02fm.", this->min1_, this->max1_); + ESP_LOGI(TAG, + "Updated detection area config:\n" + "Detection area 1 from %.02fm to %.02fm.", + this->min1_, this->max1_); if (this->min2_ >= 0 && this->max2_ >= 0) { ESP_LOGI(TAG, "Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_); } @@ -209,9 +211,11 @@ uint8_t SetLatencyCommand::on_message(std::string &message) { ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); return 1; // Command done } else if (message == "Done") { - ESP_LOGI(TAG, "Updated output latency config:"); - ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_); - ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_); + ESP_LOGI(TAG, + "Updated output latency config:\n" + "Signal that someone was detected is delayed by %.03f s.\n" + "Signal that nobody is detected anymore is delayed by %.03f s.", + this->delay_after_detection_, this->delay_after_disappear_); ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); return 1; // Command done } diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index cc0bf55a80..6cb204c8de 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -8,17 +8,23 @@ namespace dht { static const char *const TAG = "dht"; void DHT::setup() { - this->pin_->digital_write(true); - this->pin_->setup(); - this->pin_->digital_write(true); + this->t_pin_->digital_write(true); + this->t_pin_->setup(); +#ifdef USE_ESP32 + this->t_pin_->pin_mode(this->t_pin_->get_flags() | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN); +#endif + this->t_pin_->digital_write(true); } void DHT::dump_config() { - ESP_LOGCONFIG(TAG, "DHT:"); - LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "", - this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent"); - ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->pin_->get_flags() & gpio::FLAG_PULLUP)); + ESP_LOGCONFIG(TAG, + "DHT:\n" + " %sModel: %s\n" + " Internal pull-up: %s", + this->is_auto_detect_ ? "Auto-detected " : "", + this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent", + ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP)); + LOG_PIN(" Pin: ", this->t_pin_); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); @@ -72,21 +78,15 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r int8_t i = 0; uint8_t data[5] = {0, 0, 0, 0, 0}; - this->pin_->digital_write(false); - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); +#ifndef USE_ESP32 + this->pin_.pin_mode(gpio::FLAG_OUTPUT); +#endif + this->pin_.digital_write(false); if (this->model_ == DHT_MODEL_DHT11) { delayMicroseconds(18000); } else if (this->model_ == DHT_MODEL_SI7021) { -#ifdef USE_ESP8266 delayMicroseconds(500); - this->pin_->digital_write(true); - delayMicroseconds(40); -#else - delayMicroseconds(400); - this->pin_->digital_write(true); -#endif } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { delayMicroseconds(2000); } else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) { @@ -94,7 +94,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r } else { delayMicroseconds(800); } - this->pin_->pin_mode(this->pin_->get_flags()); + +#ifdef USE_ESP32 + this->pin_.digital_write(true); +#else + this->pin_.pin_mode(this->t_pin_->get_flags()); +#endif { InterruptLock lock; @@ -110,7 +115,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r uint32_t start_time = micros(); // Wait for rising edge - while (!this->pin_->digital_read()) { + while (!this->pin_.digital_read()) { if (micros() - start_time > 90) { if (i < 0) { error_code = 1; // line didn't clear @@ -127,7 +132,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r uint32_t end_time = start_time; // Wait for falling edge - while (this->pin_->digital_read()) { + while (this->pin_.digital_read()) { end_time = micros(); if (end_time - start_time > 90) { if (i < 0) { diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index 327e8a4f5c..9047dd2c96 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -38,7 +38,10 @@ class DHT : public PollingComponent { */ void set_dht_model(DHTModel model); - void set_pin(InternalGPIOPin *pin) { pin_ = pin; } + void set_pin(InternalGPIOPin *pin) { + this->t_pin_ = pin; + this->pin_ = pin->to_isr(); + } void set_model(DHTModel model) { model_ = model; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -54,7 +57,8 @@ class DHT : public PollingComponent { protected: bool read_sensor_(float *temperature, float *humidity, bool report_errors); - InternalGPIOPin *pin_; + InternalGPIOPin *t_pin_; + ISRInternalGPIOPin pin_; DHTModel model_{DHT_MODEL_AUTO_DETECT}; bool is_auto_detect_{false}; sensor::Sensor *temperature_sensor_{nullptr}; diff --git a/esphome/components/display_menu_base/menu_item.cpp b/esphome/components/display_menu_base/menu_item.cpp index 08f758045e..ad8b03de60 100644 --- a/esphome/components/display_menu_base/menu_item.cpp +++ b/esphome/components/display_menu_base/menu_item.cpp @@ -42,7 +42,8 @@ std::string MenuItemSelect::get_value_text() const { result = this->value_getter_.value()(this); } else { if (this->select_var_ != nullptr) { - result = this->select_var_->current_option(); + auto option = this->select_var_->current_option(); + result.assign(option.c_str(), option.size()); } } diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 017a11673f..0ba68daf5d 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -4,7 +4,7 @@ from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_UART_ID -CODEOWNERS = ["@glmnet", "@zuidwijk"] +CODEOWNERS = ["@glmnet", "@zuidwijk", "@PolarGoose"] MULTI_CONF = True @@ -61,7 +61,6 @@ CONFIG_SCHEMA = cv.All( ): cv.positive_time_period_milliseconds, } ).extend(uart.UART_DEVICE_SCHEMA), - cv.only_with_arduino, ) @@ -83,7 +82,7 @@ async def to_code(config): cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID])) # DSMR Parser - cg.add_library("glmnet/Dsmr", "0.8") + cg.add_library("esphome/dsmr_parser", "1.0.0") # Crypto - cg.add_library("rweather/Crypto", "0.4.0") + cg.add_library("polargoose/Crypto-no-arduino", "0.4.0") diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index d99cf5e7a9..5c62aa93ab 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "dsmr.h" #include "esphome/core/log.h" @@ -7,8 +5,7 @@ #include #include -namespace esphome { -namespace dsmr { +namespace esphome::dsmr { static const char *const TAG = "dsmr"; @@ -257,9 +254,9 @@ bool Dsmr::parse_telegram() { ESP_LOGV(TAG, "Trying to parse telegram"); this->stop_requesting_data_(); - ::dsmr::ParseResult res = - ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, - this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. + const auto &res = dsmr_parser::P1Parser::parse( + data, this->telegram_, this->bytes_read_, false, + this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. if (res.err) { // Parsing error, show it auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_); @@ -271,7 +268,7 @@ bool Dsmr::parse_telegram() { // publish the telegram, after publishing the sensors so it can also trigger action based on latest values if (this->s_telegram_ != nullptr) { - this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_)); + this->s_telegram_->publish_state(this->telegram_, this->bytes_read_); } return true; } @@ -329,7 +326,4 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } } -} // namespace dsmr -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::dsmr diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 7304737b50..56ba75b5fa 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -1,24 +1,17 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/uart/uart.h" #include "esphome/core/log.h" -#include "esphome/core/defines.h" - -// don't include because it puts everything in global namespace -#include -#include - +#include +#include #include -namespace esphome { -namespace dsmr { +namespace esphome::dsmr { -using namespace ::dsmr::fields; +using namespace dsmr_parser::fields; // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines @@ -44,8 +37,8 @@ using namespace ::dsmr::fields; #define DSMR_DATA_SENSOR(s) s #define DSMR_COMMA , -using MyData = ::dsmr::ParsedData; +using MyData = dsmr_parser::ParsedData; class Dsmr : public Component, public uart::UARTDevice { public: @@ -140,7 +133,4 @@ class Dsmr : public Component, public uart::UARTDevice { std::vector decryption_key_{}; bool crc_check_; }; -} // namespace dsmr -} // namespace esphome - -#endif // USE_ARDUINO +} // namespace esphome::dsmr diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 0696fccdf7..7d69f79530 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -3,27 +3,34 @@ from esphome.components import sensor import esphome.config_validation as cv from esphome.const import ( CONF_ID, + DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_DURATION, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, + DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_WATER, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_CUBIC_METER, + UNIT_HERTZ, + UNIT_KILOVOLT_AMPS, UNIT_KILOVOLT_AMPS_REACTIVE, UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, UNIT_KILOWATT, UNIT_KILOWATT_HOURS, + UNIT_SECOND, UNIT_VOLT, ) from . import CONF_DSMR_ID, Dsmr AUTO_LOAD = ["dsmr"] - +UNIT_GIGA_JOULE = "GJ" CONFIG_SCHEMA = cv.Schema( { @@ -46,6 +53,18 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("energy_delivered_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=3, @@ -64,14 +83,82 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("energy_returned_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff1_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_delivered_tariff2_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff1_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("energy_returned_tariff2_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), cv.Optional("total_imported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, ), + cv.Optional("reactive_energy_delivered_tariff1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_delivered_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), cv.Optional("total_exported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, ), + cv.Optional("reactive_energy_returned_tariff1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), + cv.Optional("reactive_energy_returned_tariff4"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, + ), cv.Optional("power_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, @@ -84,61 +171,195 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("power_delivered_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("power_returned_ch"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_switch_position"): sensor.sensor_schema( accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_failures"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_long_failures"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l1"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_swells_l3"): sensor.sensor_schema( accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_time_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_sag_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_time_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_swell_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=1, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_n"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_sum"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_fuse_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=3, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), @@ -181,51 +402,93 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, + accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("voltage_avg_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_avg_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_avg_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("frequency"): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + accuracy_decimals=3, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("abs_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional("gas_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_CUBIC_METER, accuracy_decimals=3, @@ -244,6 +507,109 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_WATER, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("thermal_delivered"): sensor.sensor_schema( + unit_of_measurement=UNIT_GIGA_JOULE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("sub_delivered"): sensor.sensor_schema( + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("gas_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("gas_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("thermal_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("thermal_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("water_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("water_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("sub_device_type"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("sub_valve_position"): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_delivery_power_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("apparent_return_power_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_demand_power"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_demand_abs"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional( "active_energy_import_current_average_demand" ): sensor.sensor_schema( @@ -252,6 +618,90 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional( + "active_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_import_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_import_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_export_current_average_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_energy_import_last_completed_demand"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_energy_export_last_completed_demand"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_import_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "reactive_energy_export_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_import_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + "apparent_energy_export_last_completed_demand" + ): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOVOLT_AMPS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional( "active_energy_import_maximum_demand_running_month" ): sensor.sensor_schema( @@ -268,6 +718,14 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional("fw_core_version"): sensor.sensor_schema( + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("fw_module_version"): sensor.sensor_schema( + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 3223d943be..4c7455a38f 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -18,11 +18,15 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(), cv.Optional("message_short"): text_sensor.text_sensor_schema(), cv.Optional("message_long"): text_sensor.text_sensor_schema(), + cv.Optional("equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("gas_equipment_id_be"): text_sensor.text_sensor_schema(), cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), + cv.Optional("fw_core_checksum"): text_sensor.text_sensor_schema(), + cv.Optional("fw_module_checksum"): text_sensor.text_sensor_schema(), cv.Optional("telegram"): text_sensor.text_sensor_schema().extend( {cv.Optional(CONF_INTERNAL, default=True): cv.boolean} ), diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index c10c88faf2..f11e7f4fe3 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -82,8 +82,9 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) { return; } - ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(), - light_effect->get_last_universe()); + auto effect_name = light_effect->get_name(); + ESP_LOGD(TAG, "Registering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(), + light_effect->get_first_universe(), light_effect->get_last_universe()); light_effects_.push_back(light_effect); @@ -98,8 +99,9 @@ void E131Component::remove_effect(E131AddressableLightEffect *light_effect) { return; } - ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(), - light_effect->get_last_universe()); + auto effect_name = light_effect->get_name(); + ESP_LOGD(TAG, "Unregistering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(), + light_effect->get_first_universe(), light_effect->get_last_universe()); // Swap with last element and pop for O(1) removal (order doesn't matter) *it = light_effects_.back(); diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index 780e181f04..7d62f739a2 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -58,8 +58,9 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1)); auto *input_data = packet.values + 1; - ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name(), universe, output_offset, - output_end); + auto effect_name = get_name(); + ESP_LOGV(TAG, "Applying data for '%.*s' on %d universe, for %" PRId32 "-%d.", (int) effect_name.size(), + effect_name.c_str(), universe, output_offset, output_end); switch (channels_) { case E131_MONO: diff --git a/esphome/components/ee895/ee895.cpp b/esphome/components/ee895/ee895.cpp index c6eaf4e728..602e31db14 100644 --- a/esphome/components/ee895/ee895.cpp +++ b/esphome/components/ee895/ee895.cpp @@ -7,6 +7,9 @@ namespace ee895 { static const char *const TAG = "ee895"; +// Serial number is 16 bytes +static constexpr size_t EE895_SERIAL_NUMBER_SIZE = 16; + static const uint16_t CRC16_ONEWIRE_START = 0xFFFF; static const uint8_t FUNCTION_CODE_READ = 0x03; static const uint16_t SERIAL_NUMBER = 0x0000; @@ -26,7 +29,10 @@ void EE895Component::setup() { this->mark_failed(); return; } - ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char serial_hex[format_hex_size(EE895_SERIAL_NUMBER_SIZE)]; +#endif + ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, serial_number + 2, EE895_SERIAL_NUMBER_SIZE)); } void EE895Component::dump_config() { diff --git a/esphome/components/emmeti/emmeti.cpp b/esphome/components/emmeti/emmeti.cpp index 3cb184f868..5286f962b8 100644 --- a/esphome/components/emmeti/emmeti.cpp +++ b/esphome/components/emmeti/emmeti.cpp @@ -153,8 +153,10 @@ void EmmetiClimate::reverse_add_(T val, size_t len, esphome::remote_base::Remote bool EmmetiClimate::check_checksum_(uint8_t checksum) { uint8_t expected = this->gen_checksum_(); - ESP_LOGV(TAG, "Expected checksum: %X", expected); - ESP_LOGV(TAG, "Checksum received: %X", checksum); + ESP_LOGV(TAG, + "Expected checksum: %X\n" + "Checksum received: %X", + expected, checksum); return checksum == expected; } @@ -264,8 +266,10 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { } } - ESP_LOGD(TAG, "Swing: %d", (curr_state.bitmap >> 1) & 0x01); - ESP_LOGD(TAG, "Sleep: %d", (curr_state.bitmap >> 2) & 0x01); + ESP_LOGD(TAG, + "Swing: %d\n" + "Sleep: %d", + (curr_state.bitmap >> 1) & 0x01, (curr_state.bitmap >> 2) & 0x01); for (size_t pos = 0; pos < 4; pos++) { if (data.expect_item(EMMETI_BIT_MARK, EMMETI_ONE_SPACE)) { @@ -291,10 +295,13 @@ bool EmmetiClimate::on_receive(remote_base::RemoteReceiveData data) { } } - ESP_LOGD(TAG, "Turbo: %d", (curr_state.bitmap >> 3) & 0x01); - ESP_LOGD(TAG, "Light: %d", (curr_state.bitmap >> 4) & 0x01); - ESP_LOGD(TAG, "Tree: %d", (curr_state.bitmap >> 5) & 0x01); - ESP_LOGD(TAG, "Blow: %d", (curr_state.bitmap >> 6) & 0x01); + ESP_LOGD(TAG, + "Turbo: %d\n" + "Light: %d\n" + "Tree: %d\n" + "Blow: %d", + (curr_state.bitmap >> 3) & 0x01, (curr_state.bitmap >> 4) & 0x01, (curr_state.bitmap >> 5) & 0x01, + (curr_state.bitmap >> 6) & 0x01); uint16_t control_data = 0; for (size_t pos = 0; pos < 11; pos++) { diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index 381f098eb5..2c281ea2e6 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -104,10 +104,12 @@ void EndstopCover::loop() { } void EndstopCover::dump_config() { LOG_COVER("", "Endstop Cover", this); + ESP_LOGCONFIG(TAG, + " Open Duration: %.1fs\n" + " Close Duration: %.1fs", + this->open_duration_ / 1e3f, this->close_duration_ / 1e3f); LOG_BINARY_SENSOR(" ", "Open Endstop", this->open_endstop_); - ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f); LOG_BINARY_SENSOR(" ", "Close Endstop", this->close_endstop_); - ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f); } float EndstopCover::get_setup_priority() const { return setup_priority::DATA; } void EndstopCover::stop_prev_trigger_() { diff --git a/esphome/components/ens160_base/ens160_base.cpp b/esphome/components/ens160_base/ens160_base.cpp index 6ffaac9588..785b053f04 100644 --- a/esphome/components/ens160_base/ens160_base.cpp +++ b/esphome/components/ens160_base/ens160_base.cpp @@ -151,14 +151,16 @@ void ENS160Component::update() { } // verbose status logging - ESP_LOGV(TAG, "Status: ENS160 STATAS bit 0x%x", - (ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS); - ESP_LOGV(TAG, "Status: ENS160 STATER bit 0x%x", - (ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER); - ESP_LOGV(TAG, "Status: ENS160 VALIDITY FLAG 0x%02x", (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2); - ESP_LOGV(TAG, "Status: ENS160 NEWDAT bit 0x%x", - (ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT); - ESP_LOGV(TAG, "Status: ENS160 NEWGPR bit 0x%x", + ESP_LOGV(TAG, + "Status: ENS160 STATAS bit 0x%x\n" + "Status: ENS160 STATER bit 0x%x\n" + "Status: ENS160 VALIDITY FLAG 0x%02x\n" + "Status: ENS160 NEWDAT bit 0x%x\n" + "Status: ENS160 NEWGPR bit 0x%x", + (ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS, + (ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER, + (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2, + (ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT, (ENS160_DATA_STATUS_NEWGPR & (status_value)) == ENS160_DATA_STATUS_NEWGPR); data_ready = ENS160_DATA_STATUS_NEWDAT & status_value; diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index b7e71a3cae..a77e291237 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -184,6 +184,7 @@ async def to_code(config): height, init_sequence_id, init_sequence_length, + *model.get_constructor_args(config), ) # Rotation is handled by setting the transform diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index b2e58694c8..db803305a5 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -7,6 +7,7 @@ namespace esphome::epaper_spi { static const char *const TAG = "epaper_spi"; +static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128; static constexpr const char *const EPAPER_STATE_STRINGS[] = { "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE", @@ -53,23 +54,20 @@ void EPaperBase::setup_pins_() const { float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; } void EPaperBase::command(uint8_t value) { - this->start_command_(); + ESP_LOGV(TAG, "Command: 0x%02X", value); + this->dc_pin_->digital_write(false); + this->enable(); this->write_byte(value); - this->end_command_(); -} - -void EPaperBase::data(uint8_t value) { - this->start_data_(); - this->write_byte(value); - this->end_data_(); + this->disable(); } // write a command followed by zero or more bytes of data. -// The command is the first byte, length is the length of data only in the second byte, followed by the data. -// [COMMAND, LENGTH, DATA...] void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)]; ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length, - format_hex_pretty(ptr, length, '.', false).c_str()); + format_hex_pretty_to(hex_buf, ptr, length, '.')); +#endif this->dc_pin_->digital_write(false); this->enable(); @@ -126,14 +124,10 @@ void EPaperBase::wait_for_idle_(bool should_wait) { void EPaperBase::loop() { auto now = millis(); - if (this->delay_until_ != 0) { - // using modulus arithmetic to handle wrap-around - int diff = now - this->delay_until_; - if (diff < 0) { - return; - } - this->delay_until_ = 0; - } + // using modulus arithmetic to handle wrap-around + int diff = now - this->delay_until_; + if (diff < 0) + return; if (this->waiting_for_idle_) { if (this->is_idle_()) { this->waiting_for_idle_ = false; @@ -188,7 +182,7 @@ void EPaperBase::process_state_() { this->set_state_(EPaperState::RESET); break; case EPaperState::INITIALISE: - this->initialise_(); + this->initialise(this->update_count_ != 0); this->set_state_(EPaperState::TRANSFER_DATA); break; case EPaperState::TRANSFER_DATA: @@ -226,11 +220,11 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) { ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_()); this->state_ = state; this->wait_for_idle_(state > EPaperState::SHOULD_WAIT); - if (delay != 0) { - this->delay_until_ = millis() + delay; - } else { - this->delay_until_ = 0; - } + // allow subclasses to nominate delays + if (delay == 0) + delay = this->next_delay_; + this->next_delay_ = 0; + this->delay_until_ = millis() + delay; ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay, TRUEFALSE(this->waiting_for_idle_)); if (state == EPaperState::IDLE) { @@ -238,22 +232,14 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) { } } -void EPaperBase::start_command_() { - this->dc_pin_->digital_write(false); - this->enable(); -} - -void EPaperBase::end_command_() { this->disable(); } - void EPaperBase::start_data_() { this->dc_pin_->digital_write(true); this->enable(); } -void EPaperBase::end_data_() { this->disable(); } void EPaperBase::on_safe_shutdown() { this->deep_sleep(); } -void EPaperBase::initialise_() { +void EPaperBase::initialise(bool partial) { size_t index = 0; auto *sequence = this->init_sequence_; @@ -313,9 +299,8 @@ bool EPaperBase::rotate_coordinates_(int &x, int &y) { void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) { if (!rotate_coordinates_(x, y)) return; - const size_t pixel_position = y * this->width_ + x; - const size_t byte_position = pixel_position / 8; - const uint8_t bit_position = pixel_position % 8; + const size_t byte_position = y * this->row_width_ + x / 8; + const uint8_t bit_position = x % 8; const uint8_t pixel_bit = 0x80 >> bit_position; const auto original = this->buffer_[byte_position]; if ((color_to_bit(color) == 0)) { @@ -327,20 +312,21 @@ void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) { void EPaperBase::dump_config() { LOG_DISPLAY("", "E-Paper SPI", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->name_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); - LOG_PIN(" DC Pin: ", this->dc_pin_); - LOG_PIN(" Busy Pin: ", this->busy_pin_); - LOG_PIN(" CS Pin: ", this->cs_); - LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, + " Model: %s\n" " SPI Data Rate: %uMHz\n" " Full update every: %d\n" " Swap X/Y: %s\n" " Mirror X: %s\n" " Mirror Y: %s", - (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, YESNO(this->transform_ & SWAP_XY), - YESNO(this->transform_ & MIRROR_X), YESNO(this->transform_ & MIRROR_Y)); + this->name_, (unsigned) (this->data_rate_ / 1000000), this->full_update_every_, + YESNO(this->transform_ & SWAP_XY), YESNO(this->transform_ & MIRROR_X), + YESNO(this->transform_ & MIRROR_Y)); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_UPDATE_INTERVAL(this); } } // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index 6852416cac..521543f026 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -36,14 +36,16 @@ class EPaperBase : public Display, public spi::SPIDevice { public: - EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, - size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY) + EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence = nullptr, + size_t init_sequence_length = 0, DisplayType display_type = DISPLAY_TYPE_BINARY) : name_(name), width_(width), height_(height), init_sequence_(init_sequence), init_sequence_length_(init_sequence_length), - display_type_(display_type) {} + display_type_(display_type) { + this->row_width_ = (this->width_ + 7) / 8; // width of a row in bytes + } void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } @@ -54,9 +56,13 @@ class EPaperBase : public Display, void dump_config() override; void command(uint8_t value); - void data(uint8_t value); void cmd_data(uint8_t command, const uint8_t *ptr, size_t length); + // variant with in-place initializer list + void cmd_data(uint8_t command, std::initializer_list data) { + this->cmd_data(command, data.begin(), data.size()); + } + void update() override; void loop() override; @@ -76,6 +82,12 @@ class EPaperBase : public Display, return 0; } void fill(Color color) override { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + auto pixel_color = color_to_bit(color) ? 0xFF : 0x00; // We store 8 pixels per byte @@ -103,7 +115,7 @@ class EPaperBase : public Display, bool is_idle_() const; void setup_pins_() const; virtual bool reset(); - void initialise_(); + virtual void initialise(bool partial); void wait_for_idle_(bool should_wait); bool init_buffer_(size_t buffer_length); bool rotate_coordinates_(int &x, int &y); @@ -137,14 +149,12 @@ class EPaperBase : public Display, void set_state_(EPaperState state, uint16_t delay = 0); - void start_command_(); - void end_command_(); void start_data_(); - void end_data_(); // properties initialised in the constructor const char *name_; uint16_t width_; + uint16_t row_width_; // width of a row in bytes uint16_t height_; const uint8_t *init_sequence_; size_t init_sequence_length_; @@ -157,7 +167,8 @@ class EPaperBase : public Display, GPIOPin *busy_pin_{}; GPIOPin *reset_pin_{}; bool waiting_for_idle_{}; - uint32_t delay_until_{}; + uint32_t delay_until_{}; // timestamp until which to delay processing + uint16_t next_delay_{}; // milliseconds to delay before next state uint8_t transform_{}; uint8_t update_count_{}; // these values represent the bounds of the updated buffer. Note that x_high and y_high diff --git a/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp b/esphome/components/epaper_spi/epaper_spi_mono.cpp similarity index 53% rename from esphome/components/epaper_spi/epaper_spi_ssd1677.cpp rename to esphome/components/epaper_spi/epaper_spi_mono.cpp index e4f04657ad..d10022c4ac 100644 --- a/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp +++ b/esphome/components/epaper_spi/epaper_spi_mono.cpp @@ -1,25 +1,24 @@ -#include "epaper_spi_ssd1677.h" +#include "epaper_spi_mono.h" #include #include "esphome/core/log.h" namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.ssd1677"; +static constexpr const char *const TAG = "epaper_spi.mono"; -void EPaperSSD1677::refresh_screen(bool partial) { +void EPaperMono::refresh_screen(bool partial) { ESP_LOGV(TAG, "Refresh screen"); - this->command(0x22); - this->data(partial ? 0xFF : 0xF7); + this->cmd_data(0x22, {partial ? (uint8_t) 0xFF : (uint8_t) 0xF7}); this->command(0x20); } -void EPaperSSD1677::deep_sleep() { +void EPaperMono::deep_sleep() { ESP_LOGV(TAG, "Deep sleep"); this->command(0x10); } -bool EPaperSSD1677::reset() { +bool EPaperMono::reset() { if (EPaperBase::reset()) { this->command(0x12); return true; @@ -27,29 +26,24 @@ bool EPaperSSD1677::reset() { return false; } -bool HOT EPaperSSD1677::transfer_data() { +void EPaperMono::set_window() { + // round x-coordinates to byte boundaries + this->x_low_ &= ~7; + this->x_high_ += 7; + this->x_high_ &= ~7; + this->cmd_data(0x44, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256), (uint8_t) (this->x_high_ - 1), + (uint8_t) ((this->x_high_ - 1) / 256)}); + this->cmd_data(0x4E, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256)}); + this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1), + (uint8_t) ((this->y_high_ - 1) / 256)}); + this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)}); +} + +bool HOT EPaperMono::transfer_data() { auto start_time = millis(); if (this->current_data_index_ == 0) { - uint8_t data[4]{}; // round to byte boundaries - this->x_low_ &= ~7; - this->y_low_ &= ~7; - this->x_high_ += 7; - this->x_high_ &= ~7; - this->y_high_ += 7; - this->y_high_ &= ~7; - data[0] = this->x_low_; - data[1] = this->x_low_ / 256; - data[2] = this->x_high_ - 1; - data[3] = (this->x_high_ - 1) / 256; - cmd_data(0x4E, data, 2); - cmd_data(0x44, data, sizeof(data)); - data[0] = this->y_low_; - data[1] = this->y_low_ / 256; - data[2] = this->y_high_ - 1; - data[3] = (this->y_high_ - 1) / 256; - cmd_data(0x4F, data, 2); - this->cmd_data(0x45, data, sizeof(data)); + this->set_window(); // for monochrome, we still need to clear the red data buffer at least once to prevent it // causing dirty pixels after partial refresh. this->command(this->send_red_ ? 0x26 : 0x24); @@ -58,10 +52,10 @@ bool HOT EPaperSSD1677::transfer_data() { size_t row_length = (this->x_high_ - this->x_low_) / 8; FixedVector bytes_to_send{}; bytes_to_send.init(row_length); - ESP_LOGV(TAG, "Writing bytes at line %zu at %ums", this->current_data_index_, (unsigned) millis()); + ESP_LOGV(TAG, "Writing %u bytes at line %zu at %ums", row_length, this->current_data_index_, (unsigned) millis()); this->start_data_(); while (this->current_data_index_ != this->y_high_) { - size_t data_idx = (this->current_data_index_ * this->width_ + this->x_low_) / 8; + size_t data_idx = this->current_data_index_ * this->row_width_ + this->x_low_ / 8; for (size_t i = 0; i != row_length; i++) { bytes_to_send[i] = this->send_red_ ? 0 : this->buffer_[data_idx++]; } @@ -69,12 +63,12 @@ bool HOT EPaperSSD1677::transfer_data() { this->write_array(&bytes_to_send.front(), row_length); // NOLINT if (millis() - start_time > MAX_TRANSFER_TIME) { // Let the main loop run and come back next loop - this->end_data_(); + this->disable(); return false; } } - this->end_data_(); + this->disable(); this->current_data_index_ = 0; if (this->send_red_) { this->send_red_ = false; diff --git a/esphome/components/epaper_spi/epaper_spi_ssd1677.h b/esphome/components/epaper_spi/epaper_spi_mono.h similarity index 57% rename from esphome/components/epaper_spi/epaper_spi_ssd1677.h rename to esphome/components/epaper_spi/epaper_spi_mono.h index 47584d24c0..f44b59e803 100644 --- a/esphome/components/epaper_spi/epaper_spi_ssd1677.h +++ b/esphome/components/epaper_spi/epaper_spi_mono.h @@ -3,13 +3,15 @@ #include "epaper_spi.h" namespace esphome::epaper_spi { - -class EPaperSSD1677 : public EPaperBase { +/** + * A class for monochrome epaper displays. + */ +class EPaperMono : public EPaperBase { public: - EPaperSSD1677(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, - size_t init_sequence_length) + EPaperMono(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, + size_t init_sequence_length) : EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) { - this->buffer_length_ = width * height / 8; // 8 pixels per byte + this->buffer_length_ = (width + 7) / 8 * height; // 8 pixels per byte, rounded up } protected: @@ -18,6 +20,7 @@ class EPaperSSD1677 : public EPaperBase { void power_off() override{}; void deep_sleep() override; bool reset() override; + virtual void set_window(); bool transfer_data() override; bool send_red_{true}; }; diff --git a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp index d0e68595d0..1ef2dd12c3 100644 --- a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +++ b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp @@ -80,23 +80,26 @@ void EPaperSpectraE6::power_on() { void EPaperSpectraE6::power_off() { ESP_LOGV(TAG, "Power off"); - this->command(0x02); - this->data(0x00); + this->cmd_data(0x02, {0x00}); } void EPaperSpectraE6::refresh_screen(bool partial) { ESP_LOGV(TAG, "Refresh"); - this->command(0x12); - this->data(0x00); + this->cmd_data(0x12, {0x00}); } void EPaperSpectraE6::deep_sleep() { ESP_LOGV(TAG, "Deep sleep"); - this->command(0x07); - this->data(0xA5); + this->cmd_data(0x07, {0xA5}); } void EPaperSpectraE6::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + EPaperBase::fill(color); + return; + } + auto pixel_color = color_to_hex(color); // We store 2 pixels per byte @@ -137,7 +140,7 @@ bool HOT EPaperSpectraE6::transfer_data() { if (buf_idx == sizeof bytes_to_send) { this->start_data_(); this->write_array(bytes_to_send, buf_idx); - this->end_data_(); + this->disable(); ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis()); buf_idx = 0; @@ -151,7 +154,7 @@ bool HOT EPaperSpectraE6::transfer_data() { if (buf_idx != 0) { this->start_data_(); this->write_array(bytes_to_send, buf_idx); - this->end_data_(); + this->disable(); } this->current_data_index_ = 0; return true; diff --git a/esphome/components/epaper_spi/epaper_waveshare.cpp b/esphome/components/epaper_spi/epaper_waveshare.cpp new file mode 100644 index 0000000000..8d382d86e7 --- /dev/null +++ b/esphome/components/epaper_spi/epaper_waveshare.cpp @@ -0,0 +1,47 @@ +#include "epaper_waveshare.h" + +namespace esphome::epaper_spi { + +static const char *const TAG = "epaper_spi.waveshare"; + +void EpaperWaveshare::initialise(bool partial) { + EPaperBase::initialise(partial); + if (partial) { + this->cmd_data(0x32, this->partial_lut_, this->partial_lut_length_); + this->cmd_data(0x3C, {0x80}); + this->cmd_data(0x22, {0xC0}); + this->command(0x20); + this->next_delay_ = 100; + } else { + this->cmd_data(0x32, this->lut_, this->lut_length_); + this->cmd_data(0x3C, {0x05}); + } + this->send_red_ = true; +} + +void EpaperWaveshare::set_window() { + this->x_low_ &= ~7; + this->x_high_ += 7; + this->x_high_ &= ~7; + uint16_t x_start = this->x_low_ / 8; + uint16_t x_end = (this->x_high_ - 1) / 8; + this->cmd_data(0x44, {(uint8_t) x_start, (uint8_t) (x_end)}); + this->cmd_data(0x4E, {(uint8_t) x_start}); + this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1), + (uint8_t) ((this->y_high_ - 1) / 256)}); + this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)}); + ESP_LOGV(TAG, "Set window X: %u-%u, Y: %u-%u", this->x_low_, this->x_high_, this->y_low_, this->y_high_); +} + +void EpaperWaveshare::refresh_screen(bool partial) { + if (partial) { + this->cmd_data(0x22, {0x0F}); + } else { + this->cmd_data(0x22, {0xC7}); + } + this->command(0x20); + this->next_delay_ = partial ? 100 : 3000; +} + +void EpaperWaveshare::deep_sleep() { this->cmd_data(0x10, {0x01}); } +} // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/epaper_waveshare.h b/esphome/components/epaper_spi/epaper_waveshare.h new file mode 100644 index 0000000000..6b894cfd09 --- /dev/null +++ b/esphome/components/epaper_spi/epaper_waveshare.h @@ -0,0 +1,30 @@ +#pragma once +#include "epaper_spi.h" +#include "epaper_spi_mono.h" + +namespace esphome::epaper_spi { +/** + * An epaper display that needs LUTs to be sent to it. + */ +class EpaperWaveshare : public EPaperMono { + public: + EpaperWaveshare(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence, + size_t init_sequence_length, const uint8_t *lut, size_t lut_length, const uint8_t *partial_lut, + uint16_t partial_lut_length) + : EPaperMono(name, width, height, init_sequence, init_sequence_length), + lut_(lut), + lut_length_(lut_length), + partial_lut_(partial_lut), + partial_lut_length_(partial_lut_length) {} + + protected: + void initialise(bool partial) override; + void set_window() override; + void refresh_screen(bool partial) override; + void deep_sleep() override; + const uint8_t *lut_; + size_t lut_length_; + const uint8_t *partial_lut_; + uint16_t partial_lut_length_; +}; +} // namespace esphome::epaper_spi diff --git a/esphome/components/epaper_spi/models/__init__.py b/esphome/components/epaper_spi/models/__init__.py index 019eb31d18..3fcf3217ec 100644 --- a/esphome/components/epaper_spi/models/__init__.py +++ b/esphome/components/epaper_spi/models/__init__.py @@ -32,6 +32,9 @@ class EpaperModel: return cv.Required(name) return cv.Optional(name, default=self.get_default(name, fallback)) + def get_constructor_args(self, config) -> tuple: + return () + def get_dimensions(self, config) -> tuple[int, int]: if CONF_DIMENSIONS in config: # Explicit dimensions, just use as is diff --git a/esphome/components/epaper_spi/models/ssd1677.py b/esphome/components/epaper_spi/models/ssd1677.py index 3eb53d650e..f7e012f162 100644 --- a/esphome/components/epaper_spi/models/ssd1677.py +++ b/esphome/components/epaper_spi/models/ssd1677.py @@ -4,10 +4,9 @@ from . import EpaperModel class SSD1677(EpaperModel): - def __init__(self, name, class_name="EPaperSSD1677", **kwargs): - if CONF_DATA_RATE not in kwargs: - kwargs[CONF_DATA_RATE] = "20MHz" - super().__init__(name, class_name, **kwargs) + def __init__(self, name, class_name="EPaperMono", data_rate="20MHz", **defaults): + defaults[CONF_DATA_RATE] = data_rate + super().__init__(name, class_name, **defaults) # fmt: off def get_init_sequence(self, config: dict): @@ -23,11 +22,15 @@ class SSD1677(EpaperModel): ssd1677 = SSD1677("ssd1677") -ssd1677.extend( - "seeed-ee04-mono-4.26", +wave_4_26 = ssd1677.extend( + "waveshare-4.26in", width=800, height=480, mirror_x=True, +) + +wave_4_26.extend( + "seeed-ee04-mono-4.26", cs_pin=44, dc_pin=10, reset_pin=38, diff --git a/esphome/components/epaper_spi/models/waveshare.py b/esphome/components/epaper_spi/models/waveshare.py new file mode 100644 index 0000000000..74a288977d --- /dev/null +++ b/esphome/components/epaper_spi/models/waveshare.py @@ -0,0 +1,88 @@ +import esphome.codegen as cg +from esphome.core import ID + +from ..display import CONF_INIT_SEQUENCE_ID +from . import EpaperModel + + +class WaveshareModel(EpaperModel): + def __init__(self, name, lut, lut_partial=None, **defaults): + super().__init__(name, "EpaperWaveshare", **defaults) + self.lut = lut + self.lut_partial = lut_partial + + def get_constructor_args(self, config) -> tuple: + lut = ( + cg.static_const_array( + ID(config[CONF_INIT_SEQUENCE_ID].id + "_lut", type=cg.uint8), self.lut + ), + len(self.lut), + ) + if self.lut_partial is None: + lut_partial = cg.nullptr, 0 + else: + lut_partial = ( + cg.static_const_array( + ID( + config[CONF_INIT_SEQUENCE_ID].id + "_lut_partial", type=cg.uint8 + ), + self.lut_partial, + ), + len(self.lut_partial), + ) + return *lut, *lut_partial + + +# fmt: off +WaveshareModel( + "waveshare-2.13in-v3", + width=122, + height=250, + initsequence=( + (0x01, 0x27, 0x01, 0x00), # driver output control + (0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00), + (0x11, 0x03), # Data entry mode + (0x3F, 0x22), # Undocumented command + (0x2C, 0x36), # write VCOM register + (0x04, 0x41, 0x0C, 0x32), # SRC voltage + (0x03, 0x17), # Gate voltage + (0x21, 0x00, 0x80), # Display update control + (0x18, 0x80), # Select internal temperature sensor + ), + lut=( + 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x0, 0x0, 0x0, + ), + lut_partial=( + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x0, 0x0, 0x0, + ), +) diff --git a/esphome/components/es8388/es8388.cpp b/esphome/components/es8388/es8388.cpp index 5abe7a5e5f..9deb29416f 100644 --- a/esphome/components/es8388/es8388.cpp +++ b/esphome/components/es8388/es8388.cpp @@ -116,9 +116,8 @@ void ES8388::setup() { if (this->dac_output_select_ != nullptr) { auto dac_power = this->get_dac_power(); if (dac_power.has_value()) { - auto dac_power_str = this->dac_output_select_->at(dac_power.value()); - if (dac_power_str.has_value()) { - this->dac_output_select_->publish_state(dac_power_str.value()); + if (this->dac_output_select_->has_index(dac_power.value())) { + this->dac_output_select_->publish_state(dac_power.value()); } else { ESP_LOGW(TAG, "Unknown DAC output power value: %d", dac_power.value()); } @@ -127,9 +126,8 @@ void ES8388::setup() { if (this->adc_input_mic_select_ != nullptr) { auto mic_input = this->get_mic_input(); if (mic_input.has_value()) { - auto mic_input_str = this->adc_input_mic_select_->at(mic_input.value()); - if (mic_input_str.has_value()) { - this->adc_input_mic_select_->publish_state(mic_input_str.value()); + if (this->adc_input_mic_select_->has_index(mic_input.value())) { + this->adc_input_mic_select_->publish_state(mic_input.value()); } else { ESP_LOGW(TAG, "Unknown ADC input mic value: %d", mic_input.value()); } @@ -210,9 +208,11 @@ bool ES8388::set_dac_output(DacOutputLine line) { return false; }; - ESP_LOGV(TAG, "Setting ES8388_DACPOWER to 0x%02X", dac_power); - ESP_LOGV(TAG, "Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X", reg_out1); - ESP_LOGV(TAG, "Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X", reg_out2); + ESP_LOGV(TAG, + "Setting ES8388_DACPOWER to 0x%02X\n" + "Setting ES8388_DACCONTROL24 / ES8388_DACCONTROL25 to 0x%02X\n" + "Setting ES8388_DACCONTROL26 / ES8388_DACCONTROL27 to 0x%02X", + dac_power, reg_out1, reg_out2); ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL24, reg_out1)); // LOUT1VOL ES8388_ERROR_CHECK(this->write_byte(ES8388_DACCONTROL25, reg_out1)); // ROUT1VOL diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 0142fd4841..45fe8d1c26 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -13,17 +13,20 @@ from esphome.const import ( CONF_ADVANCED, CONF_BOARD, CONF_COMPONENTS, + CONF_DISABLED, CONF_ESPHOME, CONF_FRAMEWORK, CONF_IGNORE_EFUSE_CUSTOM_MAC, CONF_IGNORE_EFUSE_MAC_CRC, CONF_LOG_LEVEL, CONF_NAME, + CONF_OTA, CONF_PATH, CONF_PLATFORM_VERSION, CONF_PLATFORMIO_OPTIONS, CONF_REF, CONF_REFRESH, + CONF_SAFE_MODE, CONF_SOURCE, CONF_TYPE, CONF_VARIANT, @@ -81,7 +84,9 @@ CONF_ASSERTION_LEVEL = "assertion_level" CONF_COMPILER_OPTIMIZATION = "compiler_optimization" CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features" CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert" +CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback" CONF_EXECUTE_FROM_PSRAM = "execute_from_psram" +CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision" CONF_RELEASE = "release" LOG_LEVELS_IDF = [ @@ -106,6 +111,21 @@ COMPILER_OPTIMIZATIONS = { "SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE", } +# ESP32 (original) chip revision options +# Setting minimum revision to 3.0 or higher: +# - Reduces flash size by excluding workaround code for older chip bugs +# - For PSRAM users: disables CONFIG_SPIRAM_CACHE_WORKAROUND, which saves significant +# IRAM by keeping C library functions in ROM instead of recompiling them +# See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/chip_revision.html +ESP32_CHIP_REVISIONS = { + "0.0": "CONFIG_ESP32_REV_MIN_0", + "1.0": "CONFIG_ESP32_REV_MIN_1", + "1.1": "CONFIG_ESP32_REV_MIN_1_1", + "2.0": "CONFIG_ESP32_REV_MIN_2", + "3.0": "CONFIG_ESP32_REV_MIN_3", + "3.1": "CONFIG_ESP32_REV_MIN_3_1", +} + # Socket limit configuration for ESP-IDF # ESP-IDF CONFIG_LWIP_MAX_SOCKETS has range 1-253, default 10 DEFAULT_MAX_SOCKETS = 10 # ESP-IDF default @@ -118,8 +138,8 @@ ARDUINO_ALLOWED_VARIANTS = [ ] -def get_cpu_frequencies(*frequencies): - return [str(x) + "MHZ" for x in frequencies] +def get_cpu_frequencies(*frequencies: int) -> list[str]: + return [f"{frequency}MHZ" for frequency in frequencies] CPU_FREQUENCIES = { @@ -136,7 +156,7 @@ CPU_FREQUENCIES = { } # Make sure not missed here if a new variant added. -assert all(v in CPU_FREQUENCIES for v in VARIANTS) +assert all(variant in CPU_FREQUENCIES for variant in VARIANTS) FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values())) @@ -250,10 +270,10 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): def add_idf_component( *, name: str, - repo: str = None, - ref: str = None, - path: str = None, - refresh: TimePeriod = None, + repo: str | None = None, + ref: str | None = None, + path: str | None = None, + refresh: TimePeriod | None = None, components: list[str] | None = None, submodules: list[str] | None = None, ): @@ -334,7 +354,7 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str: return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}" -def _is_framework_url(source: str) -> str: +def _is_framework_url(source: str) -> bool: # platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink import urllib.parse @@ -354,11 +374,12 @@ def _is_framework_url(source: str) -> str: # The default/recommended arduino framework version # - https://github.com/espressif/arduino-esp32/releases ARDUINO_FRAMEWORK_VERSION_LOOKUP = { - "recommended": cv.Version(3, 3, 2), - "latest": cv.Version(3, 3, 4), - "dev": cv.Version(3, 3, 4), + "recommended": cv.Version(3, 3, 5), + "latest": cv.Version(3, 3, 5), + "dev": cv.Version(3, 3, 5), } ARDUINO_PLATFORM_VERSION_LOOKUP = { + cv.Version(3, 3, 5): cv.Version(55, 3, 35), cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"), cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"), @@ -371,15 +392,33 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = { cv.Version(3, 1, 1): cv.Version(53, 3, 11), cv.Version(3, 1, 0): cv.Version(53, 3, 10), } +# Maps Arduino framework versions to a compatible ESP-IDF version +# These versions correspond to pioarduino/esp-idf releases +# See: https://github.com/pioarduino/esp-idf/releases +ARDUINO_IDF_VERSION_LOOKUP = { + cv.Version(3, 3, 5): cv.Version(5, 5, 2), + cv.Version(3, 3, 4): cv.Version(5, 5, 1), + cv.Version(3, 3, 3): cv.Version(5, 5, 1), + cv.Version(3, 3, 2): cv.Version(5, 5, 1), + cv.Version(3, 3, 1): cv.Version(5, 5, 1), + cv.Version(3, 3, 0): cv.Version(5, 5, 0), + cv.Version(3, 2, 1): cv.Version(5, 4, 2), + cv.Version(3, 2, 0): cv.Version(5, 4, 2), + cv.Version(3, 1, 3): cv.Version(5, 3, 2), + cv.Version(3, 1, 2): cv.Version(5, 3, 2), + cv.Version(3, 1, 1): cv.Version(5, 3, 1), + cv.Version(3, 1, 0): cv.Version(5, 3, 0), +} # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { - "recommended": cv.Version(5, 5, 1), - "latest": cv.Version(5, 5, 1), - "dev": cv.Version(5, 5, 1), + "recommended": cv.Version(5, 5, 2), + "latest": cv.Version(5, 5, 2), + "dev": cv.Version(5, 5, 2), } ESP_IDF_PLATFORM_VERSION_LOOKUP = { + cv.Version(5, 5, 2): cv.Version(55, 3, 35), cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"), cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"), cv.Version(5, 4, 3): cv.Version(55, 3, 32), @@ -396,9 +435,9 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = { # The platform-espressif32 version # - https://github.com/pioarduino/platform-espressif32/releases PLATFORM_VERSION_LOOKUP = { - "recommended": cv.Version(55, 3, 31, "2"), - "latest": cv.Version(55, 3, 31, "2"), - "dev": cv.Version(55, 3, 31, "2"), + "recommended": cv.Version(55, 3, 35), + "latest": cv.Version(55, 3, 35), + "dev": cv.Version(55, 3, 35), } @@ -544,6 +583,16 @@ def final_validate(config): path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC], ) ) + if ( + config[CONF_VARIANT] != VARIANT_ESP32 + and advanced.get(CONF_MINIMUM_CHIP_REVISION) is not None + ): + errs.append( + cv.Invalid( + f"'{CONF_MINIMUM_CHIP_REVISION}' is only supported on {VARIANT_ESP32}", + path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION], + ) + ) if advanced[CONF_EXECUTE_FROM_PSRAM]: if config[CONF_VARIANT] != VARIANT_ESP32S3: errs.append( @@ -571,6 +620,20 @@ def final_validate(config): path=[CONF_FLASH_SIZE], ) ) + if advanced[CONF_ENABLE_OTA_ROLLBACK]: + # "disabled: false" means safe mode *is* enabled. + safe_mode_config = full_config.get(CONF_SAFE_MODE, {CONF_DISABLED: True}) + safe_mode_enabled = not safe_mode_config[CONF_DISABLED] + ota_enabled = CONF_OTA in full_config + # Both need to be enabled for rollback to work + if not (ota_enabled and safe_mode_enabled): + # But only warn if ota is even possible + if ota_enabled: + _LOGGER.warning( + "OTA rollback requires safe_mode, disabling rollback support" + ) + # disable the rollback feature anyway since it can't be used. + advanced[CONF_ENABLE_OTA_ROLLBACK] = False if errs: raise cv.MultipleInvalid(errs) @@ -589,6 +652,7 @@ CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select" CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir" CONF_FREERTOS_IN_IRAM = "freertos_in_iram" CONF_RINGBUF_IN_IRAM = "ringbuf_in_iram" +CONF_HEAP_IN_IRAM = "heap_in_iram" CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size" # VFS requirement tracking @@ -665,6 +729,9 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean, cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, + cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of( + *ESP32_CHIP_REVISIONS + ), # DHCP server is needed for WiFi AP mode. When WiFi component is used, # it will handle disabling DHCP server when AP is not configured. # Default to false (disabled) when WiFi is not used. @@ -687,10 +754,12 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean, cv.Optional(CONF_FREERTOS_IN_IRAM, default=False): cv.boolean, cv.Optional(CONF_RINGBUF_IN_IRAM, default=False): cv.boolean, + cv.Optional(CONF_HEAP_IN_IRAM, default=False): cv.boolean, cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean, cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range( min=8192, max=32768 ), + cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean, } ), cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( @@ -716,12 +785,14 @@ FRAMEWORK_SCHEMA = cv.Schema( ) +# Remove this class in 2026.7.0 class _FrameworkMigrationWarning: shown = False def _show_framework_migration_message(name: str, variant: str) -> None: - """Show a friendly message about framework migration when defaulting to Arduino.""" + """Show a message about the framework default change and how to switch back to Arduino.""" + # Remove this function in 2026.7.0 if _FrameworkMigrationWarning.shown: return _FrameworkMigrationWarning.shown = True @@ -731,41 +802,27 @@ def _show_framework_migration_message(name: str, variant: str) -> None: message = ( color( AnsiFore.BOLD_CYAN, - f"💡 IMPORTANT: {name} doesn't have a framework specified!", + f"💡 NOTICE: {name} does not have a framework specified.", ) + "\n\n" - + f"Currently, {variant} defaults to the Arduino framework.\n" - + color(AnsiFore.YELLOW, "This will change to ESP-IDF in ESPHome 2026.1.0.\n") + + f"Starting with ESPHome 2026.1.0, the default framework for {variant} is ESP-IDF.\n" + + "(We've been warning about this change since ESPHome 2025.8.0)\n" + "\n" - + "Note: Newer ESP32 variants (C6, H2, P4, etc.) already use ESP-IDF by default.\n" - + "\n" - + "Why change? ESP-IDF offers:\n" - + color(AnsiFore.GREEN, " ✨ Up to 40% smaller binaries\n") - + color(AnsiFore.GREEN, " 🚀 Better performance and optimization\n") + + "Why we made this change:\n" + + color(AnsiFore.GREEN, " ✨ Up to 40% smaller firmware binaries\n") + color(AnsiFore.GREEN, " ⚡ 2-3x faster compile times\n") - + color(AnsiFore.GREEN, " 📦 Custom-built firmware for your exact needs\n") - + color( - AnsiFore.GREEN, - " 🔧 Active development and testing by ESPHome developers\n", - ) + + color(AnsiFore.GREEN, " 🚀 Better performance and newer features\n") + + color(AnsiFore.GREEN, " 🔧 More actively maintained by ESPHome\n") + "\n" - + "Trade-offs:\n" - + color(AnsiFore.YELLOW, " 🔄 Some components need migration\n") + + "To continue using Arduino, add this to your YAML under 'esp32:':\n" + + color(AnsiFore.WHITE, " framework:\n") + + color(AnsiFore.WHITE, " type: arduino\n") + "\n" - + "What should I do?\n" - + color(AnsiFore.CYAN, " Option 1") - + ": Migrate to ESP-IDF (recommended)\n" - + " Add this to your YAML under 'esp32:':\n" - + color(AnsiFore.WHITE, " framework:\n") - + color(AnsiFore.WHITE, " type: esp-idf\n") + + "To silence this message with ESP-IDF, explicitly set:\n" + + color(AnsiFore.WHITE, " framework:\n") + + color(AnsiFore.WHITE, " type: esp-idf\n") + "\n" - + color(AnsiFore.CYAN, " Option 2") - + ": Keep using Arduino (still supported)\n" - + " Add this to your YAML under 'esp32:':\n" - + color(AnsiFore.WHITE, " framework:\n") - + color(AnsiFore.WHITE, " type: arduino\n") - + "\n" - + "Need help? Check out the migration guide:\n" + + "Migration guide: " + color( AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf/", @@ -780,13 +837,13 @@ def _set_default_framework(config): config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({}) if CONF_TYPE not in config[CONF_FRAMEWORK]: variant = config[CONF_VARIANT] + config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF + # Show migration message for variants that previously defaulted to Arduino + # Remove this message in 2026.7.0 if variant in ARDUINO_ALLOWED_VARIANTS: - config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO _show_framework_migration_message( config.get(CONF_NAME, "This device"), variant ) - else: - config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF return config @@ -971,29 +1028,21 @@ async def to_code(config): cg.add_platformio_option("framework", "arduino, espidf") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") - cg.add_platformio_option( - "board_build.embed_txtfiles", - [ - "managed_components/espressif__esp_insights/server_certs/https_server.crt", - "managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt", - "managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt", - "managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt", - ], - ) cg.add_define( "USE_ARDUINO_VERSION_CODE", cg.RawExpression( f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" ), ) - add_idf_sdkconfig_option( - "CONFIG_ARDUINO_LOOP_STACK_SIZE", - conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE], - ) - add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True) add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) - add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True) + + # Add IDF framework source for Arduino builds to ensure it uses the same version as + # the ESP-IDF framework + if (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None: + cg.add_platformio_option( + "platform_packages", [_format_framework_espidf_version(idf_ver, None)] + ) # ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency if get_esp32_variant() == VARIANT_ESP32S2: @@ -1007,6 +1056,16 @@ async def to_code(config): add_idf_sdkconfig_option( f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True ) + + # Set minimum chip revision for ESP32 variant + # Setting this to 3.0 or higher reduces flash size by excluding workaround code, + # and for PSRAM users saves significant IRAM by keeping C library functions in ROM. + if variant == VARIANT_ESP32: + min_rev = conf[CONF_ADVANCED].get(CONF_MINIMUM_CHIP_REVISION) + if min_rev is not None: + for rev, flag in ESP32_CHIP_REVISIONS.items(): + add_idf_sdkconfig_option(flag, rev == min_rev) + cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET") add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv") @@ -1041,6 +1100,12 @@ async def to_code(config): # Place in flash to save IRAM (default) add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True) + # Place heap functions into flash to save IRAM (~4-6KB savings) + # Safe as long as heap functions are not called from ISRs (which they shouldn't be) + # Users can set heap_in_iram: true as an escape hatch if needed + if not conf[CONF_ADVANCED][CONF_HEAP_IN_IRAM]: + add_idf_sdkconfig_option("CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH", True) + # Setup watchdog add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) @@ -1164,6 +1229,11 @@ async def to_code(config): "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True ) + # Enable OTA rollback support + if advanced[CONF_ENABLE_OTA_ROLLBACK]: + add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True) + cg.add_define("USE_OTA_ROLLBACK") + cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE]) cg.add_define( @@ -1193,7 +1263,7 @@ APP_PARTITION_SIZES = { } -def get_arduino_partition_csv(flash_size): +def get_arduino_partition_csv(flash_size: str): app_partition_size = APP_PARTITION_SIZES[flash_size] eeprom_partition_size = 0x1000 # 4 KB spiffs_partition_size = 0xF000 # 60 KB @@ -1213,7 +1283,7 @@ spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size: """ -def get_idf_partition_csv(flash_size): +def get_idf_partition_csv(flash_size: str): app_partition_size = APP_PARTITION_SIZES[flash_size] return f"""\ diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index 514d674b55..8a7a9428db 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1488,6 +1488,10 @@ BOARDS = { "name": "Arduino Nano ESP32", "variant": VARIANT_ESP32S3, }, + "arduino_nesso_n1": { + "name": "Arduino Nesso-N1", + "variant": VARIANT_ESP32C6, + }, "atd147_s3": { "name": "ArtronShop ATD1.47-S3", "variant": VARIANT_ESP32S3, @@ -1656,6 +1660,10 @@ BOARDS = { "name": "Espressif ESP32-C6-DevKitM-1", "variant": VARIANT_ESP32C6, }, + "esp32-c61-devkitc1-n8r2": { + "name": "Espressif ESP32-C61-DevKitC-1 N8R2 (8 MB Flash Quad, 2 MB PSRAM Quad)", + "variant": VARIANT_ESP32C61, + }, "esp32-devkitlipo": { "name": "OLIMEX ESP32-DevKit-LiPo", "variant": VARIANT_ESP32, @@ -1673,11 +1681,15 @@ BOARDS = { "variant": VARIANT_ESP32H2, }, "esp32-p4": { - "name": "Espressif ESP32-P4 generic", + "name": "Espressif ESP32-P4 ES (pre rev.300) generic", "variant": VARIANT_ESP32P4, }, "esp32-p4-evboard": { - "name": "Espressif ESP32-P4 Function EV Board", + "name": "Espressif ESP32-P4 Function EV Board (ES pre rev.300)", + "variant": VARIANT_ESP32P4, + }, + "esp32-p4_r3": { + "name": "Espressif ESP32-P4 rev.300 generic", "variant": VARIANT_ESP32P4, }, "esp32-pico-devkitm-2": { @@ -2093,7 +2105,7 @@ BOARDS = { "variant": VARIANT_ESP32, }, "m5stack-tab5-p4": { - "name": "M5STACK Tab5 esp32-p4 Board", + "name": "M5STACK Tab5 esp32-p4 Board (ES pre rev.300)", "variant": VARIANT_ESP32P4, }, "m5stack-timer-cam": { diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 6215ff862f..09a45c14a6 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -4,25 +4,20 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" -#include -#include +#include +#include #include #include #include #include -#include +#include +#include -#include +void setup(); // NOLINT(readability-redundant-declaration) +void loop(); // NOLINT(readability-redundant-declaration) -#ifdef USE_ARDUINO -#include -#else -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) -#include -#endif -void setup(); -void loop(); -#endif +// Weak stub for initArduino - overridden when the Arduino component is present +extern "C" __attribute__((weak)) void initArduino() {} namespace esphome { @@ -41,29 +36,13 @@ void arch_restart() { void arch_init() { // Enable the task watchdog only on the loop task (from which we're currently running) -#if defined(USE_ESP_IDF) esp_task_wdt_add(nullptr); - // Idle task watchdog is disabled on ESP-IDF -#elif defined(USE_ARDUINO) - enableLoopWDT(); - // Disable idle task watchdog on the core we're using (Arduino pins the task to a core) -#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0 - disableCore0WDT(); -#endif -#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1 - disableCore1WDT(); -#endif -#endif - // If the bootloader was compiled with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE the current - // partition will get rolled back unless it is marked as valid. - esp_ota_img_states_t state; - const esp_partition_t *running = esp_ota_get_running_partition(); - if (esp_ota_get_state_partition(running, &state) == ESP_OK) { - if (state == ESP_OTA_IMG_PENDING_VERIFY) { - esp_ota_mark_app_valid_cancel_rollback(); - } - } + // Handle OTA rollback: mark partition valid immediately unless USE_OTA_ROLLBACK is enabled, + // in which case safe_mode will mark it valid after confirming successful boot. +#ifndef USE_OTA_ROLLBACK + esp_ota_mark_app_valid_cancel_rollback(); +#endif } void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } @@ -71,21 +50,10 @@ uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { uint32_t freq = 0; -#ifdef USE_ESP_IDF -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq); -#else - rtc_cpu_freq_config_t config; - rtc_clk_cpu_freq_get_config(&config); - freq = config.freq_mhz * 1000000U; -#endif -#elif defined(USE_ARDUINO) - freq = ESP.getCpuFreqMHz() * 1000000; -#endif return freq; } -#ifdef USE_ESP_IDF TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void loop_task(void *pv_params) { @@ -96,6 +64,7 @@ void loop_task(void *pv_params) { } extern "C" void app_main() { + initArduino(); esp32::setup_preferences(); #if CONFIG_FREERTOS_UNICORE xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle); @@ -103,11 +72,6 @@ extern "C" void app_main() { xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1); #endif } -#endif // USE_ESP_IDF - -#ifdef USE_ARDUINO -extern "C" void init() { esp32::setup_preferences(); } -#endif // USE_ARDUINO } // namespace esphome diff --git a/esphome/components/esp32/gpio.cpp b/esphome/components/esp32/gpio.cpp index a98245b889..4b53d3a172 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -97,10 +97,8 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi gpio_isr_handler_add(this->get_pin_num(), func, arg); } -std::string ESP32InternalGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast(this->pin_)); - return buffer; +size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%" PRIu32, static_cast(this->pin_)); } void ESP32InternalGPIOPin::setup() { diff --git a/esphome/components/esp32/gpio.h b/esphome/components/esp32/gpio.h index d30f4bdcba..3c13bd9b4f 100644 --- a/esphome/components/esp32/gpio.h +++ b/esphome/components/esp32/gpio.h @@ -24,7 +24,7 @@ class ESP32InternalGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return this->pin_; } diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index c995214232..5ef5860687 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -5,6 +5,7 @@ import json # noqa: E402 import os # noqa: E402 import pathlib # noqa: E402 import shutil # noqa: E402 +from glob import glob # noqa: E402 def merge_factory_bin(source, target, env): @@ -126,3 +127,14 @@ def esp32_copy_ota_bin(source, target, env): # Run merge first, then ota copy second env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821 + +# Find server certificates in managed components and generate .S files. +# Workaround for PlatformIO not processing target_add_binary_data() from managed component CMakeLists. +project_dir = env.subst("$PROJECT_DIR") +managed_components = os.path.join(project_dir, "managed_components") +if os.path.isdir(managed_components): + for cert_file in glob(os.path.join(managed_components, "**/server_certs/*.crt"), recursive=True): + try: + env.FileToAsm(cert_file, FILE_TYPE="TEXT") + except Exception as e: + print(f"Error processing {os.path.basename(cert_file)}: {e}") diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 7bdbb265ca..08439746b6 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -4,26 +4,30 @@ #include "esphome/core/log.h" #include "esphome/core/preferences.h" #include -#include #include -#include -#include +#include #include +#include namespace esphome { namespace esp32 { static const char *const TAG = "esp32.preferences"; +// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding +static constexpr size_t KEY_BUFFER_SIZE = 12; + struct NVSData { - std::string key; + uint32_t key; std::unique_ptr data; size_t len; void set_data(const uint8_t *src, size_t size) { - data = std::make_unique(size); - memcpy(data.get(), src, size); - len = size; + if (!this->data || this->len != size) { + this->data = std::make_unique(size); + this->len = size; + } + memcpy(this->data.get(), src, size); } }; @@ -31,27 +35,27 @@ static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-n class ESP32PreferenceBackend : public ESPPreferenceBackend { public: - std::string key; + uint32_t key; uint32_t nvs_handle; bool save(const uint8_t *data, size_t len) override { // try find in pending saves and update that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { obj.set_data(data, len); return true; } } NVSData save{}; - save.key = key; + save.key = this->key; save.set_data(data, len); s_pending_save.emplace_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); return true; } bool load(uint8_t *data, size_t len) override { // try find in pending saves and load from that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { if (obj.len != len) { // size mismatch return false; @@ -61,22 +65,24 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend { } } + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); size_t actual_len; - esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len); + esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); return false; } if (actual_len != len) { ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); return false; } - err = nvs_get_blob(nvs_handle, key.c_str(), data, &len); + err = nvs_get_blob(this->nvs_handle, key_str, data, &len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); return false; } else { - ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); } return true; } @@ -103,14 +109,12 @@ class ESP32Preferences : public ESPPreferences { } } ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); + return this->make_preference(length, type); } ESPPreferenceObject make_preference(size_t length, uint32_t type) override { auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->nvs_handle = nvs_handle; - - uint32_t keyval = type; - pref->key = str_sprintf("%" PRIu32, keyval); + pref->nvs_handle = this->nvs_handle; + pref->key = type; return ESPPreferenceObject(pref); } @@ -123,17 +127,19 @@ class ESP32Preferences : public ESPPreferences { // goal try write all pending saves even if one fails int cached = 0, written = 0, failed = 0; esp_err_t last_err = ESP_OK; - std::string last_key{}; + uint32_t last_key = 0; // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str()); - if (is_changed(nvs_handle, save)) { - esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len); - ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len); + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); + if (this->is_changed_(this->nvs_handle, save, key_str)) { + esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len); if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err)); failed++; last_err = err; last_key = save.key; @@ -141,7 +147,7 @@ class ESP32Preferences : public ESPPreferences { } written++; } else { - ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len); + ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len); cached++; } s_pending_save.erase(s_pending_save.begin() + i); @@ -149,12 +155,12 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, failed); if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%s", failed, esp_err_to_name(last_err), - last_key.c_str()); + ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err), + last_key); } // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes - esp_err_t err = nvs_commit(nvs_handle); + esp_err_t err = nvs_commit(this->nvs_handle); if (err != 0) { ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); return false; @@ -162,11 +168,13 @@ class ESP32Preferences : public ESPPreferences { return failed == 0; } - bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) { + + protected: + bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { size_t actual_len; - esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len); + esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); return true; } // Check size first before allocating memory @@ -174,9 +182,9 @@ class ESP32Preferences : public ESPPreferences { return true; } auto stored_data = std::make_unique(actual_len); - err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len); + err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); return true; } return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0; diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index a279f7d2a4..87b5e2b738 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -24,7 +24,9 @@ extern "C" { #include #ifdef USE_ARDUINO -#include +// Prevent Arduino from releasing BT memory at startup (esp32-hal-misc.c). +// Without this, esp_bt_controller_init() fails with ESP_ERR_INVALID_STATE. +extern "C" bool btInUse() { return true; } // NOLINT(readability-identifier-naming) #endif namespace esphome::esp32_ble { @@ -165,12 +167,6 @@ void ESP32BLE::advertising_init_() { bool ESP32BLE::ble_setup_() { esp_err_t err; #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID -#ifdef USE_ARDUINO - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return false; - } -#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { @@ -195,7 +191,6 @@ bool ESP32BLE::ble_setup_() { return false; } } -#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); #else @@ -256,8 +251,11 @@ bool ESP32BLE::ble_setup_() { } #endif + // BLE device names are limited to 20 characters + // Buffer: 20 chars + null terminator + constexpr size_t ble_name_max_len = 21; + char name_buffer[ble_name_max_len]; const char *device_name; - std::string name_with_suffix; if (this->name_ != nullptr) { if (App.is_name_add_mac_suffix_enabled()) { @@ -268,23 +266,28 @@ bool ESP32BLE::ble_setup_() { char mac_addr[mac_address_len]; get_mac_address_into_buffer(mac_addr); const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len; - name_with_suffix = - make_name_with_suffix(this->name_, strlen(this->name_), '-', mac_suffix_ptr, mac_address_suffix_len); - device_name = name_with_suffix.c_str(); + make_name_with_suffix_to(name_buffer, sizeof(name_buffer), this->name_, strlen(this->name_), '-', mac_suffix_ptr, + mac_address_suffix_len); + device_name = name_buffer; } else { device_name = this->name_; } } else { - name_with_suffix = App.get_name(); - if (name_with_suffix.length() > 20) { + const std::string &app_name = App.get_name(); + size_t name_len = app_name.length(); + if (name_len > 20) { if (App.is_name_add_mac_suffix_enabled()) { // Keep first 13 chars and last 7 chars (MAC suffix), remove middle - name_with_suffix.erase(13, name_with_suffix.length() - 20); + memcpy(name_buffer, app_name.c_str(), 13); + memcpy(name_buffer + 13, app_name.c_str() + name_len - 7, 7); } else { - name_with_suffix.resize(20); + memcpy(name_buffer, app_name.c_str(), 20); } + name_buffer[20] = '\0'; + } else { + memcpy(name_buffer, app_name.c_str(), name_len + 1); // Include null terminator } - device_name = name_with_suffix.c_str(); + device_name = name_buffer; } err = esp_ble_gap_set_device_name(device_name); @@ -326,12 +329,6 @@ bool ESP32BLE::ble_dismantle_() { } #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID -#ifdef USE_ARDUINO - if (!btStop()) { - ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status()); - return false; - } -#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) { // stop bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) { @@ -355,7 +352,6 @@ bool ESP32BLE::ble_dismantle_() { return false; } } -#endif #else if (esp_hosted_bt_controller_disable() != ESP_OK) { ESP_LOGW(TAG, "esp_hosted_bt_controller_disable failed"); diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index dcbb285e07..334780e3b8 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -39,36 +39,36 @@ ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) { ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i]; return ret; } -ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { +ESPBTUUID ESPBTUUID::from_raw(const char *data, size_t length) { ESPBTUUID ret; - if (data.length() == 4) { + if (length == 4) { // 16-bit UUID as 4-character hex string - auto parsed = parse_hex(data); + auto parsed = parse_hex(data, length); if (parsed.has_value()) { ret.uuid_.len = ESP_UUID_LEN_16; ret.uuid_.uuid.uuid16 = parsed.value(); } - } else if (data.length() == 8) { + } else if (length == 8) { // 32-bit UUID as 8-character hex string - auto parsed = parse_hex(data); + auto parsed = parse_hex(data, length); if (parsed.has_value()) { ret.uuid_.len = ESP_UUID_LEN_32; ret.uuid_.uuid.uuid32 = parsed.value(); } - } else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be - // investigated (lack of time) + } else if (length == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be + // investigated (lack of time) ret.uuid_.len = ESP_UUID_LEN_128; - memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16); - } else if (data.length() == 36) { + memcpy(ret.uuid_.uuid.uuid128, reinterpret_cast(data), 16); + } else if (length == 36) { // If the length of the string is 36 bytes then we will assume it is a long hex string in // UUID format. ret.uuid_.len = ESP_UUID_LEN_128; int n = 0; - for (uint i = 0; i < data.length(); i += 2) { - if (data.c_str()[i] == '-') + for (size_t i = 0; i < length; i += 2) { + if (data[i] == '-') i++; - uint8_t msb = data.c_str()[i]; - uint8_t lsb = data.c_str()[i + 1]; + uint8_t msb = data[i]; + uint8_t lsb = data[i + 1]; if (msb > '9') msb -= 7; @@ -77,7 +77,7 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); } } else { - ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str()); + ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data); } return ret; } @@ -143,9 +143,8 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { return this->as_128bit() == uuid.as_128bit(); } esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; } -std::string ESPBTUUID::to_string() const { - char buf[40]; // Enough for 128-bit UUID with dashes - char *pos = buf; +const char *ESPBTUUID::to_str(std::span output) const { + char *pos = output.data(); switch (this->uuid_.len) { case ESP_UUID_LEN_16: @@ -156,7 +155,7 @@ std::string ESPBTUUID::to_string() const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 4) & 0x0F); *pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 & 0x0F); *pos = '\0'; - return std::string(buf); + return output.data(); case ESP_UUID_LEN_32: *pos++ = '0'; @@ -165,7 +164,7 @@ std::string ESPBTUUID::to_string() const { *pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid32 >> shift) & 0x0F); } *pos = '\0'; - return std::string(buf); + return output.data(); default: case ESP_UUID_LEN_128: @@ -179,9 +178,13 @@ std::string ESPBTUUID::to_string() const { } } *pos = '\0'; - return std::string(buf); + return output.data(); } - return ""; +} +std::string ESPBTUUID::to_string() const { + char buf[UUID_STR_LEN]; + this->to_str(buf); + return std::string(buf); } } // namespace esphome::esp32_ble diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 4cf2d10abd..ae593955a4 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -7,11 +7,16 @@ #ifdef USE_ESP32 #ifdef USE_ESP32_BLE_UUID +#include +#include #include #include namespace esphome::esp32_ble { +/// Buffer size for UUID string: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\0" +static constexpr size_t UUID_STR_LEN = 37; + class ESPBTUUID { public: ESPBTUUID(); @@ -23,7 +28,12 @@ class ESPBTUUID { static ESPBTUUID from_raw(const uint8_t *data); static ESPBTUUID from_raw_reversed(const uint8_t *data); - static ESPBTUUID from_raw(const std::string &data); + static ESPBTUUID from_raw(const char *data, size_t length); + static ESPBTUUID from_raw(const char *data) { return from_raw(data, strlen(data)); } + static ESPBTUUID from_raw(const std::string &data) { return from_raw(data.c_str(), data.length()); } + static ESPBTUUID from_raw(std::initializer_list data) { + return from_raw(reinterpret_cast(data.begin()), data.size()); + } static ESPBTUUID from_uuid(esp_bt_uuid_t uuid); @@ -37,6 +47,7 @@ class ESPBTUUID { esp_bt_uuid_t get_uuid() const; std::string to_string() const; + const char *to_str(std::span output) const; protected: esp_bt_uuid_t uuid_; diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp index e0d0174c57..e830702f11 100644 --- a/esphome/components/esp32_ble_client/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -50,8 +50,12 @@ void BLECharacteristic::parse_descriptors() { desc->handle = result.handle; desc->characteristic = this; this->descriptors.push_back(desc); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[espbt::UUID_STR_LEN]; + desc->uuid.to_str(uuid_buf); ESP_LOGV(TAG, "[%d] [%s] descriptor %s, handle 0x%x", this->service->client->get_connection_index(), - this->service->client->address_str(), desc->uuid.to_string().c_str(), desc->handle); + this->service->client->address_str(), uuid_buf, desc->handle); +#endif offset++; } } diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 07e88c7528..149fcc79d5 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -70,9 +70,9 @@ float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_B void BLEClientBase::dump_config() { ESP_LOGCONFIG(TAG, " Address: %s\n" - " Auto-Connect: %s", - this->address_str(), TRUEFALSE(this->auto_connect_)); - ESP_LOGCONFIG(TAG, " State: %s", espbt::client_state_to_string(this->state())); + " Auto-Connect: %s\n" + " State: %s", + this->address_str(), TRUEFALSE(this->auto_connect_), espbt::client_state_to_string(this->state())); if (this->status_ == ESP_GATT_NO_RESOURCES) { ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config."); } else if (this->status_ != ESP_GATT_OK) { @@ -411,12 +411,17 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium"); } else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) { #ifdef USE_ESP32_BLE_DEVICE +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE for (auto &svc : this->services_) { - ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_, - svc->uuid.to_string().c_str()); - ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_, + char uuid_buf[espbt::UUID_STR_LEN]; + svc->uuid.to_str(uuid_buf); + ESP_LOGV(TAG, + "[%d] [%s] Service UUID: %s\n" + "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", + this->connection_index_, this->address_str_, uuid_buf, this->connection_index_, this->address_str_, svc->start_handle, svc->end_handle); } +#endif #endif } ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_); @@ -524,10 +529,9 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ case ESP_GAP_BLE_AUTH_CMPL_EVT: if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr)) return; - esp_bd_addr_t bd_addr; - memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, - format_hex(bd_addr, 6).c_str()); + char addr_str[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(param->ble_security.auth_cmpl.bd_addr, addr_str); + ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, addr_str); if (!param->ble_security.auth_cmpl.success) { this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason); } else { diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 7786495915..92c7444ee1 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -22,7 +22,6 @@ namespace esphome::esp32_ble_client { namespace espbt = esphome::esp32_ble_tracker; static const int UNSET_CONN_ID = 0xFFFF; -static constexpr size_t MAC_ADDR_STR_LEN = 18; // "AA:BB:CC:DD:EE:FF\0" class BLEClientBase : public espbt::ESPBTClient, public Component { public: @@ -111,8 +110,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { esp_gatt_status_t status_{ESP_GATT_OK}; // Group 4: Arrays - char address_str_[MAC_ADDR_STR_LEN]{}; // 18 bytes: "AA:BB:CC:DD:EE:FF\0" - esp_bd_addr_t remote_bda_; // 6 bytes + char address_str_[MAC_ADDRESS_PRETTY_BUFFER_SIZE]{}; + esp_bd_addr_t remote_bda_; // 6 bytes // Group 5: 2-byte types uint16_t conn_id_{UNSET_CONN_ID}; diff --git a/esphome/components/esp32_ble_client/ble_service.cpp b/esphome/components/esp32_ble_client/ble_service.cpp index deaaa3de02..695f468c5b 100644 --- a/esphome/components/esp32_ble_client/ble_service.cpp +++ b/esphome/components/esp32_ble_client/ble_service.cpp @@ -64,9 +64,12 @@ void BLEService::parse_characteristics() { characteristic->handle = result.char_handle; characteristic->service = this; this->characteristics.push_back(characteristic); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[espbt::UUID_STR_LEN]; + characteristic->uuid.to_str(uuid_buf); ESP_LOGV(TAG, "[%d] [%s] characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(), - this->client->address_str(), characteristic->uuid.to_string().c_str(), characteristic->handle, - characteristic->properties); + this->client->address_str(), uuid_buf, characteristic->handle, characteristic->properties); +#endif offset++; } } diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 7627a58338..0482848ea0 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -109,7 +109,11 @@ void BLECharacteristic::do_create(BLEService *service) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; - ESP_LOGV(TAG, "Creating characteristic - %s", this->uuid_.to_string().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + this->uuid_.to_str(uuid_buf); + ESP_LOGV(TAG, "Creating characteristic - %s", uuid_buf); +#endif esp_bt_uuid_t uuid = this->uuid_.get_uuid(); esp_err_t err = esp_ble_gatts_add_char(service->get_handle(), &uuid, static_cast(this->permissions_), diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index 2d053c09bd..4ffca7312b 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -34,7 +34,11 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_AUTO_RSP; - ESP_LOGV(TAG, "Creating descriptor - %s", this->uuid_.to_string().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + this->uuid_.to_str(uuid_buf); + ESP_LOGV(TAG, "Creating descriptor - %s", uuid_buf); +#endif esp_bt_uuid_t uuid = this->uuid_.get_uuid(); esp_err_t err = esp_ble_gatts_add_char_descr(this->characteristic_->get_service()->get_handle(), &uuid, this->permissions_, &this->value_, &control); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 0e58224a5a..2c13a8ac36 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -106,7 +106,11 @@ void BLEServer::restart_advertising_() { } BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles) { - ESP_LOGV(TAG, "Creating BLE service - %s", uuid.to_string().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(uuid_buf); + ESP_LOGV(TAG, "Creating BLE service - %s", uuid_buf); +#endif // Calculate the inst_id for the service uint8_t inst_id = 0; for (; inst_id < 0xFF; inst_id++) { @@ -115,7 +119,9 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n } } if (inst_id == 0xFF) { - ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", uuid.to_string().c_str()); + char warn_uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(warn_uuid_buf); + ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", warn_uuid_buf); return nullptr; } BLEService *service = // NOLINT(cppcoreguidelines-owning-memory) @@ -128,7 +134,11 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n } void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) { - ESP_LOGV(TAG, "Removing BLE service - %s %d", uuid.to_string().c_str(), inst_id); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(uuid_buf); + ESP_LOGV(TAG, "Removing BLE service - %s %d", uuid_buf, inst_id); +#endif for (auto it = this->services_.begin(); it != this->services_.end(); ++it) { if (it->uuid == uuid && it->inst_id == inst_id) { it->service->do_delete(); @@ -137,7 +147,9 @@ void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) { return; } } - ESP_LOGW(TAG, "BLE service %s %d does not exist", uuid.to_string().c_str(), inst_id); + char warn_uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(warn_uuid_buf); + ESP_LOGW(TAG, "BLE service %s %d does not exist", warn_uuid_buf, inst_id); } BLEService *BLEServer::get_service(ESPBTUUID uuid, uint8_t inst_id) { diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 4e25434aad..37e74672ed 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -5,7 +5,7 @@ import logging from esphome import automation import esphome.codegen as cg -from esphome.components import esp32_ble +from esphome.components import esp32_ble, ota from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32_ble import ( IDF_MAX_CONNECTIONS, @@ -328,7 +328,7 @@ async def to_code(config): # Note: CONFIG_BT_ACL_CONNECTIONS and CONFIG_BTDM_CTRL_BLE_MAX_CONN are now # configured in esp32_ble component based on max_connections setting - cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts + ota.request_ota_state_listeners() # To be notified when an OTA update starts cg.add_define("USE_ESP32_BLE_CLIENT") CORE.add_job(_add_ble_features) diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index bbf7992fa4..6d26040ccb 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -98,7 +98,13 @@ template class ESP32BLEStartScanAction : public Action { TEMPLATABLE_VALUE(bool, continuous) void play(const Ts &...x) override { this->parent_->set_scan_continuous(this->continuous_.value(x...)); - this->parent_->start_scan(); + // Only call start_scan() if scanner is IDLE + // For other states (STARTING, RUNNING, STOPPING, FAILED), the normal state + // machine flow will eventually transition back to IDLE, at which point + // loop() will see scan_continuous_ and restart scanning if it is true. + if (this->parent_->get_scanner_state() == ScannerState::IDLE) { + this->parent_->start_scan(); + } } protected: diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 45e343c0d2..995755ac84 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -37,6 +37,9 @@ namespace esphome::esp32_ble_tracker { static const char *const TAG = "esp32_ble_tracker"; +// BLE advertisement max: 31 bytes adv data + 31 bytes scan response +static constexpr size_t BLE_ADV_MAX_LOG_BYTES = 62; + ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) const char *client_state_to_string(ClientState state) { @@ -71,21 +74,24 @@ void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; -#ifdef USE_OTA - ota::get_global_ota_callback()->add_on_state_callback( - [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { - if (state == ota::OTA_STARTED) { - this->stop_scan(); -#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT - for (auto *client : this->clients_) { - client->disconnect(); - } -#endif - } - }); +#ifdef USE_OTA_STATE_LISTENER + ota::get_global_ota_callback()->add_global_state_listener(this); #endif } +#ifdef USE_OTA_STATE_LISTENER +void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { + this->stop_scan(); +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + for (auto *client : this->clients_) { + client->disconnect(); + } +#endif + } +} +#endif + void ESP32BLETracker::loop() { if (!this->parent_->is_active()) { this->ble_was_disabled_ = true; @@ -438,29 +444,38 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_); } for (auto &uuid : this->service_uuids_) { - ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + uuid.to_str(uuid_buf); + ESP_LOGVV(TAG, " Service UUID: %s", uuid_buf); } + char hex_buf[format_hex_pretty_size(BLE_ADV_MAX_LOG_BYTES)]; for (auto &data : this->manufacturer_datas_) { auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data); if (ibeacon.has_value()) { ESP_LOGVV(TAG, " Manufacturer iBeacon:"); - ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + ibeacon.value().get_uuid().to_str(uuid_buf); + ESP_LOGVV(TAG, " UUID: %s", uuid_buf); ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major()); ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor()); ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power()); } else { - ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(), - format_hex_pretty(data.data).c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + data.uuid.to_str(uuid_buf); + ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf, + format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } } for (auto &data : this->service_datas_) { ESP_LOGVV(TAG, " Service data:"); - ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str()); - ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); + char uuid_buf[esp32_ble::UUID_STR_LEN]; + data.uuid.to_str(uuid_buf); + ESP_LOGVV(TAG, " UUID: %s", uuid_buf); + ESP_LOGVV(TAG, " Data: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } ESP_LOGVV(TAG, " Adv data: %s", - format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str()); + format_hex_pretty_to(hex_buf, scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len)); #endif } @@ -624,9 +639,8 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) { } std::string ESPBTDevice::address_str() const { - char mac[18]; - format_mac_addr_upper(this->address_, mac); - return mac; + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + return this->address_str_to(buf); } uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); } @@ -642,8 +656,10 @@ void ESP32BLETracker::dump_config() { " Continuous Scanning: %s", this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f, this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_)); - ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_)); - ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting, + ESP_LOGCONFIG(TAG, + " Scanner State: %s\n" + " Connecting: %d, discovered: %d, disconnecting: %d", + this->scanner_state_to_string_(this->scanner_state_), this->client_state_counts_.connecting, this->client_state_counts_.discovered, this->client_state_counts_.disconnecting); if (this->scan_start_fail_count_) { ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_); @@ -659,7 +675,8 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { } this->already_discovered_.push_back(address); - ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str().c_str(), device.get_rssi()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str_to(addr_buf), device.get_rssi()); const char *address_type_s; switch (device.get_address_type()) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 92d13a62ad..f538a0eddc 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -6,6 +6,7 @@ #include "esphome/core/helpers.h" #include +#include #include #include @@ -22,6 +23,10 @@ #include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/components/esp32_ble/ble_scan_result.h" +#ifdef USE_OTA_STATE_LISTENER +#include "esphome/components/ota/ota_backend.h" +#endif + namespace esphome::esp32_ble_tracker { using namespace esp32_ble; @@ -69,6 +74,12 @@ class ESPBTDevice { std::string address_str() const; + /// Format MAC address into provided buffer, returns pointer to buffer for convenience + const char *address_str_to(std::span buf) const { + format_mac_addr_upper(this->address_, buf.data()); + return buf.data(); + } + uint64_t address_uint64() const; const uint8_t *address() const { return address_; } @@ -241,6 +252,9 @@ class ESP32BLETracker : public Component, public GAPScanEventHandler, public GATTcEventHandler, public BLEStatusEventHandler, +#ifdef USE_OTA_STATE_LISTENER + public ota::OTAGlobalStateListener, +#endif public Parented { public: void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; } @@ -274,6 +288,10 @@ class ESP32BLETracker : public Component, void gap_scan_event_handler(const BLEScanResult &scan_result) override; void ble_before_disabled_event_handler() override; +#ifdef USE_OTA_STATE_LISTENER + void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; +#endif + /// Add a listener for scanner state changes void add_scanner_state_listener(BLEScannerStateListener *listener) { this->scanner_state_listeners_.push_back(listener); diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index ca37cb392d..db6244fb3f 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -2,7 +2,7 @@ import logging from esphome import automation, pins import esphome.codegen as cg -from esphome.components import i2c +from esphome.components import i2c, socket from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option from esphome.components.psram import DOMAIN as psram_domain import esphome.config_validation as cv @@ -27,7 +27,7 @@ import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) -AUTO_LOAD = ["camera"] +AUTO_LOAD = ["camera", "socket"] DEPENDENCIES = ["esp32"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") @@ -324,6 +324,7 @@ SETTERS = { async def to_code(config): cg.add_define("USE_CAMERA") + socket.require_wake_loop_threadsafe() var = cg.new_Pvariable(config[CONF_ID]) await setup_entity(var, config, "camera") await cg.register_component(var, config) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index a3677330ca..5466d2e7ef 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -11,6 +11,7 @@ namespace esphome { namespace esp32_camera { static const char *const TAG = "esp32_camera"; +static constexpr size_t FRAMEBUFFER_TASK_STACK_SIZE = 1792; #if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000; #endif @@ -42,12 +43,12 @@ void ESP32Camera::setup() { this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, - "framebuffer_task", // name - 1024, // stack size - this, // task pv params - 1, // priority - nullptr, // handle - 1 // core + "framebuffer_task", // name + FRAMEBUFFER_TASK_STACK_SIZE, // stack size + this, // task pv params + 1, // priority + nullptr, // handle + 1 // core ); } @@ -167,6 +168,19 @@ void ESP32Camera::dump_config() { } void ESP32Camera::loop() { + // Fast path: skip all work when truly idle + // (no current image, no pending requests, and not time for idle request yet) + const uint32_t now = App.get_loop_component_start_time(); + if (!this->current_image_ && !this->has_requested_image_()) { + // Only check idle interval when we're otherwise idle + if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { + this->last_idle_request_ = now; + this->request_image(camera::IDLE); + } else { + return; + } + } + // check if we can return the image if (this->can_return_image_()) { // return image @@ -175,13 +189,6 @@ void ESP32Camera::loop() { this->current_image_.reset(); } - // request idle image every idle_update_interval - const uint32_t now = App.get_loop_component_start_time(); - if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { - this->last_idle_request_ = now; - this->request_image(camera::IDLE); - } - // Check if we should fetch a new image if (!this->has_requested_image_()) return; @@ -421,6 +428,12 @@ void ESP32Camera::framebuffer_task(void *pv) { while (true) { camera_fb_t *framebuffer = esp_camera_fb_get(); xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); + // Only wake the main loop if there's a pending request to consume the frame +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + if (that->has_requested_image_()) { + App.wake_loop_threadsafe(); + } +#endif // return is no-op for config with 1 fb xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); esp_camera_fb_return(framebuffer); diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index a49fca6511..e97eb27c70 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -2,6 +2,7 @@ #ifdef USE_ESP32 +#include #include #include #include @@ -205,8 +206,8 @@ class ESP32Camera : public camera::Camera { esp_err_t init_error_{ESP_OK}; std::shared_ptr current_image_; - uint8_t single_requesters_{0}; - uint8_t stream_requesters_{0}; + std::atomic single_requesters_{0}; + std::atomic stream_requesters_{0}; QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; std::vector listeners_; diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index 0899a0dc2b..0768b35507 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -19,6 +19,7 @@ from esphome.components.esp32 import ( import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_MODE, CONF_RX_PIN, CONF_RX_QUEUE_LEN, CONF_TX_PIN, @@ -33,6 +34,13 @@ CONF_TX_ENQUEUE_TIMEOUT = "tx_enqueue_timeout" esp32_can_ns = cg.esphome_ns.namespace("esp32_can") esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent) +# Mode options - consistent with MCP2515 component +CanMode = esp32_can_ns.enum("CanMode") +CAN_MODES = { + "NORMAL": CanMode.CAN_MODE_NORMAL, + "LISTENONLY": CanMode.CAN_MODE_LISTEN_ONLY, +} + # Currently the driver only supports a subset of the bit rates defined in canbus # The supported bit rates differ between ESP32 variants. # See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI) @@ -95,6 +103,7 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate, cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_MODE, default="NORMAL"): cv.enum(CAN_MODES, upper=True), cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t, cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t, cv.Optional(CONF_TX_ENQUEUE_TIMEOUT): cv.positive_time_period_milliseconds, @@ -117,6 +126,7 @@ async def to_code(config): cg.add(var.set_rx(config[CONF_RX_PIN])) cg.add(var.set_tx(config[CONF_TX_PIN])) + cg.add(var.set_mode(config[CONF_MODE])) if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None: cg.add(var.set_rx_queue_len(rx_queue_len)) if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None: diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index d50964187d..f521b63430 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -75,8 +75,15 @@ bool ESP32Can::setup_internal() { return false; } + // Select TWAI mode based on configuration + twai_mode_t twai_mode = (this->mode_ == CAN_MODE_LISTEN_ONLY) ? TWAI_MODE_LISTEN_ONLY : TWAI_MODE_NORMAL; + + if (this->mode_ == CAN_MODE_LISTEN_ONLY) { + ESP_LOGI(TAG, "CAN bus configured in LISTEN_ONLY mode (passive, no ACKs)"); + } + twai_general_config_t g_config = - TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); + TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, twai_mode); g_config.controller_id = next_twai_ctrl_num++; if (this->tx_queue_len_.has_value()) { g_config.tx_queue_len = this->tx_queue_len_.value(); @@ -111,6 +118,12 @@ bool ESP32Can::setup_internal() { } canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { + // In listen-only mode, we cannot transmit + if (this->mode_ == CAN_MODE_LISTEN_ONLY) { + ESP_LOGW(TAG, "Cannot send messages in LISTEN_ONLY mode"); + return canbus::ERROR_FAIL; + } + if (this->twai_handle_ == nullptr) { // not setup yet or setup failed return canbus::ERROR_FAIL; diff --git a/esphome/components/esp32_can/esp32_can.h b/esphome/components/esp32_can/esp32_can.h index dc44aceb36..c3f200271b 100644 --- a/esphome/components/esp32_can/esp32_can.h +++ b/esphome/components/esp32_can/esp32_can.h @@ -10,10 +10,16 @@ namespace esphome { namespace esp32_can { +enum CanMode : uint8_t { + CAN_MODE_NORMAL = 0, + CAN_MODE_LISTEN_ONLY = 1, +}; + class ESP32Can : public canbus::Canbus { public: void set_rx(int rx) { rx_ = rx; } void set_tx(int tx) { tx_ = tx; } + void set_mode(CanMode mode) { mode_ = mode; } void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; } void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; } void set_tx_enqueue_timeout_ms(uint32_t tx_enqueue_timeout_ms) { @@ -28,6 +34,7 @@ class ESP32Can : public canbus::Canbus { int rx_{-1}; int tx_{-1}; + CanMode mode_{CAN_MODE_NORMAL}; TickType_t tx_enqueue_timeout_ticks_{}; optional tx_queue_len_{}; optional rx_queue_len_{}; diff --git a/esphome/components/esp32_hosted/__init__.py b/esphome/components/esp32_hosted/__init__.py index 9c9d1d4bb4..e40431c851 100644 --- a/esphome/components/esp32_hosted/__init__.py +++ b/esphome/components/esp32_hosted/__init__.py @@ -93,9 +93,9 @@ async def to_code(config): framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}" if framework_ver >= cv.Version(5, 5, 0): - esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2") - esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3") - esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0") + esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.4") + esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.4") + esp32.add_idf_component(name="espressif/esp_hosted", ref="2.9.3") else: esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0") esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0") diff --git a/esphome/components/esp32_hosted/update/__init__.py b/esphome/components/esp32_hosted/update/__init__.py index fff0d3623a..b258a26b08 100644 --- a/esphome/components/esp32_hosted/update/__init__.py +++ b/esphome/components/esp32_hosted/update/__init__.py @@ -4,18 +4,24 @@ from typing import Any import esphome.codegen as cg from esphome.components import esp32, update import esphome.config_validation as cv -from esphome.const import CONF_PATH, CONF_RAW_DATA_ID -from esphome.core import CORE, HexInt +from esphome.const import CONF_ID, CONF_PATH, CONF_SOURCE, CONF_TYPE +from esphome.core import CORE, ID, HexInt CODEOWNERS = ["@swoboda1337"] -AUTO_LOAD = ["sha256", "watchdog"] +AUTO_LOAD = ["sha256", "watchdog", "json"] DEPENDENCIES = ["esp32_hosted"] CONF_SHA256 = "sha256" +CONF_HTTP_REQUEST_ID = "http_request_id" + +TYPE_EMBEDDED = "embedded" +TYPE_HTTP = "http" esp32_hosted_ns = cg.esphome_ns.namespace("esp32_hosted") +http_request_ns = cg.esphome_ns.namespace("http_request") +HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component) Esp32HostedUpdate = esp32_hosted_ns.class_( - "Esp32HostedUpdate", update.UpdateEntity, cg.Component + "Esp32HostedUpdate", update.UpdateEntity, cg.PollingComponent ) @@ -30,12 +36,29 @@ def _validate_sha256(value: Any) -> str: return value +BASE_SCHEMA = update.update_schema(Esp32HostedUpdate, device_class="firmware").extend( + cv.polling_component_schema("6h") +) + +EMBEDDED_SCHEMA = BASE_SCHEMA.extend( + { + cv.Required(CONF_PATH): cv.file_, + cv.Required(CONF_SHA256): _validate_sha256, + } +) + +HTTP_SCHEMA = BASE_SCHEMA.extend( + { + cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent), + cv.Required(CONF_SOURCE): cv.url, + } +) + CONFIG_SCHEMA = cv.All( - update.update_schema(Esp32HostedUpdate, device_class="firmware").extend( + cv.typed_schema( { - cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), - cv.Required(CONF_PATH): cv.file_, - cv.Required(CONF_SHA256): _validate_sha256, + TYPE_EMBEDDED: EMBEDDED_SCHEMA, + TYPE_HTTP: HTTP_SCHEMA, } ), esp32.only_on_variant( @@ -48,6 +71,9 @@ CONFIG_SCHEMA = cv.All( def _validate_firmware(config: dict[str, Any]) -> None: + if config[CONF_TYPE] != TYPE_EMBEDDED: + return + path = CORE.relative_config_path(config[CONF_PATH]) with open(path, "rb") as f: firmware_data = f.read() @@ -65,14 +91,22 @@ FINAL_VALIDATE_SCHEMA = _validate_firmware async def to_code(config: dict[str, Any]) -> None: var = await update.new_update(config) - path = config[CONF_PATH] - with open(CORE.relative_config_path(path), "rb") as f: - firmware_data = f.read() - rhs = [HexInt(x) for x in firmware_data] - prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + if config[CONF_TYPE] == TYPE_EMBEDDED: + path = config[CONF_PATH] + with open(CORE.relative_config_path(path), "rb") as f: + firmware_data = f.read() + rhs = [HexInt(x) for x in firmware_data] + arr_id = ID(f"{config[CONF_ID]}_data", is_declaration=True, type=cg.uint8) + prog_arr = cg.progmem_array(arr_id, rhs) + + sha256_bytes = bytes.fromhex(config[CONF_SHA256]) + cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes])) + cg.add(var.set_firmware_data(prog_arr)) + cg.add(var.set_firmware_size(len(firmware_data))) + else: + http_request_var = await cg.get_variable(config[CONF_HTTP_REQUEST_ID]) + cg.add(var.set_http_request_parent(http_request_var)) + cg.add(var.set_source_url(config[CONF_SOURCE])) + cg.add_define("USE_ESP32_HOSTED_HTTP_UPDATE") - sha256_bytes = bytes.fromhex(config[CONF_SHA256]) - cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes])) - cg.add(var.set_firmware_data(prog_arr)) - cg.add(var.set_firmware_size(len(firmware_data))) await cg.register_component(var, config) diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index de130ca71f..9f8ae3277e 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -7,6 +7,13 @@ #include #include #include +#include +#include + +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE +#include "esphome/components/json/json_util.h" +#include "esphome/components/network/util.h" +#endif extern "C" { #include @@ -16,18 +23,50 @@ namespace esphome::esp32_hosted { static const char *const TAG = "esp32_hosted.update"; -// older coprocessor firmware versions have a 1500-byte limit per RPC call +// Older coprocessor firmware versions have a 1500-byte limit per RPC call constexpr size_t CHUNK_SIZE = 1500; +// Compile-time version string from esp_hosted_host_fw_ver.h macros +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) +static const char *const ESP_HOSTED_VERSION_STR = STRINGIFY(ESP_HOSTED_VERSION_MAJOR_1) "." STRINGIFY( + ESP_HOSTED_VERSION_MINOR_1) "." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1); + +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE +// Parse version string "major.minor.patch" into components +// Returns true if parsing succeeded +static bool parse_version(const std::string &version_str, int &major, int &minor, int &patch) { + major = minor = patch = 0; + if (sscanf(version_str.c_str(), "%d.%d.%d", &major, &minor, &patch) >= 2) { + return true; + } + return false; +} + +// Compare two versions, returns: +// -1 if v1 < v2 +// 0 if v1 == v2 +// 1 if v1 > v2 +static int compare_versions(int major1, int minor1, int patch1, int major2, int minor2, int patch2) { + if (major1 != major2) + return major1 < major2 ? -1 : 1; + if (minor1 != minor2) + return minor1 < minor2 ? -1 : 1; + if (patch1 != patch2) + return patch1 < patch2 ? -1 : 1; + return 0; +} +#endif + void Esp32HostedUpdate::setup() { this->update_info_.title = "ESP32 Hosted Coprocessor"; - // if wifi is not present, connect to the coprocessor #ifndef USE_WIFI + // If WiFi is not present, connect to the coprocessor esp_hosted_connect_to_slave(); // NOLINT #endif - // get coprocessor version + // Get coprocessor version esp_hosted_coprocessor_fwver_t ver_info; if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) { this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1); @@ -36,16 +75,19 @@ void Esp32HostedUpdate::setup() { } ESP_LOGD(TAG, "Coprocessor version: %s", this->update_info_.current_version.c_str()); - // get image version +#ifndef USE_ESP32_HOSTED_HTTP_UPDATE + // Embedded mode: get image version from embedded firmware const int app_desc_offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t); if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) { esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset); if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) { - ESP_LOGD(TAG, "Firmware version: %s", app_desc->version); - ESP_LOGD(TAG, "Project name: %s", app_desc->project_name); - ESP_LOGD(TAG, "Build date: %s", app_desc->date); - ESP_LOGD(TAG, "Build time: %s", app_desc->time); - ESP_LOGD(TAG, "IDF version: %s", app_desc->idf_ver); + ESP_LOGD(TAG, + "Firmware version: %s\n" + "Project name: %s\n" + "Build date: %s\n" + "Build time: %s\n" + "IDF version: %s", + app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver); this->update_info_.latest_version = app_desc->version; if (this->update_info_.latest_version != this->update_info_.current_version) { this->state_ = update::UPDATE_STATE_AVAILABLE; @@ -62,57 +104,272 @@ void Esp32HostedUpdate::setup() { this->state_ = update::UPDATE_STATE_NO_UPDATE; } - // publish state + // Publish state this->status_clear_error(); this->publish_state(); +#else + // HTTP mode: retry initial check every 10s until network is ready (max 6 attempts) + // Only if update interval is > 1 minute to avoid redundant checks + if (this->get_update_interval() > 60000) { + this->set_retry("initial_check", 10000, 6, [this](uint8_t) { + if (!network::is_connected()) { + return RetryResult::RETRY; + } + this->check(); + return RetryResult::DONE; + }); + } +#endif } void Esp32HostedUpdate::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Hosted Update:\n" - " Current Version: %s\n" - " Latest Version: %s\n" - " Latest Size: %zu bytes", - this->update_info_.current_version.c_str(), this->update_info_.latest_version.c_str(), + " Host Library Version: %s\n" + " Coprocessor Version: %s\n" + " Latest Version: %s", + ESP_HOSTED_VERSION_STR, this->update_info_.current_version.c_str(), + this->update_info_.latest_version.c_str()); +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE + ESP_LOGCONFIG(TAG, + " Mode: HTTP\n" + " Source URL: %s", + this->source_url_.c_str()); +#else + ESP_LOGCONFIG(TAG, + " Mode: Embedded\n" + " Firmware Size: %zu bytes", this->firmware_size_); +#endif } -void Esp32HostedUpdate::perform(bool force) { - if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) { - ESP_LOGW(TAG, "Update not available"); +void Esp32HostedUpdate::check() { +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE + if (!network::is_connected()) { + ESP_LOGD(TAG, "Network not connected, skipping update check"); return; } + if (!this->fetch_manifest_()) { + return; + } + + // Compare versions + if (this->update_info_.latest_version.empty() || + this->update_info_.latest_version == this->update_info_.current_version) { + this->state_ = update::UPDATE_STATE_NO_UPDATE; + } else { + this->state_ = update::UPDATE_STATE_AVAILABLE; + } + + this->update_info_.has_progress = false; + this->update_info_.progress = 0.0f; + this->status_clear_error(); + this->publish_state(); +#endif +} + +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE +bool Esp32HostedUpdate::fetch_manifest_() { + ESP_LOGD(TAG, "Fetching manifest"); + + auto container = this->http_request_parent_->get(this->source_url_); + if (container == nullptr || container->status_code != 200) { + ESP_LOGE(TAG, "Failed to fetch manifest from %s", this->source_url_.c_str()); + this->status_set_error(LOG_STR("Failed to fetch manifest")); + return false; + } + + // Read manifest JSON into string (manifest is small, ~1KB max) + std::string json_str; + json_str.reserve(container->content_length); + uint8_t buf[256]; + while (container->get_bytes_read() < container->content_length) { + int read = container->read(buf, sizeof(buf)); + if (read > 0) { + json_str.append(reinterpret_cast(buf), read); + } + yield(); + } + container->end(); + + // Parse JSON manifest + // Format: {"versions": [{"version": "2.7.0", "url": "...", "sha256": "..."}]} + // Only consider versions <= host library version to avoid compatibility issues + bool valid = json::parse_json(json_str, [this](JsonObject root) -> bool { + if (!root["versions"].is()) { + ESP_LOGE(TAG, "Manifest does not contain 'versions' array"); + return false; + } + + JsonArray versions = root["versions"].as(); + if (versions.size() == 0) { + ESP_LOGE(TAG, "Manifest 'versions' array is empty"); + return false; + } + + // Find the highest version that is compatible with the host library + // (version <= host version to avoid upgrading coprocessor ahead of host) + int best_major = -1, best_minor = -1, best_patch = -1; + std::string best_version, best_url, best_sha256; + + for (JsonObject entry : versions) { + if (!entry["version"].is() || !entry["url"].is() || + !entry["sha256"].is()) { + continue; // Skip malformed entries + } + + std::string ver_str = entry["version"].as(); + int major, minor, patch; + if (!parse_version(ver_str, major, minor, patch)) { + ESP_LOGW(TAG, "Failed to parse version: %s", ver_str.c_str()); + continue; + } + + // Check if this version is compatible (not newer than host) + if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1, + ESP_HOSTED_VERSION_PATCH_1) > 0) { + continue; + } + + // Check if this is better than our current best + if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) { + best_major = major; + best_minor = minor; + best_patch = patch; + best_version = ver_str; + best_url = entry["url"].as(); + best_sha256 = entry["sha256"].as(); + } + } + + if (best_major < 0) { + ESP_LOGW(TAG, "No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR); + return false; + } + + this->update_info_.latest_version = best_version; + this->firmware_url_ = best_url; + + // Parse SHA256 hex string to bytes + if (!parse_hex(best_sha256, this->firmware_sha256_.data(), 32)) { + ESP_LOGE(TAG, "Invalid SHA256: %s", best_sha256.c_str()); + return false; + } + + ESP_LOGD(TAG, "Best compatible version: %s", this->update_info_.latest_version.c_str()); + + return true; + }); + + if (!valid) { + ESP_LOGE(TAG, "Failed to parse manifest JSON"); + this->status_set_error(LOG_STR("Failed to parse manifest")); + return false; + } + + return true; +} + +bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() { + ESP_LOGI(TAG, "Downloading firmware"); + + auto container = this->http_request_parent_->get(this->firmware_url_); + if (container == nullptr || container->status_code != 200) { + ESP_LOGE(TAG, "Failed to fetch firmware"); + this->status_set_error(LOG_STR("Failed to fetch firmware")); + return false; + } + + size_t total_size = container->content_length; + ESP_LOGI(TAG, "Firmware size: %zu bytes", total_size); + + // Begin OTA on coprocessor + esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err)); + container->end(); + this->status_set_error(LOG_STR("Failed to begin OTA")); + return false; + } + + // Stream firmware to coprocessor while computing SHA256 + // Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+) + alignas(32) sha256::SHA256 hasher; + hasher.init(); + + uint8_t buffer[CHUNK_SIZE]; + while (container->get_bytes_read() < total_size) { + int read = container->read(buffer, sizeof(buffer)); + + // Feed watchdog and give other tasks a chance to run + App.feed_wdt(); + yield(); + + // Exit loop if no data available (stream closed or end of data) + if (read <= 0) { + if (read < 0) { + ESP_LOGE(TAG, "Stream closed with error"); + esp_hosted_slave_ota_end(); // NOLINT + container->end(); + this->status_set_error(LOG_STR("Download failed")); + return false; + } + // read == 0: no more data available, exit loop + break; + } + + hasher.add(buffer, read); + err = esp_hosted_slave_ota_write(buffer, read); // NOLINT + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err)); + esp_hosted_slave_ota_end(); // NOLINT + container->end(); + this->status_set_error(LOG_STR("Failed to write OTA data")); + return false; + } + } + container->end(); + + // Verify SHA256 + hasher.calculate(); + if (!hasher.equals_bytes(this->firmware_sha256_.data())) { + ESP_LOGE(TAG, "SHA256 mismatch"); + esp_hosted_slave_ota_end(); // NOLINT + this->status_set_error(LOG_STR("SHA256 verification failed")); + return false; + } + + ESP_LOGI(TAG, "SHA256 verified successfully"); + return true; +} +#else +bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() { if (this->firmware_data_ == nullptr || this->firmware_size_ == 0) { ESP_LOGE(TAG, "No firmware data available"); - return; + this->status_set_error(LOG_STR("No firmware data available")); + return false; } - sha256::SHA256 hasher; + // Verify SHA256 before writing + // Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+) + alignas(32) sha256::SHA256 hasher; hasher.init(); hasher.add(this->firmware_data_, this->firmware_size_); hasher.calculate(); if (!hasher.equals_bytes(this->firmware_sha256_.data())) { + ESP_LOGE(TAG, "SHA256 mismatch"); this->status_set_error(LOG_STR("SHA256 verification failed")); - this->publish_state(); - return; + return false; } ESP_LOGI(TAG, "Starting OTA update (%zu bytes)", this->firmware_size_); - watchdog::WatchdogManager watchdog(20000); - update::UpdateState prev_state = this->state_; - this->state_ = update::UPDATE_STATE_INSTALLING; - this->update_info_.has_progress = false; - this->publish_state(); - esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err)); - this->state_ = prev_state; this->status_set_error(LOG_STR("Failed to begin OTA")); - this->publish_state(); - return; + return false; } uint8_t chunk[CHUNK_SIZE]; @@ -125,42 +382,74 @@ void Esp32HostedUpdate::perform(bool force) { if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err)); esp_hosted_slave_ota_end(); // NOLINT - this->state_ = prev_state; this->status_set_error(LOG_STR("Failed to write OTA data")); - this->publish_state(); - return; + return false; } data_ptr += chunk_size; remaining -= chunk_size; App.feed_wdt(); } - err = esp_hosted_slave_ota_end(); // NOLINT - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err)); + return true; +} +#endif + +void Esp32HostedUpdate::perform(bool force) { + if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) { + ESP_LOGW(TAG, "Update not available"); + return; + } + + update::UpdateState prev_state = this->state_; + this->state_ = update::UPDATE_STATE_INSTALLING; + this->update_info_.has_progress = false; + this->publish_state(); + + watchdog::WatchdogManager watchdog(60000); + +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE + if (!this->stream_firmware_to_coprocessor_()) +#else + if (!this->write_embedded_firmware_to_coprocessor_()) +#endif + { + this->state_ = prev_state; + this->publish_state(); + return; + } + + // End OTA and activate new firmware + esp_err_t end_err = esp_hosted_slave_ota_end(); // NOLINT + if (end_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(end_err)); this->state_ = prev_state; this->status_set_error(LOG_STR("Failed to end OTA")); this->publish_state(); return; } - // activate new firmware - err = esp_hosted_slave_ota_activate(); // NOLINT - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err)); + esp_err_t activate_err = esp_hosted_slave_ota_activate(); // NOLINT + if (activate_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(activate_err)); this->state_ = prev_state; this->status_set_error(LOG_STR("Failed to activate OTA")); this->publish_state(); return; } - // update state + // Update state ESP_LOGI(TAG, "OTA update successful"); this->state_ = update::UPDATE_STATE_NO_UPDATE; this->status_clear_error(); this->publish_state(); - // schedule a restart to ensure everything is in sync +#ifdef USE_OTA_ROLLBACK + // Mark the host partition as valid before rebooting, in case the safe mode + // timer hasn't expired yet. + esp_ota_mark_app_valid_cancel_rollback(); +#endif + + // Schedule a restart to ensure everything is in sync ESP_LOGI(TAG, "Restarting in 1 second"); this->set_timeout(1000, []() { App.safe_reboot(); }); } diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.h b/esphome/components/esp32_hosted/update/esp32_hosted_update.h index 9c087bf72a..7c9645c12a 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.h +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.h @@ -5,26 +5,55 @@ #include "esphome/core/component.h" #include "esphome/components/update/update_entity.h" #include +#include + +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE +#include "esphome/components/http_request/http_request.h" +#endif namespace esphome::esp32_hosted { -class Esp32HostedUpdate : public update::UpdateEntity, public Component { +class Esp32HostedUpdate : public update::UpdateEntity, public PollingComponent { public: void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void update() override { this->check(); } // PollingComponent - delegates to check() void perform(bool force) override; - void check() override {} + void check() override; +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE + // HTTP mode setters + void set_source_url(const std::string &url) { this->source_url_ = url; } + void set_http_request_parent(http_request::HttpRequestComponent *parent) { this->http_request_parent_ = parent; } +#else + // Embedded mode setters void set_firmware_data(const uint8_t *data) { this->firmware_data_ = data; } void set_firmware_size(size_t size) { this->firmware_size_ = size; } void set_firmware_sha256(const std::array &sha256) { this->firmware_sha256_ = sha256; } +#endif protected: +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE + // HTTP mode members + http_request::HttpRequestComponent *http_request_parent_{nullptr}; + std::string source_url_; + std::string firmware_url_; + + // HTTP mode helpers + bool fetch_manifest_(); + bool stream_firmware_to_coprocessor_(); +#else + // Embedded mode members const uint8_t *firmware_data_{nullptr}; size_t firmware_size_{0}; - std::array firmware_sha256_; + + // Embedded mode helper + bool write_embedded_firmware_to_coprocessor_(); +#endif + + std::array firmware_sha256_{}; }; } // namespace esphome::esp32_hosted diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 2e69d400ca..ad2f057163 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -3,7 +3,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor, esp32_ble, improv_base, output from esphome.components.esp32_ble import BTLoggers import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, CONF_TRIGGER_ID AUTO_LOAD = ["esp32_ble_server", "improv_base"] CODEOWNERS = ["@jesserockz"] @@ -15,7 +15,6 @@ CONF_BLE_SERVER_ID = "ble_server_id" CONF_IDENTIFY_DURATION = "identify_duration" CONF_ON_PROVISIONED = "on_provisioned" CONF_ON_PROVISIONING = "on_provisioning" -CONF_ON_START = "on_start" CONF_ON_STOP = "on_stop" CONF_STATUS_INDICATOR = "status_indicator" CONF_WIFI_TIMEOUT = "wifi_timeout" diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 0ad54bbb15..1a19472c87 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -4,6 +4,7 @@ #include "esphome/components/esp32_ble/ble.h" #include "esphome/components/esp32_ble_server/ble_2902.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -14,6 +15,7 @@ namespace esp32_improv { using namespace bytebuffer; static const char *const TAG = "esp32_improv.component"; +static constexpr size_t IMPROV_MAX_LOG_BYTES = 128; static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; static constexpr uint16_t STOP_ADVERTISING_DELAY = 10000; // Delay (ms) before stopping service to allow BLE clients to read the final state @@ -314,7 +316,11 @@ void ESP32ImprovComponent::dump_config() { void ESP32ImprovComponent::process_incoming_data_() { uint8_t length = this->incoming_data_[1]; - ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(IMPROV_MAX_LOG_BYTES)]; + ESP_LOGV(TAG, "Processing bytes - %s", + format_hex_pretty_to(hex_buf, this->incoming_data_.data(), this->incoming_data_.size())); +#endif if (this->incoming_data_.size() - 3 == length) { this->set_error_(improv::ERROR_NONE); improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_); @@ -392,9 +398,12 @@ void ESP32ImprovComponent::check_wifi_connection_() { #ifdef USE_ESP32_IMPROV_NEXT_URL // Add next_url if configured (should be first per Improv BLE spec) - std::string next_url = this->get_formatted_next_url_(); - if (!next_url.empty()) { - url_strings[url_count++] = std::move(next_url); + { + char url_buffer[384]; + size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer)); + if (len > 0) { + url_strings[url_count++] = std::string(url_buffer, len); + } } #endif @@ -403,8 +412,12 @@ void ESP32ImprovComponent::check_wifi_connection_() { #ifdef USE_WEBSERVER for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { if (ip.is_ip4()) { - char url_buffer[64]; - snprintf(url_buffer, sizeof(url_buffer), "http://%s:%d", ip.str().c_str(), USE_WEBSERVER_PORT); + // "http://" (7) + IPv4 max (15) + ":" (1) + port max (5) + null = 29 + char url_buffer[32]; + memcpy(url_buffer, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates + ip.str_to(url_buffer + 7); + size_t len = strlen(url_buffer); + snprintf(url_buffer + len, sizeof(url_buffer) - len, ":%d", USE_WEBSERVER_PORT); url_strings[url_count++] = url_buffer; break; } diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index 2c7963b366..4ca0b998b1 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -98,7 +98,7 @@ void ESP32RMTLEDStripLightOutput::setup() { channel.trans_queue_depth = 1; channel.flags.io_loop_back = 0; channel.flags.io_od_mode = 0; - channel.flags.invert_out = 0; + channel.flags.invert_out = this->invert_out_; channel.flags.with_dma = this->use_dma_; channel.intr_priority = 0; if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) { diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.h b/esphome/components/esp32_rmt_led_strip/led_strip.h index 72ce659b4f..6f3aea9878 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.h +++ b/esphome/components/esp32_rmt_led_strip/led_strip.h @@ -49,6 +49,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { } void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_inverted(bool inverted) { this->invert_out_ = inverted; } void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; } @@ -93,6 +94,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { bool is_wrgb_{false}; bool use_dma_{false}; bool use_psram_{false}; + bool invert_out_{false}; RGBOrder rgb_order_{ORDER_RGB}; diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index f020d02e86..3be3c758f1 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -8,9 +8,11 @@ from esphome.components.const import CONF_USE_PSRAM import esphome.config_validation as cv from esphome.const import ( CONF_CHIPSET, + CONF_INVERTED, CONF_IS_RGBW, CONF_MAX_REFRESH_RATE, CONF_NUM_LEDS, + CONF_NUMBER, CONF_OUTPUT_ID, CONF_PIN, CONF_RGB_ORDER, @@ -71,7 +73,7 @@ CONFIG_SCHEMA = cv.All( light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput), - cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.SplitDefault( @@ -132,7 +134,9 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) - cg.add(var.set_pin(config[CONF_PIN])) + cg.add(var.set_pin(config[CONF_PIN][CONF_NUMBER])) + if config[CONF_PIN][CONF_INVERTED]: + cg.add(var.set_inverted(True)) if CONF_MAX_REFRESH_RATE in config: cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index fb1973e26f..812c746301 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -243,6 +243,16 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; } +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + /// Ensure benchmark value is read (v2 touch hardware only). + /// Called from multiple places - kept as helper to document shared usage. + void ensure_benchmark_read() { + if (this->benchmark_ == 0) { + touch_pad_read_benchmark(this->touch_pad_, &this->benchmark_); + } + } +#endif + protected: friend ESP32TouchComponent; diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index a0b1df38c1..429b5173be 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -102,7 +102,16 @@ void ESP32TouchComponent::process_setup_mode_logging_(uint32_t now) { uint32_t value = this->read_touch_value(child->get_touch_pad()); // Store the value for get_value() access in lambdas child->value_ = value; - ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value); + // Read benchmark if not already read + child->ensure_benchmark_read(); + // Calculate difference to help user set threshold + // For ESP32-S2/S3 v2: touch detected when value > benchmark + threshold + // So threshold should be < (value - benchmark) when touched + int32_t difference = static_cast(value) - static_cast(child->benchmark_); + ESP_LOGD(TAG, + "Touch Pad '%s' (T%d): value=%d, benchmark=%" PRIu32 ", difference=%" PRId32 " (set threshold < %" PRId32 + " to detect touch)", + child->get_name().c_str(), child->get_touch_pad(), value, child->benchmark_, difference, difference); #endif } this->setup_mode_last_log_print_ = now; diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 9662b009f6..b34ca1abd3 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -105,8 +105,10 @@ void ESP32TouchComponent::setup() { touch_pad_set_charge_discharge_times(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); - // Configure timeout if needed - touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); + // Disable hardware timeout - it causes continuous interrupts with high-capacitance + // setups (e.g., pressure sensors under cushions). The periodic release check in + // loop() handles state detection reliably without needing hardware timeout. + touch_pad_timeout_set(false, TOUCH_PAD_THRESHOLD_MAX); // Register ISR handler with interrupt mask esp_err_t err = @@ -314,8 +316,7 @@ void ESP32TouchComponent::loop() { size_t pads_off = 0; for (auto *child : this->children_) { - if (child->benchmark_ == 0) - touch_pad_read_benchmark(child->touch_pad_, &child->benchmark_); + child->ensure_benchmark_read(); // Handle initial state publication after startup this->publish_initial_state_if_needed_(child, now); @@ -354,7 +355,7 @@ void ESP32TouchComponent::loop() { void ESP32TouchComponent::on_shutdown() { // Disable interrupts - touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); + touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_ACTIVE); touch_pad_isr_deregister(touch_isr_handler, this); this->cleanup_touch_queue_(); diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index a74f9ee8ce..c7b5d5c130 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -23,11 +23,18 @@ from esphome.helpers import copy_file_if_changed from .boards import BOARDS, ESP8266_LD_SCRIPTS from .const import ( CONF_EARLY_PIN_INIT, + CONF_ENABLE_SERIAL, + CONF_ENABLE_SERIAL1, CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, KEY_FLASH_SIZE, KEY_PIN_INITIAL_STATES, + KEY_SERIAL1_REQUIRED, + KEY_SERIAL_REQUIRED, + KEY_WAVEFORM_REQUIRED, + enable_serial, + enable_serial1, esp8266_ns, ) from .gpio import PinInitialState, add_pin_initial_states_array @@ -170,6 +177,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of( *BUILD_FLASH_MODES, lower=True ), + cv.Optional(CONF_ENABLE_SERIAL): cv.boolean, + cv.Optional(CONF_ENABLE_SERIAL1): cv.boolean, } ), set_core_data, @@ -191,7 +200,13 @@ async def to_code(config): cg.add_define(ThreadModel.SINGLE) cg.add_platformio_option( - "extra_scripts", ["pre:testing_mode.py", "post:post_build.py"] + "extra_scripts", + [ + "pre:testing_mode.py", + "pre:exclude_updater.py", + "pre:exclude_waveform.py", + "post:post_build.py", + ], ) conf = config[CONF_FRAMEWORK] @@ -224,6 +239,12 @@ async def to_code(config): if config[CONF_EARLY_PIN_INIT]: cg.add_define("USE_ESP8266_EARLY_PIN_INIT") + # Allow users to force-enable Serial objects for use in lambdas or external libraries + if config.get(CONF_ENABLE_SERIAL): + enable_serial() + if config.get(CONF_ENABLE_SERIAL1): + enable_serial1() + # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of @@ -263,10 +284,43 @@ async def to_code(config): cg.add_platformio_option("board_build.ldscript", ld_script) CORE.add_job(add_pin_initial_states_array) + CORE.add_job(finalize_waveform_config) + CORE.add_job(finalize_serial_config) + + +@coroutine_with_priority(CoroPriority.WORKAROUNDS) +async def finalize_waveform_config() -> None: + """Add waveform stubs define if waveform is not required. + + This runs at WORKAROUNDS priority (-999) to ensure all components + have had a chance to call require_waveform() first. + """ + if not CORE.data.get(KEY_ESP8266, {}).get(KEY_WAVEFORM_REQUIRED, False): + # No component needs waveform - enable stubs and exclude Arduino waveform code + # Use build flag (visible to both C++ code and PlatformIO script) + cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS") + + +@coroutine_with_priority(CoroPriority.WORKAROUNDS) +async def finalize_serial_config() -> None: + """Exclude unused Arduino Serial objects from the build. + + This runs at WORKAROUNDS priority (-999) to ensure all components + have had a chance to call enable_serial() or enable_serial1() first. + + The Arduino ESP8266 core defines two global Serial objects (32 bytes each). + By adding NO_GLOBAL_SERIAL or NO_GLOBAL_SERIAL1 build flags, we prevent + unused Serial objects from being linked, saving 32 bytes each. + """ + esp8266_data = CORE.data.get(KEY_ESP8266, {}) + if not esp8266_data.get(KEY_SERIAL_REQUIRED, False): + cg.add_build_flag("-DNO_GLOBAL_SERIAL") + if not esp8266_data.get(KEY_SERIAL1_REQUIRED, False): + cg.add_build_flag("-DNO_GLOBAL_SERIAL1") # Called by writer.py -def copy_files(): +def copy_files() -> None: dir = Path(__file__).parent post_build_file = dir / "post_build.py.script" copy_file_if_changed( @@ -278,3 +332,13 @@ def copy_files(): testing_mode_file, CORE.relative_build_path("testing_mode.py"), ) + exclude_updater_file = dir / "exclude_updater.py.script" + copy_file_if_changed( + exclude_updater_file, + CORE.relative_build_path("exclude_updater.py"), + ) + exclude_waveform_file = dir / "exclude_waveform.py.script" + copy_file_if_changed( + exclude_waveform_file, + CORE.relative_build_path("exclude_waveform.py"), + ) diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index b718306b01..229ac61f24 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -1,11 +1,67 @@ import esphome.codegen as cg +from esphome.core import CORE KEY_ESP8266 = "esp8266" KEY_BOARD = "board" KEY_PIN_INITIAL_STATES = "pin_initial_states" CONF_RESTORE_FROM_FLASH = "restore_from_flash" CONF_EARLY_PIN_INIT = "early_pin_init" +CONF_ENABLE_SERIAL = "enable_serial" +CONF_ENABLE_SERIAL1 = "enable_serial1" KEY_FLASH_SIZE = "flash_size" +KEY_WAVEFORM_REQUIRED = "waveform_required" +KEY_SERIAL_REQUIRED = "serial_required" +KEY_SERIAL1_REQUIRED = "serial1_required" # esp8266 namespace is already defined by arduino, manually prefix esphome esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") + + +def require_waveform() -> None: + """Mark that Arduino waveform/PWM support is required. + + Call this from components that need the Arduino waveform generator + (startWaveform, stopWaveform, analogWrite, Tone, Servo). + + If no component calls this, the waveform code is excluded from the build + to save ~596 bytes of RAM and 464 bytes of flash. + + Example: + from esphome.components.esp8266.const import require_waveform + + async def to_code(config): + require_waveform() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True + + +def enable_serial() -> None: + """Mark that Arduino Serial (UART0) is required. + + Call this from components that use the global Serial object. + If no component calls this, Serial is excluded from the build + to save 32 bytes of RAM. + + Example: + from esphome.components.esp8266.const import enable_serial + + async def to_code(config): + enable_serial() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL_REQUIRED] = True + + +def enable_serial1() -> None: + """Mark that Arduino Serial1 (UART1) is required. + + Call this from components that use the global Serial1 object. + If no component calls this, Serial1 is excluded from the build + to save 32 bytes of RAM. + + Example: + from esphome.components.esp8266.const import enable_serial1 + + async def to_code(config): + enable_serial1() + """ + CORE.data.setdefault(KEY_ESP8266, {})[KEY_SERIAL1_REQUIRED] = True diff --git a/esphome/components/esp8266/exclude_updater.py.script b/esphome/components/esp8266/exclude_updater.py.script new file mode 100644 index 0000000000..69331e3b03 --- /dev/null +++ b/esphome/components/esp8266/exclude_updater.py.script @@ -0,0 +1,21 @@ +# pylint: disable=E0602 +Import("env") # noqa + +import os + +# Filter out Updater.cpp from the Arduino core build +# This saves 228 bytes of .bss by not instantiating the global Update object +# ESPHome uses its own native OTA backend instead + + +def filter_updater_from_core(env, node): + """Filter callback to exclude Updater.cpp from framework build.""" + path = node.get_path() + if path.endswith("Updater.cpp"): + print(f"ESPHome: Excluding {os.path.basename(path)} from build (using native OTA backend)") + return None + return node + + +# Apply the filter to framework sources +env.AddBuildMiddleware(filter_updater_from_core, "**/cores/esp8266/Updater.cpp") diff --git a/esphome/components/esp8266/exclude_waveform.py.script b/esphome/components/esp8266/exclude_waveform.py.script new file mode 100644 index 0000000000..35d6bc31f6 --- /dev/null +++ b/esphome/components/esp8266/exclude_waveform.py.script @@ -0,0 +1,50 @@ +# pylint: disable=E0602 +Import("env") # noqa + +import os + +# Filter out waveform/PWM code from the Arduino core build +# This saves ~596 bytes of RAM and 464 bytes of flash by not +# instantiating the waveform generator state structures (wvfState + pwmState). +# +# The waveform code is used by: analogWrite, Tone, Servo, and direct +# startWaveform/stopWaveform calls. ESPHome's esp8266_pwm component +# calls require_waveform() to keep this code when needed. +# +# When excluded, we provide stub implementations of stopWaveform() and +# _stopPWM() since digitalWrite() calls these unconditionally. + + +def has_define_flag(env, name): + """Check if a define exists in the build flags.""" + define_flag = f"-D{name}" + # Check BUILD_FLAGS (where ESPHome puts its defines) + for flag in env.get("BUILD_FLAGS", []): + if flag == define_flag or flag.startswith(f"{define_flag}="): + return True + # Also check CPPDEFINES list (parsed defines) + for define in env.get("CPPDEFINES", []): + if isinstance(define, tuple): + if define[0] == name: + return True + elif define == name: + return True + return False + +# USE_ESP8266_WAVEFORM_STUBS is defined when no component needs waveform +if has_define_flag(env, "USE_ESP8266_WAVEFORM_STUBS"): + + def filter_waveform_from_core(env, node): + """Filter callback to exclude waveform files from framework build.""" + path = node.get_path() + filename = os.path.basename(path) + if filename in ( + "core_esp8266_waveform_pwm.cpp", + "core_esp8266_waveform_phase.cpp", + ): + print(f"ESPHome: Excluding {filename} from build (waveform not required)") + return None + return node + + # Apply the filter to framework sources + env.AddBuildMiddleware(filter_waveform_from_core, "**/cores/esp8266/*.cpp") diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 124df39ce3..7a5ee08984 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -98,10 +98,8 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT } -std::string ESP8266GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); - return buffer; +size_t ESP8266GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%u", this->pin_); } bool ESP8266GPIOPin::digital_read() { diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h index 213a5c54be..ff149abfbe 100644 --- a/esphome/components/esp8266/gpio.h +++ b/esphome/components/esp8266/gpio.h @@ -17,7 +17,7 @@ class ESP8266GPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index 197d244dc4..47987b4a95 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -1,7 +1,6 @@ #ifdef USE_ESP8266 #include -#include extern "C" { #include "spi_flash.h" } @@ -27,6 +26,16 @@ static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; +// RTC memory layout for preferences: +// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 96-127) +// - Normal region: RTC words 32-127 (mapped from preference offset 0-95) +static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot +static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 96; // Words 32-127 for normal prefs +static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 128 + +// Maximum preference size in words (limited by uint8_t length_words field) +static constexpr uint32_t MAX_PREFERENCE_WORDS = 255; + #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) #ifdef USE_ESP8266_PREFERENCES_FLASH @@ -118,6 +127,10 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { return true; } +// Stack buffer size - 16 words total: up to 15 words of preference data + 1 word CRC (60 bytes of preference data) +// This handles virtually all real-world preferences without heap allocation +static constexpr size_t PREF_BUFFER_WORDS = 16; + class ESP8266PreferenceBackend : public ESPPreferenceBackend { public: uint32_t type = 0; @@ -126,36 +139,54 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { bool in_flash = false; bool save(const uint8_t *data, size_t len) override { - if (bytes_to_words(len) != length_words) { + if (bytes_to_words(len) != this->length_words) return false; - } - size_t buffer_size = static_cast(length_words) + 1; - std::unique_ptr buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization - memcpy(buffer.get(), data, len); - buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type); - if (in_flash) { - return save_to_flash(offset, buffer.get(), buffer_size); + const size_t buffer_size = static_cast(this->length_words) + 1; + uint32_t stack_buffer[PREF_BUFFER_WORDS]; + std::unique_ptr heap_buffer; + uint32_t *buffer; + + if (buffer_size <= PREF_BUFFER_WORDS) { + buffer = stack_buffer; + } else { + heap_buffer = make_unique(buffer_size); + buffer = heap_buffer.get(); } - return save_to_rtc(offset, buffer.get(), buffer_size); + memset(buffer, 0, buffer_size * sizeof(uint32_t)); + + memcpy(buffer, data, len); + buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); + + return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) + : save_to_rtc(this->offset, buffer, buffer_size); } + bool load(uint8_t *data, size_t len) override { - if (bytes_to_words(len) != length_words) { + if (bytes_to_words(len) != this->length_words) return false; + + const size_t buffer_size = static_cast(this->length_words) + 1; + uint32_t stack_buffer[PREF_BUFFER_WORDS]; + std::unique_ptr heap_buffer; + uint32_t *buffer; + + if (buffer_size <= PREF_BUFFER_WORDS) { + buffer = stack_buffer; + } else { + heap_buffer = make_unique(buffer_size); + buffer = heap_buffer.get(); } - size_t buffer_size = static_cast(length_words) + 1; - std::unique_ptr buffer(new uint32_t[buffer_size]()); - bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size) - : load_from_rtc(offset, buffer.get(), buffer_size); + + bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) + : load_from_rtc(this->offset, buffer, buffer_size); if (!ret) return false; - uint32_t crc = calculate_crc(buffer.get(), buffer.get() + length_words, type); - if (buffer[length_words] != crc) { + if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) return false; - } - memcpy(data, buffer.get(), len); + memcpy(data, buffer, len); return true; } }; @@ -176,50 +207,42 @@ class ESP8266Preferences : public ESPPreferences { } ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - uint32_t length_words = bytes_to_words(length); - if (length_words > 255) { - ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words); + const uint32_t length_words = bytes_to_words(length); + if (length_words > MAX_PREFERENCE_WORDS) { + ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); return {}; } + + const uint32_t total_words = length_words + 1; // +1 for CRC + uint16_t offset; + if (in_flash) { - uint32_t start = current_flash_offset; - uint32_t end = start + length_words + 1; - if (end > ESP8266_FLASH_STORAGE_SIZE) + if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) return {}; - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = static_cast(start); - pref->type = type; - pref->length_words = static_cast(length_words); - pref->in_flash = true; - current_flash_offset = end; - return {pref}; + offset = static_cast(this->current_flash_offset); + this->current_flash_offset += total_words; + } else { + uint32_t start = this->current_offset; + bool in_normal = start < RTC_NORMAL_REGION_WORDS; + // Normal: offset 0-95 maps to RTC offset 32-127 + // Eboot: offset 96-127 maps to RTC offset 0-31 + if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { + // start is in normal but end is not -> switch to Eboot + this->current_offset = start = RTC_NORMAL_REGION_WORDS; + in_normal = false; + } + if (start + total_words > PREF_TOTAL_WORDS) + return {}; // Doesn't fit in RTC memory + // Convert preference offset to RTC memory offset + offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); + this->current_offset = start + total_words; } - uint32_t start = current_offset; - uint32_t end = start + length_words + 1; - bool in_normal = start < 96; - // Normal: offset 0-95 maps to RTC offset 32 - 127, - // Eboot: offset 96-127 maps to RTC offset 0 - 31 words - if (in_normal && end > 96) { - // start is in normal but end is not -> switch to Eboot - current_offset = start = 96; - end = start + length_words + 1; - in_normal = false; - } - - if (end > 128) { - // Doesn't fit in data, return uninitialized preference obj. - return {}; - } - - uint32_t rtc_offset = in_normal ? start + 32 : start - 96; - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = static_cast(rtc_offset); + pref->offset = offset; pref->type = type; pref->length_words = static_cast(length_words); - pref->in_flash = false; - current_offset += length_words + 1; + pref->in_flash = in_flash; return pref; } diff --git a/esphome/components/esp8266/waveform_stubs.cpp b/esphome/components/esp8266/waveform_stubs.cpp new file mode 100644 index 0000000000..686e03c6a9 --- /dev/null +++ b/esphome/components/esp8266/waveform_stubs.cpp @@ -0,0 +1,34 @@ +#ifdef USE_ESP8266_WAVEFORM_STUBS + +// Stub implementations for Arduino waveform/PWM functions. +// +// When the waveform generator is not needed (no esp8266_pwm component), +// we exclude core_esp8266_waveform_pwm.cpp from the build to save ~596 bytes +// of RAM and 464 bytes of flash. +// +// These stubs satisfy calls from the Arduino GPIO code when the real +// waveform implementation is excluded. They must be in the global namespace +// with C linkage to match the Arduino core function declarations. + +#include + +// Empty namespace to satisfy linter - actual stubs must be at global scope +namespace esphome::esp8266 {} // namespace esphome::esp8266 + +extern "C" { + +// Called by Arduino GPIO code to stop any waveform on a pin +int stopWaveform(uint8_t pin) { + (void) pin; + return 1; // Success (no waveform to stop) +} + +// Called by Arduino GPIO code to stop any PWM on a pin +bool _stopPWM(uint8_t pin) { + (void) pin; + return false; // No PWM was running +} + +} // extern "C" + +#endif // USE_ESP8266_WAVEFORM_STUBS diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 0aaef597d3..cc6bfbc8a8 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -18,9 +18,11 @@ void ESP8266PWM::setup() { this->turn_off(); } void ESP8266PWM::dump_config() { - ESP_LOGCONFIG(TAG, "ESP8266 PWM:"); + ESP_LOGCONFIG(TAG, + "ESP8266 PWM:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); LOG_FLOAT_OUTPUT(this); } void HOT ESP8266PWM::write_state(float state) { diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 2ddf4b9014..a78831c516 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import output +from esphome.components.esp8266.const import require_waveform import esphome.config_validation as cv from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NUMBER, CONF_PIN @@ -34,7 +35,9 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config) -> None: + require_waveform() + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await output.register_output(var, config) diff --git a/esphome/components/esp_ldo/__init__.py b/esphome/components/esp_ldo/__init__.py index 38e684c537..f136dd149b 100644 --- a/esphome/components/esp_ldo/__init__.py +++ b/esphome/components/esp_ldo/__init__.py @@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All( } ) ), - cv.only_with_esp_idf, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32P4]), ) diff --git a/esphome/components/esp_ldo/esp_ldo.cpp b/esphome/components/esp_ldo/esp_ldo.cpp index 5e3d4159f3..2eee855b46 100644 --- a/esphome/components/esp_ldo/esp_ldo.cpp +++ b/esphome/components/esp_ldo/esp_ldo.cpp @@ -21,9 +21,11 @@ void EspLdo::setup() { } } void EspLdo::dump_config() { - ESP_LOGCONFIG(TAG, "ESP LDO Channel %d:", this->channel_); - ESP_LOGCONFIG(TAG, " Voltage: %fV", this->voltage_); - ESP_LOGCONFIG(TAG, " Adjustable: %s", YESNO(this->adjustable_)); + ESP_LOGCONFIG(TAG, + "ESP LDO Channel %d:\n" + " Voltage: %fV\n" + " Adjustable: %s", + this->channel_, this->voltage_, YESNO(this->adjustable_)); } void EspLdo::adjust_voltage(float voltage) { diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index e56e85b231..2f637d714d 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -16,7 +16,7 @@ from esphome.const import ( CONF_SAFE_MODE, CONF_VERSION, ) -from esphome.core import CORE, coroutine_with_priority +from esphome.core import coroutine_with_priority from esphome.coroutine import CoroPriority import esphome.final_validate as fv from esphome.types import ConfigType @@ -28,17 +28,7 @@ CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] -def supports_sha256() -> bool: - """Check if the current platform supports SHA256 for OTA authentication.""" - return bool(CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny) - - -def AUTO_LOAD() -> list[str]: - """Conditionally auto-load sha256 only on platforms that support it.""" - base_components = ["md5", "socket"] - if supports_sha256(): - return base_components + ["sha256"] - return base_components +AUTO_LOAD = ["sha256", "socket"] esphome = cg.esphome_ns.namespace("esphome") @@ -155,11 +145,6 @@ async def to_code(config: ConfigType) -> None: if config.get(CONF_PASSWORD): cg.add(var.set_auth_password(config[CONF_PASSWORD])) cg.add_define("USE_OTA_PASSWORD") - # Only include hash algorithms when password is configured - cg.add_define("USE_OTA_MD5") - # Only include SHA256 support on platforms that have it - if supports_sha256(): - cg.add_define("USE_OTA_SHA256") cg.add_define("USE_OTA_VERSION", config[CONF_VERSION]) await cg.register_component(var, config) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 175bc40f81..b2ae185687 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -1,16 +1,12 @@ #include "ota_esphome.h" #ifdef USE_OTA #ifdef USE_OTA_PASSWORD -#ifdef USE_OTA_MD5 -#include "esphome/components/md5/md5.h" -#endif -#ifdef USE_OTA_SHA256 #include "esphome/components/sha256/sha256.h" #endif -#endif #include "esphome/components/network/util.h" +#include "esphome/components/socket/socket.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_libretiny.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" #include "esphome/components/ota/ota_backend_esp_idf.h" @@ -31,20 +27,7 @@ static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer -#ifdef USE_OTA_PASSWORD -#ifdef USE_OTA_MD5 -static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2) -#endif -#ifdef USE_OTA_SHA256 -static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2) -#endif -#endif // USE_OTA_PASSWORD - void ESPHomeOTAComponent::setup() { -#ifdef USE_OTA_STATE_CALLBACK - ota::register_ota_platform(this); -#endif - this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections if (this->server_ == nullptr) { this->log_socket_error_(LOG_STR("creation")); @@ -112,15 +95,7 @@ void ESPHomeOTAComponent::loop() { } static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; -#ifdef USE_OTA_SHA256 static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02; -#endif - -// Temporary flag to allow MD5 downgrade for ~3 versions (until 2026.1.0) -// This allows users to downgrade via OTA if they encounter issues after updating. -// Without this, users would need to do a serial flash to downgrade. -// TODO: Remove this flag and all associated code in 2026.1.0 -#define ALLOW_OTA_DOWNGRADE_MD5 void ESPHomeOTAComponent::handle_handshake_() { /// Handle the OTA handshake and authentication. @@ -297,8 +272,8 @@ void ESPHomeOTAComponent::handle_data_() { // accidentally trigger the update process. this->log_start_(LOG_STR("update")); this->status_set_warning(); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_STARTED, 0.0f, 0); #endif // This will block for a few seconds as it locks flash @@ -357,8 +332,8 @@ void ESPHomeOTAComponent::handle_data_() { last_progress = now; float percentage = (total * 100.0f) / ota_size; ESP_LOGD(TAG, "Progress: %0.1f%%", percentage); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0); #endif // feed watchdog and give other tasks a chance to run this->yield_and_feed_watchdog_(); @@ -387,8 +362,8 @@ void ESPHomeOTAComponent::handle_data_() { delay(10); ESP_LOGI(TAG, "Update complete"); this->status_clear_warning(); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_COMPLETED, 100.0f, 0); #endif delay(100); // NOLINT App.safe_reboot(); @@ -404,8 +379,8 @@ error: this->cleanup_connection_(); this->status_momentary_error("err", 5000); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif } @@ -415,14 +390,14 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) { while (len - at > 0) { uint32_t now = millis(); if (now - start > OTA_SOCKET_TIMEOUT_DATA) { - ESP_LOGW(TAG, "Timeout reading %d bytes", len); + ESP_LOGW(TAG, "Timeout reading %zu bytes", len); return false; } ssize_t read = this->client_->read(buf + at, len - at); if (read == -1) { if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Read err %d bytes, errno %d", len, errno); + ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, errno); return false; } } else if (read == 0) { @@ -442,14 +417,14 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) { while (len - at > 0) { uint32_t now = millis(); if (now - start > OTA_SOCKET_TIMEOUT_DATA) { - ESP_LOGW(TAG, "Timeout writing %d bytes", len); + ESP_LOGW(TAG, "Timeout writing %zu bytes", len); return false; } ssize_t written = this->client_->write(buf + at, len - at); if (written == -1) { if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Write err %d bytes, errno %d", len, errno); + ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, errno); return false; } } else { @@ -471,7 +446,9 @@ void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) { void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); } void ESPHomeOTAComponent::log_start_(const LogString *phase) { - ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str()); + char peername[socket::SOCKADDR_STR_LEN]; + this->client_->getpeername_to(peername); + ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername); } void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) { @@ -553,26 +530,8 @@ void ESPHomeOTAComponent::yield_and_feed_watchdog_() { void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); } bool ESPHomeOTAComponent::select_auth_type_() { -#ifdef USE_OTA_SHA256 bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0; -#ifdef ALLOW_OTA_DOWNGRADE_MD5 - // Allow fallback to MD5 if client doesn't support SHA256 - if (client_supports_sha256) { - this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH; - return true; - } -#ifdef USE_OTA_MD5 - this->log_auth_warning_(LOG_STR("Using deprecated MD5")); - this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH; - return true; -#else - this->log_auth_warning_(LOG_STR("SHA256 required")); - this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID); - return false; -#endif // USE_OTA_MD5 - -#else // !ALLOW_OTA_DOWNGRADE_MD5 // Require SHA256 if (!client_supports_sha256) { this->log_auth_warning_(LOG_STR("SHA256 required")); @@ -581,20 +540,6 @@ bool ESPHomeOTAComponent::select_auth_type_() { } this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH; return true; -#endif // ALLOW_OTA_DOWNGRADE_MD5 - -#else // !USE_OTA_SHA256 -#ifdef USE_OTA_MD5 - // Only MD5 available - this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH; - return true; -#else - // No auth methods available - this->log_auth_warning_(LOG_STR("No auth methods available")); - this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID); - return false; -#endif // USE_OTA_MD5 -#endif // USE_OTA_SHA256 } bool ESPHomeOTAComponent::handle_auth_send_() { @@ -618,31 +563,14 @@ bool ESPHomeOTAComponent::handle_auth_send_() { // [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce // [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash - // Declare both hash objects in same stack frame, use pointer to select. - // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3 - // hardware SHA acceleration - the object must exist in this stack frame for all operations. - // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope. -#ifdef USE_OTA_SHA256 - sha256::SHA256 sha_hasher; -#endif -#ifdef USE_OTA_MD5 - md5::MD5Digest md5_hasher; -#endif - HashBase *hasher = nullptr; + // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame + // (no passing to other functions). All hash operations must happen in this function. + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; -#ifdef USE_OTA_SHA256 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { - hasher = &sha_hasher; - } -#endif -#ifdef USE_OTA_MD5 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { - hasher = &md5_hasher; - } -#endif - - const size_t hex_size = hasher->get_size() * 2; - const size_t nonce_len = hasher->get_size() / 4; + const size_t hex_size = hasher.get_size() * 2; + const size_t nonce_len = hasher.get_size() / 4; const size_t auth_buf_size = 1 + 3 * hex_size; this->auth_buf_ = std::make_unique(auth_buf_size); this->auth_buf_pos_ = 0; @@ -654,22 +582,17 @@ bool ESPHomeOTAComponent::handle_auth_send_() { return false; } - hasher->init(); - hasher->add(buf, nonce_len); - hasher->calculate(); + hasher.init(); + hasher.add(buf, nonce_len); + hasher.calculate(); this->auth_buf_[0] = this->auth_type_; - hasher->get_hex(buf); + hasher.get_hex(buf); -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too - memcpy(log_buf, buf, hex_size); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf); -#endif + ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf); } // Try to write auth_type + nonce - size_t hex_size = this->get_auth_hex_size_(); + constexpr size_t hex_size = SHA256_HEX_SIZE; const size_t to_write = 1 + hex_size; size_t remaining = to_write - this->auth_buf_pos_; @@ -691,7 +614,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() { } bool ESPHomeOTAComponent::handle_auth_read_() { - size_t hex_size = this->get_auth_hex_size_(); + constexpr size_t hex_size = SHA256_HEX_SIZE; const size_t to_read = hex_size * 2; // CNonce + Response // Try to read remaining bytes (CNonce + Response) @@ -716,55 +639,27 @@ bool ESPHomeOTAComponent::handle_auth_read_() { const char *cnonce = nonce + hex_size; const char *response = cnonce + hex_size; - // CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions). - // Declare both hash objects in same stack frame, use pointer to select. - // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3 - // hardware SHA acceleration - the object must exist in this stack frame for all operations. - // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope. -#ifdef USE_OTA_SHA256 - sha256::SHA256 sha_hasher; -#endif -#ifdef USE_OTA_MD5 - md5::MD5Digest md5_hasher; -#endif - HashBase *hasher = nullptr; + // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame + // (no passing to other functions). All hash operations must happen in this function. + // NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for + // hardware SHA acceleration DMA operations. + alignas(32) sha256::SHA256 hasher; -#ifdef USE_OTA_SHA256 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { - hasher = &sha_hasher; - } -#endif -#ifdef USE_OTA_MD5 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { - hasher = &md5_hasher; - } -#endif - - hasher->init(); - hasher->add(this->password_.c_str(), this->password_.length()); - hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer) - hasher->calculate(); + hasher.init(); + hasher.add(this->password_.c_str(), this->password_.length()); + hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer) + hasher.calculate(); + ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - char log_buf[65]; // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too - // Log CNonce - memcpy(log_buf, cnonce, hex_size); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: CNonce is %s", log_buf); - - // Log computed hash - hasher->get_hex(log_buf); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: Result is %s", log_buf); - - // Log received response - memcpy(log_buf, response, hex_size); - log_buf[hex_size] = '\0'; - ESP_LOGV(TAG, "Auth: Response is %s", log_buf); + char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator) + hasher.get_hex(computed_hash); + ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash); #endif + ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response); // Compare response - bool matches = hasher->equals_hex(response); + bool matches = hasher.equals_hex(response); if (!matches) { this->log_auth_warning_(LOG_STR("Password mismatch")); @@ -778,21 +673,6 @@ bool ESPHomeOTAComponent::handle_auth_read_() { return true; } -size_t ESPHomeOTAComponent::get_auth_hex_size_() const { -#ifdef USE_OTA_SHA256 - if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { - return SHA256_HEX_SIZE; - } -#endif -#ifdef USE_OTA_MD5 - return MD5_HEX_SIZE; -#else -#ifndef USE_OTA_SHA256 -#error "Either USE_OTA_MD5 or USE_OTA_SHA256 must be defined when USE_OTA_PASSWORD is enabled" -#endif -#endif -} - void ESPHomeOTAComponent::cleanup_auth_() { this->auth_buf_ = nullptr; this->auth_buf_pos_ = 0; diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index 057461e6a4..e199b7e406 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -44,10 +44,10 @@ class ESPHomeOTAComponent : public ota::OTAComponent { void handle_handshake_(); void handle_data_(); #ifdef USE_OTA_PASSWORD + static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2) bool handle_auth_send_(); bool handle_auth_read_(); bool select_auth_type_(); - size_t get_auth_hex_size_() const; void cleanup_auth_(); void log_auth_warning_(const LogString *msg); #endif // USE_OTA_PASSWORD @@ -80,6 +80,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent { #ifdef USE_OTA_PASSWORD std::string password_; + std::unique_ptr auth_buf_; #endif // USE_OTA_PASSWORD std::unique_ptr server_; @@ -93,7 +94,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent { uint8_t handshake_buf_pos_{0}; uint8_t ota_features_{0}; #ifdef USE_OTA_PASSWORD - std::unique_ptr auth_buf_; uint8_t auth_buf_pos_{0}; uint8_t auth_type_{0}; // Store auth type to know which hasher to use #endif // USE_OTA_PASSWORD diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index bc05833709..991803d870 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -6,6 +6,7 @@ #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -63,18 +64,6 @@ static const LogString *espnow_error_to_str(esp_err_t error) { } } -std::string peer_str(uint8_t *peer) { - if (peer == nullptr || peer[0] == 0) { - return "[Not Set]"; - } else if (memcmp(peer, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) { - return "[Broadcast]"; - } else if (memcmp(peer, ESPNOW_MULTICAST_ADDR, ESP_NOW_ETH_ALEN) == 0) { - return "[Multicast]"; - } else { - return format_mac_address_pretty(peer); - } -} - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status) #else @@ -139,11 +128,13 @@ void ESPNowComponent::dump_config() { ESP_LOGCONFIG(TAG, " Disabled"); return; } + char own_addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(this->own_address_, own_addr_buf); ESP_LOGCONFIG(TAG, " Own address: %s\n" " Version: v%" PRIu32 "\n" " Wi-Fi channel: %d", - format_mac_address_pretty(this->own_address_).c_str(), version, this->wifi_channel_); + own_addr_buf, version, this->wifi_channel_); #ifdef USE_WIFI ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled())); #endif @@ -299,9 +290,13 @@ void ESPNowComponent::loop() { // Intentionally left as if instead of else in case the peer is added above if (esp_now_is_peer_exist(info.src_addr)) { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - ESP_LOGV(TAG, "<<< [%s -> %s] %s", format_mac_address_pretty(info.src_addr).c_str(), - format_mac_address_pretty(info.des_addr).c_str(), - format_hex_pretty(packet->packet_.receive.data, packet->packet_.receive.size).c_str()); + char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)]; + format_mac_addr_upper(info.src_addr, src_buf); + format_mac_addr_upper(info.des_addr, dst_buf); + ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf, + format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size)); #endif if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) { for (auto *handler : this->broadcasted_handlers_) { @@ -319,8 +314,9 @@ void ESPNowComponent::loop() { } case ESPNowPacket::SENT: { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE - ESP_LOGV(TAG, ">>> [%s] %s", format_mac_address_pretty(packet->packet_.sent.address).c_str(), - LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status))); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(packet->packet_.sent.address, addr_buf); + ESP_LOGV(TAG, ">>> [%s] %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status))); #endif if (this->current_send_packet_ != nullptr) { this->current_send_packet_->callback_(packet->packet_.sent.status); @@ -407,8 +403,9 @@ void ESPNowComponent::send_() { this->current_send_packet_ = packet; esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send packet to %s - %s", format_mac_address_pretty(packet->address_).c_str(), - LOG_STR_ARG(espnow_error_to_str(err))); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(packet->address_, addr_buf); + ESP_LOGE(TAG, "Failed to send packet to %s - %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(err))); if (packet->callback_ != nullptr) { packet->callback_(err); } @@ -437,8 +434,9 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) { esp_err_t err = esp_now_add_peer(&peer_info); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to add peer %s - %s", format_mac_address_pretty(peer).c_str(), - LOG_STR_ARG(espnow_error_to_str(err))); + char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(peer, peer_buf); + ESP_LOGE(TAG, "Failed to add peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err))); this->status_momentary_warning("peer-add-failed"); return err; } @@ -466,8 +464,9 @@ esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) { if (esp_now_is_peer_exist(peer)) { esp_err_t err = esp_now_del_peer(peer); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to delete peer %s - %s", format_mac_address_pretty(peer).c_str(), - LOG_STR_ARG(espnow_error_to_str(err))); + char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(peer, peer_buf); + ESP_LOGE(TAG, "Failed to delete peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err))); this->status_momentary_warning("peer-del-failed"); return err; } diff --git a/esphome/components/espnow/packet_transport/espnow_transport.cpp b/esphome/components/espnow/packet_transport/espnow_transport.cpp index c1252acc9d..3d16f28c7d 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.cpp +++ b/esphome/components/espnow/packet_transport/espnow_transport.cpp @@ -21,9 +21,11 @@ void ESPNowTransport::setup() { return; } - ESP_LOGI(TAG, "Registering ESP-NOW handlers"); - ESP_LOGI(TAG, "Peer address: %02X:%02X:%02X:%02X:%02X:%02X", this->peer_address_[0], this->peer_address_[1], - this->peer_address_[2], this->peer_address_[3], this->peer_address_[4], this->peer_address_[5]); + ESP_LOGI(TAG, + "Registering ESP-NOW handlers\n" + "Peer address: %02X:%02X:%02X:%02X:%02X:%02X", + this->peer_address_[0], this->peer_address_[1], this->peer_address_[2], this->peer_address_[3], + this->peer_address_[4], this->peer_address_[5]); // Register received handler this->parent_->register_received_handler(this); diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index e1ed327fb9..1f2fe61fe1 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -61,6 +61,21 @@ DEPENDENCIES = ["esp32"] AUTO_LOAD = ["network"] LOGGER = logging.getLogger(__name__) +# Key for tracking IP state listener count in CORE.data +ETHERNET_IP_STATE_LISTENERS_KEY = "ethernet_ip_state_listeners" + + +def request_ethernet_ip_state_listener() -> None: + """Request an IP state listener slot. + + Components that implement EthernetIPStateListener should call this + in their to_code() to register for IP state notifications. + """ + CORE.data[ETHERNET_IP_STATE_LISTENERS_KEY] = ( + CORE.data.get(ETHERNET_IP_STATE_LISTENERS_KEY, 0) + 1 + ) + + # RMII pins that are hardcoded on ESP32 classic and cannot be changed # These pins are used by the internal Ethernet MAC when using RMII PHYs ESP32_RMII_FIXED_PINS = { @@ -220,10 +235,6 @@ BASE_SCHEMA = cv.Schema( cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, - cv.Optional("enable_mdns"): cv.invalid( - "This option has been removed. Please use the [disabled] option under the " - "new mdns component instead." - ), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, } ).extend(cv.COMPONENT_SCHEMA) @@ -415,6 +426,8 @@ async def to_code(config): if CORE.using_arduino: cg.add_library("WiFi", None) + CORE.add_job(final_step) + def _final_validate_rmii_pins(config: ConfigType) -> None: """Validate that RMII pins are not used by other components.""" @@ -471,3 +484,11 @@ def _final_validate(config: ConfigType) -> ConfigType: FINAL_VALIDATE_SCHEMA = _final_validate + + +@coroutine_with_priority(CoroPriority.FINAL) +async def final_step(): + """Final code generation step to configure optional Ethernet features.""" + if ip_state_count := CORE.data.get(ETHERNET_IP_STATE_LISTENERS_KEY, 0): + cg.add_define("USE_ETHERNET_IP_STATE_LISTENERS") + cg.add_define("ESPHOME_ETHERNET_IP_STATE_LISTENERS", ip_state_count) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 793ebdec42..70f8ce1204 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -1,5 +1,6 @@ #include "ethernet_component.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" @@ -39,6 +40,9 @@ namespace ethernet { static const char *const TAG = "ethernet"; +// PHY register size for hex logging +static constexpr size_t PHY_REG_SIZE = 2; + EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) { @@ -468,6 +472,12 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base break; case ETHERNET_EVENT_CONNECTED: event_name = "ETH connected"; + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP) + if (global_eth_component->manual_ip_.has_value()) { + global_eth_component->notify_ip_state_listeners_(); + } +#endif break; case ETHERNET_EVENT_DISCONNECTED: event_name = "ETH disconnected"; @@ -494,6 +504,9 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b global_eth_component->connected_ = true; global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes #endif /* USE_NETWORK_IPV6 */ +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + global_eth_component->notify_ip_state_listeners_(); +#endif } #if USE_NETWORK_IPV6 @@ -510,9 +523,23 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_ global_eth_component->connected_ = global_eth_component->got_ipv4_address_; global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes #endif +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + global_eth_component->notify_ip_state_listeners_(); +#endif } #endif /* USE_NETWORK_IPV6 */ +#ifdef USE_ETHERNET_IP_STATE_LISTENERS +void EthernetComponent::notify_ip_state_listeners_() { + auto ips = this->get_ip_addresses(); + auto dns1 = this->get_dns_address(0); + auto dns2 = this->get_dns_address(1); + for (auto *listener : this->ip_state_listeners_) { + listener->on_ip_state(ips, dns1, dns2); + } +} +#endif // USE_ETHERNET_IP_STATE_LISTENERS + void EthernetComponent::finish_connect_() { #if USE_NETWORK_IPV6 // Retry IPv6 link-local setup if it failed during initial connect @@ -644,6 +671,12 @@ void EthernetComponent::dump_connect_params_() { dns_ip2 = dns_getserver(1); } + // Use stack buffers for IP address formatting to avoid heap allocations + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " IP Address: %s\n" " Hostname: '%s'\n" @@ -651,9 +684,9 @@ void EthernetComponent::dump_connect_params_() { " Gateway: %s\n" " DNS1: %s\n" " DNS2: %s", - network::IPAddress(&ip.ip).str().c_str(), App.get_name().c_str(), - network::IPAddress(&ip.netmask).str().c_str(), network::IPAddress(&ip.gw).str().c_str(), - network::IPAddress(dns_ip1).str().c_str(), network::IPAddress(dns_ip2).str().c_str()); + network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(), + network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf), + network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf)); #if USE_NETWORK_IPV6 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; @@ -665,12 +698,13 @@ void EthernetComponent::dump_connect_params_() { } #endif /* USE_NETWORK_IPV6 */ + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " MAC Address: %s\n" " Is Full Duplex: %s\n" " Link Speed: %u", - this->get_eth_mac_address_pretty().c_str(), YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), - this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); + this->get_eth_mac_address_pretty_into_buffer(mac_buf), + YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); } #ifdef USE_ETHERNET_SPI @@ -711,11 +745,16 @@ void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { } std::string EthernetComponent::get_eth_mac_address_pretty() { + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + return std::string(this->get_eth_mac_address_pretty_into_buffer(buf)); +} + +const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer( + std::span buf) { uint8_t mac[6]; get_eth_mac_address_raw(mac); - char buf[18]; - format_mac_addr_upper(mac, buf); - return std::string(buf); + format_mac_addr_upper(mac, buf.data()); + return buf.data(); } eth_duplex_t EthernetComponent::get_duplex_mode() { @@ -761,7 +800,10 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { uint32_t phy_control_2; err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); - ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)]; +#endif + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); /* * Bit 7 is `RMII Reference Clock Select`. Default is `0`. @@ -778,7 +820,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed"); err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); - ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str()); + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", + format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); } } #endif // USE_ETHERNET_KSZ8081 @@ -793,8 +836,10 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi ESPHL_ERROR_CHECK(err, "Select PHY Register page failed"); } - ESP_LOGD(TAG, "Writing to PHY Register Address: 0x%02" PRIX32, register_data.address); - ESP_LOGD(TAG, "Writing to PHY Register Value: 0x%04" PRIX32, register_data.value); + ESP_LOGD(TAG, + "Writing to PHY Register Address: 0x%02" PRIX32 "\n" + "Writing to PHY Register Value: 0x%04" PRIX32, + register_data.address, register_data.value); err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value); ESPHL_ERROR_CHECK(err, "Writing PHY Register failed"); diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index bffed4dc4a..34380047d1 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #include "esphome/components/network/ip_address.h" #ifdef USE_ESP32 @@ -16,6 +17,22 @@ namespace esphome { namespace ethernet { +#ifdef USE_ETHERNET_IP_STATE_LISTENERS +/** Listener interface for Ethernet IP state changes. + * + * Components can implement this interface to receive IP address updates + * without the overhead of std::function callbacks or polling. + * + * @note Components must call ethernet.request_ethernet_ip_state_listener() in their + * Python to_code() to register for this listener type. + */ +class EthernetIPStateListener { + public: + virtual void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) = 0; +}; +#endif // USE_ETHERNET_IP_STATE_LISTENERS + enum EthernetType : uint8_t { ETHERNET_TYPE_UNKNOWN = 0, ETHERNET_TYPE_LAN8720, @@ -93,16 +110,24 @@ class EthernetComponent : public Component { void set_use_address(const char *use_address); void get_eth_mac_address_raw(uint8_t *mac); std::string get_eth_mac_address_pretty(); + const char *get_eth_mac_address_pretty_into_buffer(std::span buf); eth_duplex_t get_duplex_mode(); eth_speed_t get_link_speed(); bool powerdown(); +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); } +#endif + protected: static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); #if LWIP_IPV6 static void got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); #endif /* LWIP_IPV6 */ +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + void notify_ip_state_listeners_(); +#endif void start_connect_(); void finish_connect_(); @@ -161,6 +186,10 @@ class EthernetComponent : public Component { esp_eth_phy_t *phy_{nullptr}; optional> fixed_mac_; +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + StaticVector ip_state_listeners_; +#endif + private: // Stores a pointer to a string literal (static storage duration). // ONLY set from Python-generated code with string literals - never dynamic strings. diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp index 329fb9113a..72ce9c86e2 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -3,16 +3,50 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ethernet_info { +namespace esphome::ethernet_info { static const char *const TAG = "ethernet_info"; +#ifdef USE_ETHERNET_IP_STATE_LISTENERS +void IPAddressEthernetInfo::setup() { ethernet::global_eth_component->add_ip_state_listener(this); } + void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); } + +void IPAddressEthernetInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) { + char buf[network::IP_ADDRESS_BUFFER_SIZE]; + ips[0].str_to(buf); + this->publish_state(buf); + uint8_t sensor = 0; + for (const auto &ip : ips) { + if (ip.is_set()) { + if (this->ip_sensors_[sensor] != nullptr) { + ip.str_to(buf); + this->ip_sensors_[sensor]->publish_state(buf); + } + sensor++; + } + } +} + +void DNSAddressEthernetInfo::setup() { ethernet::global_eth_component->add_ip_state_listener(this); } + void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); } + +void DNSAddressEthernetInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) { + // IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot + char buf[network::IP_ADDRESS_BUFFER_SIZE * 2]; + dns1.str_to(buf); + size_t len1 = strlen(buf); + buf[len1] = ' '; + dns2.str_to(buf + len1 + 1); + this->publish_state(buf); +} +#endif // USE_ETHERNET_IP_STATE_LISTENERS + void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); } -} // namespace ethernet_info -} // namespace esphome +} // namespace esphome::ethernet_info #endif // USE_ESP32 diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index 2adc08e31e..912a39a83f 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -6,65 +6,48 @@ #ifdef USE_ESP32 -namespace esphome { -namespace ethernet_info { +namespace esphome::ethernet_info { -class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { +#ifdef USE_ETHERNET_IP_STATE_LISTENERS +class IPAddressEthernetInfo final : public Component, + public text_sensor::TextSensor, + public ethernet::EthernetIPStateListener { public: - void update() override { - auto ips = ethernet::global_eth_component->get_ip_addresses(); - if (ips != this->last_ips_) { - this->last_ips_ = ips; - this->publish_state(ips[0].str()); - uint8_t sensor = 0; - for (auto &ip : ips) { - if (ip.is_set()) { - if (this->ip_sensors_[sensor] != nullptr) { - this->ip_sensors_[sensor]->publish_state(ip.str()); - } - sensor++; - } - } - } - } - - float get_setup_priority() const override { return setup_priority::ETHERNET; } + void setup() override; void dump_config() override; void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } + // EthernetIPStateListener interface + void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) override; + protected: - network::IPAddresses last_ips_; - std::array ip_sensors_; + std::array ip_sensors_{}; }; -class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { +class DNSAddressEthernetInfo final : public Component, + public text_sensor::TextSensor, + public ethernet::EthernetIPStateListener { public: - void update() override { - auto dns_one = ethernet::global_eth_component->get_dns_address(0); - auto dns_two = ethernet::global_eth_component->get_dns_address(1); + void setup() override; + void dump_config() override; - std::string dns_results = dns_one.str() + " " + dns_two.str(); + // EthernetIPStateListener interface + void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, + const network::IPAddress &dns2) override; +}; +#endif // USE_ETHERNET_IP_STATE_LISTENERS - if (dns_results != this->last_results_) { - this->last_results_ = dns_results; - this->publish_state(dns_results); - } +class MACAddressEthernetInfo final : public Component, public text_sensor::TextSensor { + public: + void setup() override { + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty_into_buffer(buf)); } float get_setup_priority() const override { return setup_priority::ETHERNET; } void dump_config() override; - - protected: - std::string last_results_; }; -class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor { - public: - void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); } - float get_setup_priority() const override { return setup_priority::ETHERNET; } - void dump_config() override; -}; - -} // namespace ethernet_info -} // namespace esphome +} // namespace esphome::ethernet_info #endif // USE_ESP32 diff --git a/esphome/components/ethernet_info/text_sensor.py b/esphome/components/ethernet_info/text_sensor.py index 31da516e44..8c20cf332c 100644 --- a/esphome/components/ethernet_info/text_sensor.py +++ b/esphome/components/ethernet_info/text_sensor.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import text_sensor +from esphome.components import ethernet, text_sensor import esphome.config_validation as cv from esphome.const import ( CONF_DNS_ADDRESS, @@ -13,24 +13,22 @@ DEPENDENCIES = ["ethernet"] ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info") IPAddressEthernetInfo = ethernet_info_ns.class_( - "IPAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent + "IPAddressEthernetInfo", text_sensor.TextSensor, cg.Component ) DNSAddressEthernetInfo = ethernet_info_ns.class_( - "DNSAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent + "DNSAddressEthernetInfo", text_sensor.TextSensor, cg.Component ) MACAddressEthernetInfo = ethernet_info_ns.class_( - "MACAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent + "MACAddressEthernetInfo", text_sensor.TextSensor, cg.Component ) CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( IPAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ) - .extend(cv.polling_component_schema("1s")) - .extend( + ).extend( { cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, @@ -40,7 +38,7 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema( DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC - ).extend(cv.polling_component_schema("1s")), + ), cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( MACAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), @@ -49,6 +47,12 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): + # Request Ethernet IP state listener slots - one per sensor type + if CONF_IP_ADDRESS in config: + ethernet.request_ethernet_ip_state_listener() + if CONF_DNS_ADDRESS in config: + ethernet.request_ethernet_ip_state_listener() + if conf := config.get(CONF_IP_ADDRESS): ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS]) await cg.register_component(ip_info, config[CONF_IP_ADDRESS]) @@ -57,8 +61,8 @@ async def to_code(config): sens = await text_sensor.new_text_sensor(sensor_conf) cg.add(ip_info.add_ip_sensors(x, sens)) if conf := config.get(CONF_DNS_ADDRESS): - dns_info = await text_sensor.new_text_sensor(config[CONF_DNS_ADDRESS]) - await cg.register_component(dns_info, config[CONF_DNS_ADDRESS]) + dns_info = await text_sensor.new_text_sensor(conf) + await cg.register_component(dns_info, conf) if conf := config.get(CONF_MAC_ADDRESS): - mac_info = await text_sensor.new_text_sensor(config[CONF_MAC_ADDRESS]) - await cg.register_component(mac_info, config[CONF_MAC_ADDRESS]) + mac_info = await text_sensor.new_text_sensor(conf) + await cg.register_component(mac_info, conf) diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index e4b2e0b845..f77ad326d9 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -1,12 +1,14 @@ #pragma once #include +#include #include #include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace event { @@ -44,13 +46,34 @@ class Event : public EntityBase, public EntityBase_DeviceClass { /// Return the event types supported by this event. const FixedVector &get_event_types() const { return this->types_; } - /// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet. - const char *get_last_event_type() const { return this->last_event_type_; } + /// Return the last triggered event type, or empty StringRef if no event triggered yet. + StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); } + + /// Return event type by index, or nullptr if index is out of bounds. + const char *get_event_type(uint8_t index) const { + return index < this->types_.size() ? this->types_[index] : nullptr; + } + + /// Return index of last triggered event type, or max uint8_t if no event triggered yet. + uint8_t get_last_event_type_index() const { + if (this->last_event_type_ == nullptr) + return std::numeric_limits::max(); + // Most events have <3 types, uint8_t is sufficient for all reasonable scenarios + const uint8_t size = static_cast(this->types_.size()); + for (uint8_t i = 0; i < size; i++) { + if (this->types_[i] == this->last_event_type_) + return i; + } + return std::numeric_limits::max(); + } + + /// Check if an event has been triggered. + bool has_event() const { return this->last_event_type_ != nullptr; } void add_on_event_callback(std::function &&callback); protected: - CallbackManager event_callback_; + LazyCallbackManager event_callback_; FixedVector types_; private: diff --git a/esphome/components/ezo_pmp/ezo_pmp.cpp b/esphome/components/ezo_pmp/ezo_pmp.cpp index 9ec41cce30..61b601328a 100644 --- a/esphome/components/ezo_pmp/ezo_pmp.cpp +++ b/esphome/components/ezo_pmp/ezo_pmp.cpp @@ -148,10 +148,13 @@ void EzoPMP::read_command_result_() { char current_char = response_buffer[i]; if (current_char == '\0') { - ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer); - ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer); - ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer); - ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer); + ESP_LOGV(TAG, + "Read Response from device: %s\n" + "First Component: %s\n" + "Second Component: %s\n" + "Third Component: %s", + (char *) response_buffer, (char *) first_parameter_buffer, (char *) second_parameter_buffer, + (char *) third_parameter_buffer); break; } diff --git a/esphome/components/factory_reset/__init__.py b/esphome/components/factory_reset/__init__.py index f3cefe6970..5784d09ce6 100644 --- a/esphome/components/factory_reset/__init__.py +++ b/esphome/components/factory_reset/__init__.py @@ -50,7 +50,9 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(FactoryResetComponent), cv.Optional(CONF_MAX_DELAY, default="10s"): cv.All( cv.positive_time_period_seconds, - cv.Range(min=cv.TimePeriod(milliseconds=1000)), + cv.Range( + min=cv.TimePeriod(seconds=1), max=cv.TimePeriod(seconds=65535) + ), ), cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int, cv.Optional(CONF_ON_INCREMENT): validate_automation( @@ -82,7 +84,7 @@ async def to_code(config): var = cg.new_Pvariable( config[CONF_ID], reset_count, - config[CONF_MAX_DELAY].total_milliseconds, + config[CONF_MAX_DELAY].total_seconds, ) await cg.register_component(var, config) for conf in config.get(CONF_ON_INCREMENT, []): diff --git a/esphome/components/factory_reset/factory_reset.cpp b/esphome/components/factory_reset/factory_reset.cpp index c900759d90..2e3f802343 100644 --- a/esphome/components/factory_reset/factory_reset.cpp +++ b/esphome/components/factory_reset/factory_reset.cpp @@ -8,8 +8,7 @@ #if !defined(USE_RP2040) && !defined(USE_HOST) -namespace esphome { -namespace factory_reset { +namespace esphome::factory_reset { static const char *const TAG = "factory_reset"; static const uint32_t POWER_CYCLES_KEY = 0xFA5C0DE; @@ -31,12 +30,12 @@ static bool was_power_cycled() { void FactoryResetComponent::dump_config() { uint8_t count = 0; this->flash_.load(&count); - ESP_LOGCONFIG(TAG, "Factory Reset by Reset:"); ESP_LOGCONFIG(TAG, - " Max interval between resets %" PRIu32 " seconds\n" + "Factory Reset by Reset:\n" + " Max interval between resets: %u seconds\n" " Current count: %u\n" " Factory reset after %u resets", - this->max_interval_ / 1000, count, this->required_count_); + this->max_interval_, count, this->required_count_); } void FactoryResetComponent::save_(uint8_t count) { @@ -61,8 +60,8 @@ void FactoryResetComponent::setup() { } this->save_(count); ESP_LOGD(TAG, "Power on reset detected, incremented count to %u", count); - this->set_timeout(this->max_interval_, [this]() { - ESP_LOGD(TAG, "No reset in the last %" PRIu32 " seconds, resetting count", this->max_interval_ / 1000); + this->set_timeout(static_cast(this->max_interval_) * 1000, [this]() { + ESP_LOGD(TAG, "No reset in the last %u seconds, resetting count", this->max_interval_); this->save_(0); // reset count }); } else { @@ -70,7 +69,6 @@ void FactoryResetComponent::setup() { } } -} // namespace factory_reset -} // namespace esphome +} // namespace esphome::factory_reset #endif // !defined(USE_RP2040) && !defined(USE_HOST) diff --git a/esphome/components/factory_reset/factory_reset.h b/esphome/components/factory_reset/factory_reset.h index 80942b29bd..990bb2edb6 100644 --- a/esphome/components/factory_reset/factory_reset.h +++ b/esphome/components/factory_reset/factory_reset.h @@ -9,12 +9,11 @@ #include #endif -namespace esphome { -namespace factory_reset { +namespace esphome::factory_reset { class FactoryResetComponent : public Component { public: - FactoryResetComponent(uint8_t required_count, uint32_t max_interval) - : required_count_(required_count), max_interval_(max_interval) {} + FactoryResetComponent(uint8_t required_count, uint16_t max_interval) + : max_interval_(max_interval), required_count_(required_count) {} void dump_config() override; void setup() override; @@ -26,9 +25,9 @@ class FactoryResetComponent : public Component { ~FactoryResetComponent() = default; void save_(uint8_t count); ESPPreferenceObject flash_{}; // saves the number of fast power cycles - uint8_t required_count_; // The number of boot attempts before fast boot is enabled - uint32_t max_interval_; // max interval between power cycles CallbackManager increment_callback_{}; + uint16_t max_interval_; // max interval between power cycles in seconds + uint8_t required_count_; // The number of boot attempts before fast boot is enabled }; class FastBootTrigger : public Trigger { @@ -37,7 +36,6 @@ class FastBootTrigger : public Trigger { parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); }); } }; -} // namespace factory_reset -} // namespace esphome +} // namespace esphome::factory_reset #endif // !defined(USE_RP2040) && !defined(USE_HOST) diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index ce1db6fc64..77abc2f13f 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -212,19 +212,18 @@ class FanPresetSetTrigger : public Trigger { public: FanPresetSetTrigger(Fan *state) { state->add_on_state_callback([this, state]() { - const auto *preset_mode = state->get_preset_mode(); + auto preset_mode = state->get_preset_mode(); auto should_trigger = preset_mode != this->last_preset_mode_; this->last_preset_mode_ = preset_mode; if (should_trigger) { - // Trigger with empty string when nullptr to maintain backward compatibility - this->trigger(preset_mode != nullptr ? preset_mode : ""); + this->trigger(std::string(preset_mode)); } }); this->last_preset_mode_ = state->get_preset_mode(); } protected: - const char *last_preset_mode_{nullptr}; + StringRef last_preset_mode_{}; }; } // namespace fan diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index d37825a651..2e48d84eb9 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -19,22 +19,28 @@ const LogString *fan_direction_to_string(FanDirection direction) { } } -FanCall &FanCall::set_preset_mode(const std::string &preset_mode) { return this->set_preset_mode(preset_mode.c_str()); } +FanCall &FanCall::set_preset_mode(const std::string &preset_mode) { + return this->set_preset_mode(preset_mode.data(), preset_mode.size()); +} FanCall &FanCall::set_preset_mode(const char *preset_mode) { - if (preset_mode == nullptr || strlen(preset_mode) == 0) { + return this->set_preset_mode(preset_mode, preset_mode ? strlen(preset_mode) : 0); +} + +FanCall &FanCall::set_preset_mode(const char *preset_mode, size_t len) { + if (preset_mode == nullptr || len == 0) { this->preset_mode_ = nullptr; return *this; } // Find and validate pointer from traits immediately auto traits = this->parent_.get_traits(); - const char *validated_mode = traits.find_preset_mode(preset_mode); + const char *validated_mode = traits.find_preset_mode(preset_mode, len); if (validated_mode != nullptr) { this->preset_mode_ = validated_mode; // Store pointer from traits } else { // Preset mode not found in traits - log warning and don't set - ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), preset_mode); + ESP_LOGW(TAG, "%s: Preset mode '%.*s' not supported", this->parent_.get_name().c_str(), (int) len, preset_mode); this->preset_mode_ = nullptr; } return *this; @@ -55,7 +61,7 @@ void FanCall::perform() { if (this->direction_.has_value()) { ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); } - if (this->has_preset_mode()) { + if (this->preset_mode_ != nullptr) { ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_); } this->parent_.control(*this); @@ -77,7 +83,7 @@ void FanCall::validate_() { *this->binary_state_ // ..,and no preset mode will be active... && !this->has_preset_mode() && - this->parent_.get_preset_mode() == nullptr + !this->parent_.has_preset_mode() // ...and neither current nor new speed is available... && traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) { // ...set speed to 100% @@ -140,18 +146,24 @@ FanCall Fan::turn_off() { return this->make_call().set_state(false); } FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } FanCall Fan::make_call() { return FanCall(*this); } -const char *Fan::find_preset_mode_(const char *preset_mode) { return this->get_traits().find_preset_mode(preset_mode); } +const char *Fan::find_preset_mode_(const char *preset_mode) { + return this->find_preset_mode_(preset_mode, preset_mode ? strlen(preset_mode) : 0); +} -bool Fan::set_preset_mode_(const char *preset_mode) { - if (preset_mode == nullptr) { - // Treat nullptr as clearing the preset mode +const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) { + return this->get_traits().find_preset_mode(preset_mode, len); +} + +bool Fan::set_preset_mode_(const char *preset_mode, size_t len) { + if (preset_mode == nullptr || len == 0) { + // Treat nullptr or empty string as clearing the preset mode (no valid preset is "") if (this->preset_mode_ == nullptr) { return false; // No change } this->clear_preset_mode_(); return true; } - const char *validated = this->find_preset_mode_(preset_mode); + const char *validated = this->find_preset_mode_(preset_mode, len); if (validated == nullptr || this->preset_mode_ == validated) { return false; // Preset mode not supported or no change } @@ -159,16 +171,39 @@ bool Fan::set_preset_mode_(const char *preset_mode) { return true; } -bool Fan::set_preset_mode_(const std::string &preset_mode) { return this->set_preset_mode_(preset_mode.c_str()); } +bool Fan::set_preset_mode_(const char *preset_mode) { + return this->set_preset_mode_(preset_mode, preset_mode ? strlen(preset_mode) : 0); +} + +bool Fan::set_preset_mode_(const std::string &preset_mode) { + return this->set_preset_mode_(preset_mode.data(), preset_mode.size()); +} + +bool Fan::set_preset_mode_(StringRef preset_mode) { + // Safe: find_preset_mode_ only uses the input for comparison and returns + // a pointer from traits, so the input StringRef's lifetime doesn't matter. + return this->set_preset_mode_(preset_mode.c_str(), preset_mode.size()); +} void Fan::clear_preset_mode_() { this->preset_mode_ = nullptr; } +void Fan::apply_preset_mode_(const FanCall &call) { + if (call.has_preset_mode()) { + this->set_preset_mode_(call.get_preset_mode()); + } else if (call.get_speed().has_value()) { + // Manually setting speed clears preset (per Home Assistant convention) + this->clear_preset_mode_(); + } +} + void Fan::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } void Fan::publish_state() { auto traits = this->get_traits(); - ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); - ESP_LOGD(TAG, " State: %s", ONOFF(this->state)); + ESP_LOGD(TAG, + "'%s' - Sending state:\n" + " State: %s", + this->name_.c_str(), ONOFF(this->state)); if (traits.supports_speed()) { ESP_LOGD(TAG, " Speed: %d", this->speed); } @@ -178,9 +213,8 @@ void Fan::publish_state() { if (traits.supports_direction()) { ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); } - const char *preset = this->get_preset_mode(); - if (preset != nullptr) { - ESP_LOGD(TAG, " Preset Mode: %s", preset); + if (this->preset_mode_ != nullptr) { + ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_); } this->state_callback_.call(); #if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY) @@ -235,12 +269,11 @@ void Fan::save_state_() { state.speed = this->speed; state.direction = this->direction; - const char *preset = this->get_preset_mode(); - if (preset != nullptr) { + if (this->has_preset_mode()) { const auto &preset_modes = traits.supported_preset_modes(); // Find index of current preset mode (pointer comparison is safe since preset is from traits) for (size_t i = 0; i < preset_modes.size(); i++) { - if (preset_modes[i] == preset) { + if (preset_modes[i] == this->preset_mode_) { state.preset_mode = i; break; } diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index e38a80dbbe..55d4ba8825 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -5,6 +5,7 @@ #include "esphome/core/log.h" #include "esphome/core/optional.h" #include "esphome/core/preferences.h" +#include "esphome/core/string_ref.h" #include "fan_traits.h" namespace esphome { @@ -72,6 +73,7 @@ class FanCall { optional get_direction() const { return this->direction_; } FanCall &set_preset_mode(const std::string &preset_mode); FanCall &set_preset_mode(const char *preset_mode); + FanCall &set_preset_mode(const char *preset_mode, size_t len); const char *get_preset_mode() const { return this->preset_mode_; } bool has_preset_mode() const { return this->preset_mode_ != nullptr; } @@ -127,8 +129,11 @@ class Fan : public EntityBase { /// Set the restore mode of this fan. void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } - /// Get the current preset mode (returns pointer to string stored in traits, or nullptr if not set) - const char *get_preset_mode() const { return this->preset_mode_; } + /// Get the current preset mode. + /// Returns a StringRef of the string stored in traits, or empty ref if not set. + /// The returned ref points to string literals from codegen (static storage). + /// Traits are set once at startup and valid for the lifetime of the program. + StringRef get_preset_mode() const { return StringRef::from_maybe_nullptr(this->preset_mode_); } /// Check if a preset mode is currently active bool has_preset_mode() const { return this->preset_mode_ != nullptr; } @@ -145,15 +150,20 @@ class Fan : public EntityBase { void dump_traits_(const char *tag, const char *prefix); /// Set the preset mode (finds and stores pointer from traits). Returns true if changed. + /// Passing nullptr or empty string clears the preset mode. + bool set_preset_mode_(const char *preset_mode, size_t len); bool set_preset_mode_(const char *preset_mode); - /// Set the preset mode (finds and stores pointer from traits). Returns true if changed. bool set_preset_mode_(const std::string &preset_mode); + bool set_preset_mode_(StringRef preset_mode); /// Clear the preset mode void clear_preset_mode_(); + /// Apply preset mode from a FanCall (handles speed-clears-preset convention) + void apply_preset_mode_(const FanCall &call); /// Find and return the matching preset mode pointer from traits, or nullptr if not found. const char *find_preset_mode_(const char *preset_mode); + const char *find_preset_mode_(const char *preset_mode, size_t len); - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; FanRestoreMode restore_mode_; diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 24987fe984..c0c5f34c50 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -47,10 +47,13 @@ class FanTraits { bool supports_preset_modes() const { return !this->preset_modes_.empty(); } /// Find and return the matching preset mode pointer from supported modes, or nullptr if not found. const char *find_preset_mode(const char *preset_mode) const { - if (preset_mode == nullptr) + return this->find_preset_mode(preset_mode, preset_mode ? strlen(preset_mode) : 0); + } + const char *find_preset_mode(const char *preset_mode, size_t len) const { + if (preset_mode == nullptr || len == 0) return nullptr; for (const char *mode : this->preset_modes_) { - if (strcmp(mode, preset_mode) == 0) { + if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') { return mode; // Return pointer from traits } } diff --git a/esphome/components/gcja5/gcja5.cpp b/esphome/components/gcja5/gcja5.cpp index a7342bc828..f7f7f8d02c 100644 --- a/esphome/components/gcja5/gcja5.cpp +++ b/esphome/components/gcja5/gcja5.cpp @@ -95,11 +95,13 @@ void GCJA5Component::parse_data_() { if (!this->first_status_log_) { this->first_status_log_ = true; - ESP_LOGI(TAG, "GCJA5 Status"); - ESP_LOGI(TAG, "Overall Status : %i", (status >> 6) & 0x03); - ESP_LOGI(TAG, "PD Status : %i", (status >> 4) & 0x03); - ESP_LOGI(TAG, "LD Status : %i", (status >> 2) & 0x03); - ESP_LOGI(TAG, "Fan Status : %i", (status >> 0) & 0x03); + ESP_LOGI(TAG, + "GCJA5 Status\n" + "Overall Status : %i\n" + "PD Status : %i\n" + "LD Status : %i\n" + "Fan Status : %i", + (status >> 6) & 0x03, (status >> 4) & 0x03, (status >> 2) & 0x03, (status >> 0) & 0x03); } } diff --git a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp index e2a64b6877..38328c4b03 100644 --- a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp +++ b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp @@ -27,8 +27,10 @@ void GLR01I2CComponent::setup() { } void GLR01I2CComponent::dump_config() { - ESP_LOGCONFIG(TAG, "GL-R01 I2C:"); - ESP_LOGCONFIG(TAG, " Firmware Version: 0x%04X", this->version_); + ESP_LOGCONFIG(TAG, + "GL-R01 I2C:\n" + " Firmware Version: 0x%04X", + this->version_); LOG_I2C_DEVICE(this); LOG_SENSOR(" ", "Distance", this); } diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index a872cf7015..2135189bd5 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_SPEED, DEVICE_CLASS_SPEED, STATE_CLASS_MEASUREMENT, + STATE_CLASS_MEASUREMENT_ANGLE, UNIT_DEGREES, UNIT_KILOMETER_PER_HOUR, UNIT_METER, @@ -21,6 +22,7 @@ CONF_HDOP = "hdop" ICON_ALTIMETER = "mdi:altimeter" ICON_COMPASS = "mdi:compass" +ICON_CIRCLE_DOUBLE = "mdi:circle-double" ICON_LATITUDE = "mdi:latitude" ICON_LONGITUDE = "mdi:longitude" ICON_SATELLITE = "mdi:satellite-variant" @@ -50,7 +52,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_DEGREES, icon=ICON_LONGITUDE, accuracy_decimals=6, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_MEASUREMENT_ANGLE, ), cv.Optional(CONF_SPEED): sensor.sensor_schema( unit_of_measurement=UNIT_KILOMETER_PER_HOUR, @@ -63,7 +65,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_DEGREES, icon=ICON_COMPASS, accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, + state_class=STATE_CLASS_MEASUREMENT_ANGLE, ), cv.Optional(CONF_ALTITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_METER, @@ -72,11 +74,14 @@ CONFIG_SCHEMA = cv.All( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SATELLITES): sensor.sensor_schema( + # no unit_of_measurement icon=ICON_SATELLITE, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HDOP): sensor.sensor_schema( + # no unit_of_measurement + icon=ICON_CIRCLE_DOUBLE, accuracy_decimals=3, state_class=STATE_CLASS_MEASUREMENT, ), diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index e3b9119108..c43cd07fe0 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -232,17 +232,19 @@ void GraphLegend::init(Graph *g) { ESP_LOGI(TAGL, " %s %d %d", txtstr.c_str(), fw, fh); if (this->values_ != VALUE_POSITION_TYPE_NONE) { - std::string valstr = - value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals()); + char valstr[VALUE_ACCURACY_MAX_LEN]; if (this->units_) { - valstr += trace->sensor_->get_unit_of_measurement_ref(); + value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(), + trace->sensor_->get_unit_of_measurement_ref()); + } else { + value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals()); } - this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh); + this->font_value_->measure(valstr, &fw, &fos, &fbl, &fh); if (fw > valw) valw = fw; if (fh > valh) valh = fh; - ESP_LOGI(TAGL, " %s %d %d", valstr.c_str(), fw, fh); + ESP_LOGI(TAGL, " %s %d %d", valstr, fw, fh); } } // Add extra margin @@ -368,13 +370,15 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of if (legend_->values_ != VALUE_POSITION_TYPE_NONE) { int xv = x + legend_->xv_; int yv = y + legend_->yv_; - std::string valstr = - value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals()); + char valstr[VALUE_ACCURACY_MAX_LEN]; if (legend_->units_) { - valstr += trace->sensor_->get_unit_of_measurement_ref(); + value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(), + trace->sensor_->get_unit_of_measurement_ref()); + } else { + value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals()); } - buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str()); - ESP_LOGV(TAG, " value: %s", valstr.c_str()); + buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr); + ESP_LOGV(TAG, " value: %s", valstr); } x += legend_->xs_; y += legend_->ys_; diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 488208b725..9bf58f9d1e 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -57,7 +57,7 @@ void HBridgeFan::control(const fan::FanCall &call) { this->oscillating = *call.get_oscillating(); if (call.get_direction().has_value()) this->direction = *call.get_direction(); - this->set_preset_mode_(call.get_preset_mode()); + this->apply_preset_mode_(call); this->write_state_(); this->publish_state(); diff --git a/esphome/components/hc8/hc8.cpp b/esphome/components/hc8/hc8.cpp index 5b649c2735..4d0f77df1b 100644 --- a/esphome/components/hc8/hc8.cpp +++ b/esphome/components/hc8/hc8.cpp @@ -89,11 +89,12 @@ void HC8Component::calibrate(uint16_t baseline) { float HC8Component::get_setup_priority() const { return setup_priority::DATA; } void HC8Component::dump_config() { - ESP_LOGCONFIG(TAG, "HC8:"); + ESP_LOGCONFIG(TAG, + "HC8:\n" + " Warmup time: %" PRIu32 " s", + this->warmup_seconds_); LOG_SENSOR(" ", "CO2", this->co2_sensor_); this->check_uart_settings(9600); - - ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_); } } // namespace esphome::hc8 diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index f4d2ca6c1d..67447a3123 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -181,6 +181,11 @@ void HeatpumpIRClimate::transmit_state() { power_mode_cmd = POWER_ON; operating_mode_cmd = MODE_HEAT; break; + // Map HEAT_COOL to hardware AUTO mode (automatic heat/cool changeover based on temperature). + // In hardware AUTO mode, the device automatically switches between heating and cooling + // based on the current temperature versus the target temperature. + // See https://github.com/esphome/esphome/issues/11161 for further discussion. + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_AUTO: power_mode_cmd = POWER_ON; operating_mode_cmd = MODE_AUTO; diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.cpp b/esphome/components/hlk_fm22x/hlk_fm22x.cpp index ab15a2340d..c0f14c7105 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.cpp +++ b/esphome/components/hlk_fm22x/hlk_fm22x.cpp @@ -8,6 +8,9 @@ namespace esphome::hlk_fm22x { static const char *const TAG = "hlk_fm22x"; +// Maximum response size is 36 bytes (VERIFY reply: face_id + 32-byte name) +static constexpr size_t HLK_FM22X_MAX_RESPONSE_SIZE = 36; + void HlkFm22xComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X..."); this->set_enrolling_(false); @@ -142,7 +145,10 @@ void HlkFm22xComponent::recv_command_() { data.push_back(byte); } - ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)]; + ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty_to(hex_buf, data.data(), data.size())); +#endif byte = this->read(); if (byte != checksum) { diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 73696bd2a5..f037ee9d8b 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -33,15 +33,15 @@ void HLW8012Component::setup() { } } void HLW8012Component::dump_config() { - ESP_LOGCONFIG(TAG, "HLW8012:"); - LOG_PIN(" SEL Pin: ", this->sel_pin_) - LOG_PIN(" CF Pin: ", this->cf_pin_) - LOG_PIN(" CF1 Pin: ", this->cf1_pin_) ESP_LOGCONFIG(TAG, + "HLW8012:\n" " Change measurement mode every %" PRIu32 "\n" " Current resistor: %.1f mΩ\n" " Voltage Divider: %.1f", this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_); + LOG_PIN(" SEL Pin: ", this->sel_pin_); + LOG_PIN(" CF Pin: ", this->cf_pin_); + LOG_PIN(" CF1 Pin: ", this->cf1_pin_); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); diff --git a/esphome/components/hm3301/abstract_aqi_calculator.h b/esphome/components/hm3301/abstract_aqi_calculator.h deleted file mode 100644 index 038828e9de..0000000000 --- a/esphome/components/hm3301/abstract_aqi_calculator.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace esphome { -namespace hm3301 { - -class AbstractAQICalculator { - public: - virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; -}; - -} // namespace hm3301 -} // namespace esphome diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h deleted file mode 100644 index aa01060d2c..0000000000 --- a/esphome/components/hm3301/aqi_calculator.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include -#include "abstract_aqi_calculator.h" -// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf - -namespace esphome { -namespace hm3301 { - -class AQICalculator : public AbstractAQICalculator { - public: - uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); - int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); - - return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; - } - - protected: - static const int AMOUNT_OF_LEVELS = 6; - - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; - - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, - {56, 125}, {126, 225}, {226, INT_MAX}}; - - int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, - {255, 354}, {355, 424}, {425, INT_MAX}}; - - int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - int grid_index = get_grid_index_(value, array); - int aqi_lo = index_grid_[grid_index][0]; - int aqi_hi = index_grid_[grid_index][1]; - int conc_lo = array[grid_index][0]; - int conc_hi = array[grid_index][1]; - - return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; - } - - int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - for (int i = 0; i < AMOUNT_OF_LEVELS; i++) { - if (value >= array[i][0] && value <= array[i][1]) { - return i; - } - } - return -1; - } -}; - -} // namespace hm3301 -} // namespace esphome diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h deleted file mode 100644 index 3f338776d8..0000000000 --- a/esphome/components/hm3301/caqi_calculator.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "esphome/core/log.h" -#include "abstract_aqi_calculator.h" - -namespace esphome { -namespace hm3301 { - -class CAQICalculator : public AbstractAQICalculator { - public: - uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); - int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); - - return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; - } - - protected: - static const int AMOUNT_OF_LEVELS = 5; - - int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; - - int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}}; - - int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}}; - - int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - int grid_index = get_grid_index_(value, array); - if (grid_index == -1) { - return -1; - } - - int aqi_lo = index_grid_[grid_index][0]; - int aqi_hi = index_grid_[grid_index][1]; - int conc_lo = array[grid_index][0]; - int conc_hi = array[grid_index][1]; - - return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; - } - - int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { - for (int i = 0; i < AMOUNT_OF_LEVELS; i++) { - if (value >= array[i][0] && value <= array[i][1]) { - return i; - } - } - return -1; - } -}; - -} // namespace hm3301 -} // namespace esphome diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index a19d9dd09f..00fb85397c 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -63,7 +63,7 @@ void HM3301Component::update() { int16_t aqi_value = -1; if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) { - AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); + 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); } diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index 6779b4e195..e155ed6b4b 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -3,7 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/i2c/i2c.h" -#include "aqi_calculator_factory.h" +#include "esphome/components/aqi/aqi_calculator_factory.h" namespace esphome { namespace hm3301 { @@ -19,7 +19,7 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { pm_10_0_sensor_ = pm_10_0_sensor; } void set_aqi_sensor(sensor::Sensor *aqi_sensor) { aqi_sensor_ = aqi_sensor; } - void set_aqi_calculation_type(AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } + void set_aqi_calculation_type(aqi::AQICalculatorType aqi_calc_type) { aqi_calc_type_ = aqi_calc_type; } void setup() override; void dump_config() override; @@ -41,8 +41,8 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *pm_10_0_sensor_{nullptr}; sensor::Sensor *aqi_sensor_{nullptr}; - AQICalculatorType aqi_calc_type_; - AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); + aqi::AQICalculatorType aqi_calc_type_; + aqi::AQICalculatorFactory aqi_calculator_factory_ = aqi::AQICalculatorFactory(); bool validate_checksum_(const uint8_t *data); uint16_t get_sensor_value_(const uint8_t *data, uint8_t i); diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 5eb1773518..9546ae1c3c 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -1,5 +1,8 @@ +import logging + import esphome.codegen as cg from esphome.components import i2c, sensor +from esphome.components.aqi import AQI_CALCULATION_TYPE, CONF_AQI, CONF_CALCULATION_TYPE import esphome.config_validation as cv from esphome.const import ( CONF_ID, @@ -15,24 +18,19 @@ from esphome.const import ( UNIT_MICROGRAMS_PER_CUBIC_METER, ) +_LOGGER = logging.getLogger(__name__) + DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["aqi"] CODEOWNERS = ["@freekode"] hm3301_ns = cg.esphome_ns.namespace("hm3301") HM3301Component = hm3301_ns.class_( "HM3301Component", cg.PollingComponent, i2c.I2CDevice ) -AQICalculatorType = hm3301_ns.enum("AQICalculatorType") -CONF_AQI = "aqi" -CONF_CALCULATION_TYPE = "calculation_type" UNIT_INDEX = "index" -AQI_CALCULATION_TYPE = { - "CAQI": AQICalculatorType.CAQI_TYPE, - "AQI": AQICalculatorType.AQI_TYPE, -} - def _validate(config): if CONF_AQI in config and CONF_PM_2_5 not in config: @@ -105,7 +103,12 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_PM_10_0]) cg.add(var.set_pm_10_0_sensor(sens)) + # Remove before 2026.12.0 if CONF_AQI in config: + _LOGGER.warning( + "The 'aqi' option in hm3301 is deprecated, " + "please use the standalone 'aqi' sensor platform instead." + ) sens = await sensor.new_sensor(config[CONF_AQI]) cg.add(var.set_aqi_sensor(sens)) cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) diff --git a/esphome/components/hmac_md5/__init__.py b/esphome/components/hmac_md5/__init__.py index fe245c0cfd..e37eb9b116 100644 --- a/esphome/components/hmac_md5/__init__.py +++ b/esphome/components/hmac_md5/__init__.py @@ -1,2 +1,6 @@ +import esphome.config_validation as cv + AUTO_LOAD = ["md5"] CODEOWNERS = ["@dwmw2"] + +CONFIG_SCHEMA = cv.Schema({}) diff --git a/esphome/components/hmac_md5/hmac_md5.h b/esphome/components/hmac_md5/hmac_md5.h index b83b9d5421..fb9479e3af 100644 --- a/esphome/components/hmac_md5/hmac_md5.h +++ b/esphome/components/hmac_md5/hmac_md5.h @@ -30,7 +30,7 @@ class HmacMD5 { void get_bytes(uint8_t *output); /// Retrieve the HMAC-MD5 digest as hex characters. - /// The output must be able to hold 32 bytes or more. + /// The output must be able to hold 33 bytes or more (32 hex chars + null terminator). void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (16 bytes). diff --git a/esphome/components/hmac_sha256/__init__.py b/esphome/components/hmac_sha256/__init__.py new file mode 100644 index 0000000000..158d740dc5 --- /dev/null +++ b/esphome/components/hmac_sha256/__init__.py @@ -0,0 +1,6 @@ +import esphome.config_validation as cv + +AUTO_LOAD = ["sha256"] +CODEOWNERS = ["@dwmw2"] + +CONFIG_SCHEMA = cv.Schema({}) diff --git a/esphome/components/hmac_sha256/hmac_sha256.cpp b/esphome/components/hmac_sha256/hmac_sha256.cpp new file mode 100644 index 0000000000..cf5daf63af --- /dev/null +++ b/esphome/components/hmac_sha256/hmac_sha256.cpp @@ -0,0 +1,102 @@ +#include +#include +#include "hmac_sha256.h" +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST) +#include "esphome/core/helpers.h" + +namespace esphome::hmac_sha256 { + +constexpr size_t SHA256_DIGEST_SIZE = 32; + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + +HmacSHA256::~HmacSHA256() { mbedtls_md_free(&this->ctx_); } + +void HmacSHA256::init(const uint8_t *key, size_t len) { + mbedtls_md_init(&this->ctx_); + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + mbedtls_md_setup(&this->ctx_, md_info, 1); // 1 = HMAC mode + mbedtls_md_hmac_starts(&this->ctx_, key, len); +} + +void HmacSHA256::add(const uint8_t *data, size_t len) { mbedtls_md_hmac_update(&this->ctx_, data, len); } + +void HmacSHA256::calculate() { mbedtls_md_hmac_finish(&this->ctx_, this->digest_); } + +void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); } + +void HmacSHA256::get_hex(char *output) { + for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) { + sprintf(output + (i * 2), "%02x", this->digest_[i]); + } +} + +bool HmacSHA256::equals_bytes(const uint8_t *expected) { + return memcmp(this->digest_, expected, SHA256_DIGEST_SIZE) == 0; +} + +bool HmacSHA256::equals_hex(const char *expected) { + char hex_output[SHA256_DIGEST_SIZE * 2 + 1]; + this->get_hex(hex_output); + hex_output[SHA256_DIGEST_SIZE * 2] = '\0'; + return strncmp(hex_output, expected, SHA256_DIGEST_SIZE * 2) == 0; +} + +#else + +HmacSHA256::~HmacSHA256() = default; + +// HMAC block size for SHA256 (RFC 2104) +constexpr size_t HMAC_BLOCK_SIZE = 64; + +void HmacSHA256::init(const uint8_t *key, size_t len) { + uint8_t ipad[HMAC_BLOCK_SIZE], opad[HMAC_BLOCK_SIZE]; + + memset(ipad, 0, sizeof(ipad)); + if (len > HMAC_BLOCK_SIZE) { + sha256::SHA256 keysha256; + keysha256.init(); + keysha256.add(key, len); + keysha256.calculate(); + keysha256.get_bytes(ipad); + } else { + memcpy(ipad, key, len); + } + memcpy(opad, ipad, sizeof(opad)); + + for (size_t i = 0; i < HMAC_BLOCK_SIZE; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + this->ihash_.init(); + this->ihash_.add(ipad, sizeof(ipad)); + + this->ohash_.init(); + this->ohash_.add(opad, sizeof(opad)); +} + +void HmacSHA256::add(const uint8_t *data, size_t len) { this->ihash_.add(data, len); } + +void HmacSHA256::calculate() { + uint8_t ibytes[32]; + + this->ihash_.calculate(); + this->ihash_.get_bytes(ibytes); + + this->ohash_.add(ibytes, sizeof(ibytes)); + this->ohash_.calculate(); +} + +void HmacSHA256::get_bytes(uint8_t *output) { this->ohash_.get_bytes(output); } + +void HmacSHA256::get_hex(char *output) { this->ohash_.get_hex(output); } + +bool HmacSHA256::equals_bytes(const uint8_t *expected) { return this->ohash_.equals_bytes(expected); } + +bool HmacSHA256::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); } + +#endif // USE_ESP32 || USE_LIBRETINY + +} // namespace esphome::hmac_sha256 +#endif diff --git a/esphome/components/hmac_sha256/hmac_sha256.h b/esphome/components/hmac_sha256/hmac_sha256.h new file mode 100644 index 0000000000..85622cac46 --- /dev/null +++ b/esphome/components/hmac_sha256/hmac_sha256.h @@ -0,0 +1,59 @@ +#pragma once + +#include "esphome/core/defines.h" +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST) + +#include + +#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#include "mbedtls/md.h" +#else +#include "esphome/components/sha256/sha256.h" +#endif + +namespace esphome::hmac_sha256 { + +class HmacSHA256 { + public: + HmacSHA256() = default; + ~HmacSHA256(); + + /// Initialize a new HMAC-SHA256 digest computation. + void init(const uint8_t *key, size_t len); + void init(const char *key, size_t len) { this->init((const uint8_t *) key, len); } + void init(const std::string &key) { this->init(key.c_str(), key.length()); } + + /// Add bytes of data for the digest. + void add(const uint8_t *data, size_t len); + void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); } + + /// Compute the digest, based on the provided data. + void calculate(); + + /// Retrieve the HMAC-SHA256 digest as bytes. + /// The output must be able to hold 32 bytes or more. + void get_bytes(uint8_t *output); + + /// Retrieve the HMAC-SHA256 digest as hex characters. + /// The output must be able to hold 65 bytes or more (64 hex chars + null terminator). + void get_hex(char *output); + + /// Compare the digest against a provided byte-encoded digest (32 bytes). + bool equals_bytes(const uint8_t *expected); + + /// Compare the digest against a provided hex-encoded digest (64 bytes). + bool equals_hex(const char *expected); + + protected: +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + static constexpr size_t SHA256_DIGEST_SIZE = 32; + mbedtls_md_context_t ctx_{}; + uint8_t digest_[SHA256_DIGEST_SIZE]{}; +#else + sha256::SHA256 ihash_; + sha256::SHA256 ohash_; +#endif +}; + +} // namespace esphome::hmac_sha256 +#endif diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp index 5652e7d603..b0d9135822 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp @@ -1,6 +1,7 @@ #include "homeassistant_binary_sensor.h" -#include "esphome/core/log.h" #include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -8,31 +9,30 @@ namespace homeassistant { static const char *const TAG = "homeassistant.binary_sensor"; void HomeassistantBinarySensor::setup() { - api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, this->attribute_, [this](const std::string &state) { - auto val = parse_on_off(state.c_str()); - switch (val) { - case PARSE_NONE: - case PARSE_TOGGLE: - ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str()); - break; - case PARSE_ON: - case PARSE_OFF: - bool new_state = val == PARSE_ON; - if (this->attribute_ != nullptr) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state)); - } else { - ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state)); - } - if (this->initial_) { - this->publish_initial_state(new_state); - } else { - this->publish_state(new_state); - } - break; + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { + auto val = parse_on_off(state.c_str()); + switch (val) { + case PARSE_NONE: + case PARSE_TOGGLE: + ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str()); + break; + case PARSE_ON: + case PARSE_OFF: + bool new_state = val == PARSE_ON; + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state %s", this->entity_id_, this->attribute_, ONOFF(new_state)); + } else { + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, ONOFF(new_state)); } - this->initial_ = false; - }); + if (this->initial_) { + this->publish_initial_state(new_state); + } else { + this->publish_state(new_state); + } + break; + } + this->initial_ = false; + }); } void HomeassistantBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Homeassistant Binary Sensor", this); diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index 1ca90180eb..92ecd5ea39 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -3,14 +3,15 @@ #include "esphome/components/api/api_pb2.h" #include "esphome/components/api/api_server.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { static const char *const TAG = "homeassistant.number"; -void HomeassistantNumber::state_changed_(const std::string &state) { - auto number_value = parse_number(state); +void HomeassistantNumber::state_changed_(StringRef state) { + auto number_value = parse_number(state.c_str()); if (!number_value.has_value()) { ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); this->publish_state(NAN); @@ -23,8 +24,8 @@ void HomeassistantNumber::state_changed_(const std::string &state) { this->publish_state(number_value.value()); } -void HomeassistantNumber::min_retrieved_(const std::string &min) { - auto min_value = parse_number(min); +void HomeassistantNumber::min_retrieved_(StringRef min) { + auto min_value = parse_number(min.c_str()); if (!min_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_, min.c_str()); return; @@ -33,8 +34,8 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) { this->traits.set_min_value(min_value.value()); } -void HomeassistantNumber::max_retrieved_(const std::string &max) { - auto max_value = parse_number(max); +void HomeassistantNumber::max_retrieved_(StringRef max) { + auto max_value = parse_number(max.c_str()); if (!max_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_, max.c_str()); return; @@ -43,8 +44,8 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) { this->traits.set_max_value(max_value.value()); } -void HomeassistantNumber::step_retrieved_(const std::string &step) { - auto step_value = parse_number(step); +void HomeassistantNumber::step_retrieved_(StringRef step) { + auto step_value = parse_number(step.c_str()); if (!step_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_, step.c_str()); return; @@ -85,16 +86,19 @@ void HomeassistantNumber::control(float value) { static constexpr auto VALUE_KEY = StringRef::from_lit("value"); api::HomeassistantActionRequest resp; - resp.set_service(SERVICE_NAME); + resp.service = SERVICE_NAME; resp.data.init(2); auto &entity_id = resp.data.emplace_back(); - entity_id.set_key(ENTITY_ID_KEY); - entity_id.value = this->entity_id_; + entity_id.key = ENTITY_ID_KEY; + entity_id.value = StringRef(this->entity_id_); auto &entity_value = resp.data.emplace_back(); - entity_value.set_key(VALUE_KEY); - entity_value.value = to_string(value); + entity_value.key = VALUE_KEY; + // Stack buffer - no heap allocation; %g produces shortest representation + char value_buf[16]; + snprintf(value_buf, sizeof(value_buf), "%g", value); + entity_value.value = StringRef(value_buf); api::global_api_server->send_homeassistant_action(resp); } diff --git a/esphome/components/homeassistant/number/homeassistant_number.h b/esphome/components/homeassistant/number/homeassistant_number.h index 0dffc108cb..275d2d5f03 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.h +++ b/esphome/components/homeassistant/number/homeassistant_number.h @@ -1,10 +1,8 @@ #pragma once -#include -#include - #include "esphome/components/number/number.h" #include "esphome/core/component.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -18,10 +16,10 @@ class HomeassistantNumber : public number::Number, public Component { float get_setup_priority() const override; protected: - void state_changed_(const std::string &state); - void min_retrieved_(const std::string &min); - void max_retrieved_(const std::string &max); - void step_retrieved_(const std::string &step); + void state_changed_(StringRef state); + void min_retrieved_(StringRef min); + void max_retrieved_(StringRef max); + void step_retrieved_(StringRef step); void control(float value) override; diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp index 78da47f9a1..66300ebba5 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp @@ -1,6 +1,7 @@ #include "homeassistant_sensor.h" -#include "esphome/core/log.h" #include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -8,22 +9,21 @@ namespace homeassistant { static const char *const TAG = "homeassistant.sensor"; void HomeassistantSensor::setup() { - api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, this->attribute_, [this](const std::string &state) { - auto val = parse_number(state); - if (!val.has_value()) { - ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); - this->publish_state(NAN); - return; - } + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { + auto val = parse_number(state.c_str()); + if (!val.has_value()) { + ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_, state.c_str()); + this->publish_state(NAN); + return; + } - if (this->attribute_ != nullptr) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val); - } else { - ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val); - } - this->publish_state(*val); - }); + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state %.2f", this->entity_id_, this->attribute_, *val); + } else { + ESP_LOGD(TAG, "'%s': Got state %.2f", this->entity_id_, *val); + } + this->publish_state(*val); + }); } void HomeassistantSensor::dump_config() { LOG_SENSOR("", "Homeassistant Sensor", this); diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index c4abf2295d..cc3d582bf3 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -1,6 +1,7 @@ #include "homeassistant_switch.h" #include "esphome/components/api/api_server.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -10,7 +11,7 @@ static const char *const TAG = "homeassistant.switch"; using namespace esphome::switch_; void HomeassistantSwitch::setup() { - api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](const std::string &state) { + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](StringRef state) { auto val = parse_on_off(state.c_str()); switch (val) { case PARSE_NONE: @@ -46,15 +47,15 @@ void HomeassistantSwitch::write_state(bool state) { api::HomeassistantActionRequest resp; if (state) { - resp.set_service(SERVICE_ON); + resp.service = SERVICE_ON; } else { - resp.set_service(SERVICE_OFF); + resp.service = SERVICE_OFF; } resp.data.init(1); auto &entity_id_kv = resp.data.emplace_back(); - entity_id_kv.set_key(ENTITY_ID_KEY); - entity_id_kv.value = this->entity_id_; + entity_id_kv.key = ENTITY_ID_KEY; + entity_id_kv.value = StringRef(this->entity_id_); api::global_api_server->send_homeassistant_action(resp); } diff --git a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp index 6154330a4e..109574e0c8 100644 --- a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp +++ b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp @@ -1,6 +1,7 @@ #include "homeassistant_text_sensor.h" -#include "esphome/core/log.h" #include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" namespace esphome { namespace homeassistant { @@ -8,15 +9,14 @@ namespace homeassistant { static const char *const TAG = "homeassistant.text_sensor"; void HomeassistantTextSensor::setup() { - api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, this->attribute_, [this](const std::string &state) { - if (this->attribute_ != nullptr) { - ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str()); - } else { - ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); - } - this->publish_state(state); - }); + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { + if (this->attribute_ != nullptr) { + ESP_LOGD(TAG, "'%s::%s': Got attribute state '%s'", this->entity_id_, this->attribute_, state.c_str()); + } else { + ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); + } + this->publish_state(state.c_str(), state.size()); + }); } void HomeassistantTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Homeassistant Text Sensor", this); diff --git a/esphome/components/honeywellabp/honeywellabp.cpp b/esphome/components/honeywellabp/honeywellabp.cpp index 4c00f034aa..c204325dfc 100644 --- a/esphome/components/honeywellabp/honeywellabp.cpp +++ b/esphome/components/honeywellabp/honeywellabp.cpp @@ -35,8 +35,10 @@ uint8_t HONEYWELLABPSensor::readsensor_() { pressure_count_ = ((uint16_t) (buf_[0]) << 8 & 0x3F00) | ((uint16_t) (buf_[1]) & 0xFF); // 11 - bit temperature is all of byte 2 (lowest 8 bits) and the first three bits of byte 3 temperature_count_ = (((uint16_t) (buf_[2]) << 3) & 0x7F8) | (((uint16_t) (buf_[3]) >> 5) & 0x7); - ESP_LOGV(TAG, "Sensor pressure_count_ %d", pressure_count_); - ESP_LOGV(TAG, "Sensor temperature_count_ %d", temperature_count_); + ESP_LOGV(TAG, + "Sensor pressure_count_ %d\n" + "Sensor temperature_count_ %d", + pressure_count_, temperature_count_); } return status_; } diff --git a/esphome/components/host/gpio.cpp b/esphome/components/host/gpio.cpp index e46f158513..f99b82bcc2 100644 --- a/esphome/components/host/gpio.cpp +++ b/esphome/components/host/gpio.cpp @@ -25,11 +25,7 @@ void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Interr } void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); } -std::string HostGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); - return buffer; -} +size_t HostGPIOPin::dump_summary(char *buffer, size_t len) const { return snprintf(buffer, len, "GPIO%u", this->pin_); } bool HostGPIOPin::digital_read() { return inverted_; } void HostGPIOPin::digital_write(bool value) { diff --git a/esphome/components/host/gpio.h b/esphome/components/host/gpio.h index ae677291b9..ea6b13f436 100644 --- a/esphome/components/host/gpio.h +++ b/esphome/components/host/gpio.h @@ -17,7 +17,7 @@ class HostGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/hte501/hte501.cpp b/esphome/components/hte501/hte501.cpp index b7d3be63fe..cde6886109 100644 --- a/esphome/components/hte501/hte501.cpp +++ b/esphome/components/hte501/hte501.cpp @@ -7,6 +7,8 @@ namespace hte501 { static const char *const TAG = "hte501"; +static constexpr size_t HTE501_SERIAL_NUMBER_SIZE = 7; + void HTE501Component::setup() { uint8_t address[] = {0x70, 0x29}; uint8_t identification[9]; @@ -16,7 +18,10 @@ void HTE501Component::setup() { this->mark_failed(); return; } - ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(identification + 0, 7).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char serial_hex[format_hex_size(HTE501_SERIAL_NUMBER_SIZE)]; +#endif + ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, identification, HTE501_SERIAL_NUMBER_SIZE)); } void HTE501Component::dump_config() { diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index f4fa448c5b..b133aa69b2 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -69,9 +69,6 @@ def validate_url(value): def validate_ssl_verification(config): error_message = "" - if CORE.is_esp32 and not CORE.using_esp_idf and config[CONF_VERIFY_SSL]: - error_message = "ESPHome supports certificate verification only via ESP-IDF" - if CORE.is_rp2040 and config[CONF_VERIFY_SSL]: error_message = "ESPHome does not support certificate verification on RP2040" @@ -93,9 +90,9 @@ def validate_ssl_verification(config): def _declare_request_class(value): if CORE.is_host: return cv.declare_id(HttpRequestHost)(value) - if CORE.using_esp_idf: + if CORE.is_esp32: return cv.declare_id(HttpRequestIDF)(value) - if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040: + if CORE.is_esp8266 or CORE.is_rp2040: return cv.declare_id(HttpRequestArduino)(value) return NotImplementedError @@ -121,11 +118,11 @@ CONFIG_SCHEMA = cv.All( cv.positive_not_null_time_period, cv.positive_time_period_milliseconds, ), - cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32_idf=512): cv.All( - cv.uint16_t, cv.only_with_esp_idf + cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32=512): cv.All( + cv.uint16_t, cv.only_on_esp32 ), - cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All( - cv.uint16_t, cv.only_with_esp_idf + cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32=512): cv.All( + cv.uint16_t, cv.only_on_esp32 ), cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All( cv.file_, @@ -158,25 +155,20 @@ async def to_code(config): cg.add(var.set_watchdog_timeout(timeout_ms)) if CORE.is_esp32: - if CORE.using_esp_idf: - cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX])) - cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX])) + cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX])) + cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX])) - esp32.add_idf_sdkconfig_option( - "CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", - config.get(CONF_VERIFY_SSL), - ) - esp32.add_idf_sdkconfig_option( - "CONFIG_ESP_TLS_INSECURE", - not config.get(CONF_VERIFY_SSL), - ) - esp32.add_idf_sdkconfig_option( - "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", - not config.get(CONF_VERIFY_SSL), - ) - else: - cg.add_library("NetworkClientSecure", None) - cg.add_library("HTTPClient", None) + if config.get(CONF_VERIFY_SSL): + esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True) + + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_TLS_INSECURE", + not config.get(CONF_VERIFY_SSL), + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", + not config.get(CONF_VERIFY_SSL), + ) if CORE.is_esp8266: cg.add_library("ESP8266HTTPClient", None) if CORE.is_rp2040 and CORE.using_arduino: @@ -327,13 +319,15 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( { "http_request_host.cpp": {PlatformFramework.HOST_NATIVE}, "http_request_arduino.cpp": { - PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP8266_ARDUINO, PlatformFramework.RP2040_ARDUINO, PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, }, - "http_request_idf.cpp": {PlatformFramework.ESP32_IDF}, + "http_request_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, } ) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 806354baf1..11dde4715a 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -4,8 +4,7 @@ #include -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request"; @@ -42,5 +41,4 @@ std::string HttpContainer::get_response_header(const std::string &header_name) { } } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 8adf13b954..1b5fd9f00e 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -15,8 +15,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { struct Header { std::string name; @@ -305,5 +304,4 @@ template class HttpRequestSendAction : public Action { size_t max_response_buffer_size_{SIZE_MAX}; }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index c64a7be554..a653942b18 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -1,6 +1,6 @@ #include "http_request_arduino.h" -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) #include "esphome/components/network/util.h" #include "esphome/components/watchdog/watchdog.h" @@ -9,8 +9,7 @@ #include "esphome/core/defines.h" #include "esphome/core/log.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request.arduino"; @@ -75,8 +74,6 @@ std::shared_ptr HttpRequestArduino::perform(const std::string &ur container->client_.setInsecure(); } bool status = container->client_.begin(url.c_str()); -#elif defined(USE_ESP32) - bool status = container->client_.begin(url.c_str()); #endif App.feed_wdt(); @@ -90,9 +87,6 @@ std::shared_ptr HttpRequestArduino::perform(const std::string &ur container->client_.setReuse(true); container->client_.setTimeout(this->timeout_); -#if defined(USE_ESP32) - container->client_.setConnectTimeout(this->timeout_); -#endif if (this->useragent_ != nullptr) { container->client_.setUserAgent(this->useragent_); @@ -177,7 +171,6 @@ void HttpContainerArduino::end() { this->client_.end(); } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ARDUINO +#endif // USE_ARDUINO && !USE_ESP32 diff --git a/esphome/components/http_request/http_request_arduino.h b/esphome/components/http_request/http_request_arduino.h index b736bb56d1..d9b5af9d81 100644 --- a/esphome/components/http_request/http_request_arduino.h +++ b/esphome/components/http_request/http_request_arduino.h @@ -2,9 +2,9 @@ #include "http_request.h" -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_ESP32) -#if defined(USE_ESP32) || defined(USE_RP2040) +#if defined(USE_RP2040) #include #include #endif @@ -15,8 +15,7 @@ #endif #endif -namespace esphome { -namespace http_request { +namespace esphome::http_request { class HttpRequestArduino; class HttpContainerArduino : public HttpContainer { @@ -36,7 +35,6 @@ class HttpRequestArduino : public HttpRequestComponent { const std::set &collect_headers) override; }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ARDUINO +#endif // USE_ARDUINO && !USE_ESP32 diff --git a/esphome/components/http_request/http_request_host.cpp b/esphome/components/http_request/http_request_host.cpp index 402affc1d1..b94570be12 100644 --- a/esphome/components/http_request/http_request_host.cpp +++ b/esphome/components/http_request/http_request_host.cpp @@ -12,8 +12,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request.host"; @@ -139,7 +138,6 @@ void HttpContainerHost::end() { this->bytes_read_ = 0; } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request #endif // USE_HOST diff --git a/esphome/components/http_request/http_request_host.h b/esphome/components/http_request/http_request_host.h index 886ba94938..32e149e6a3 100644 --- a/esphome/components/http_request/http_request_host.h +++ b/esphome/components/http_request/http_request_host.h @@ -2,8 +2,8 @@ #ifdef USE_HOST #include "http_request.h" -namespace esphome { -namespace http_request { + +namespace esphome::http_request { class HttpRequestHost; class HttpContainerHost : public HttpContainer { @@ -27,7 +27,6 @@ class HttpRequestHost : public HttpRequestComponent { const char *ca_path_{}; }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request #endif // USE_HOST diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index 34a3fb87eb..725a9c1c1e 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -1,6 +1,6 @@ #include "http_request_idf.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/network/util.h" #include "esphome/components/watchdog/watchdog.h" @@ -14,8 +14,7 @@ #include "esp_task_wdt.h" -namespace esphome { -namespace http_request { +namespace esphome::http_request { static const char *const TAG = "http_request.idf"; @@ -245,7 +244,6 @@ void HttpContainerIDF::feed_wdt() { } } -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/http_request/http_request_idf.h b/esphome/components/http_request/http_request_idf.h index e51b3aaebc..4dc4736423 100644 --- a/esphome/components/http_request/http_request_idf.h +++ b/esphome/components/http_request/http_request_idf.h @@ -2,15 +2,14 @@ #include "http_request.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include #include #include -namespace esphome { -namespace http_request { +namespace esphome::http_request { class HttpContainerIDF : public HttpContainer { public: @@ -48,7 +47,6 @@ class HttpRequestIDF : public HttpRequestComponent { static esp_err_t http_event_handler(esp_http_client_event_t *evt); }; -} // namespace http_request -} // namespace esphome +} // namespace esphome::http_request -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index b257518e06..2a7db9137f 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -7,7 +7,7 @@ #include "esphome/components/md5/md5.h" #include "esphome/components/watchdog/watchdog.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" #include "esphome/components/ota/ota_backend_esp_idf.h" @@ -16,12 +16,6 @@ namespace http_request { static const char *const TAG = "http_request.ota"; -void OtaHttpRequestComponent::setup() { -#ifdef USE_OTA_STATE_CALLBACK - ota::register_ota_platform(this); -#endif -} - void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); }; void OtaHttpRequestComponent::set_md5_url(const std::string &url) { @@ -48,24 +42,24 @@ void OtaHttpRequestComponent::flash() { } ESP_LOGI(TAG, "Starting update"); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_STARTED, 0.0f, 0); #endif auto ota_status = this->do_ota_(); switch (ota_status) { case ota::OTA_RESPONSE_OK: -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_COMPLETED, 100.0f, ota_status); #endif delay(10); App.safe_reboot(); break; default: -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_ERROR, 0.0f, ota_status); #endif this->md5_computed_.clear(); // will be reset at next attempt this->md5_expected_.clear(); // will be reset at next attempt @@ -111,9 +105,8 @@ uint8_t OtaHttpRequestComponent::do_ota_() { // we will compute MD5 on the fly for verification -- Arduino OTA seems to ignore it md5_receive.init(); - ESP_LOGV(TAG, "MD5Digest initialized"); - - ESP_LOGV(TAG, "OTA backend begin"); + ESP_LOGV(TAG, "MD5Digest initialized\n" + "OTA backend begin"); auto backend = ota::make_ota_backend(); auto error_code = backend->begin(container->content_length); if (error_code != ota::OTA_RESPONSE_OK) { @@ -165,8 +158,8 @@ uint8_t OtaHttpRequestComponent::do_ota_() { last_progress = now; float percentage = container->get_bytes_read() * 100.0f / container->content_length; ESP_LOGD(TAG, "Progress: %0.1f%%", percentage); -#ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0); +#ifdef USE_OTA_STATE_LISTENER + this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0); #endif } } // while diff --git a/esphome/components/http_request/ota/ota_http_request.h b/esphome/components/http_request/ota/ota_http_request.h index 6a86b4ab43..8735189e99 100644 --- a/esphome/components/http_request/ota/ota_http_request.h +++ b/esphome/components/http_request/ota/ota_http_request.h @@ -24,7 +24,6 @@ enum OtaHttpRequestError : uint8_t { class OtaHttpRequestComponent : public ota::OTAComponent, public Parented { public: - void setup() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } diff --git a/esphome/components/http_request/update/__init__.py b/esphome/components/http_request/update/__init__.py index abb4b2a430..d84d80109a 100644 --- a/esphome/components/http_request/update/__init__.py +++ b/esphome/components/http_request/update/__init__.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import update +from esphome.components import ota, update import esphome.config_validation as cv from esphome.const import CONF_SOURCE @@ -38,6 +38,6 @@ async def to_code(config): cg.add(var.set_source_url(config[CONF_SOURCE])) - cg.add_define("USE_OTA_STATE_CALLBACK") + ota.request_ota_state_listeners() await cg.register_component(var, config) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 22cad625d1..82b391e01f 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -20,19 +20,19 @@ static const char *const TAG = "http_request.update"; static const size_t MAX_READ_SIZE = 256; -void HttpRequestUpdate::setup() { - this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) { - if (state == ota::OTAState::OTA_IN_PROGRESS) { - this->state_ = update::UPDATE_STATE_INSTALLING; - this->update_info_.has_progress = true; - this->update_info_.progress = progress; - this->publish_state(); - } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) { - this->state_ = update::UPDATE_STATE_AVAILABLE; - this->status_set_error(LOG_STR("Failed to install firmware")); - this->publish_state(); - } - }); +void HttpRequestUpdate::setup() { this->ota_parent_->add_state_listener(this); } + +void HttpRequestUpdate::on_ota_state(ota::OTAState state, float progress, uint8_t error) { + if (state == ota::OTAState::OTA_IN_PROGRESS) { + this->state_ = update::UPDATE_STATE_INSTALLING; + this->update_info_.has_progress = true; + this->update_info_.progress = progress; + this->publish_state(); + } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) { + this->state_ = update::UPDATE_STATE_AVAILABLE; + this->status_set_error(LOG_STR("Failed to install firmware")); + this->publish_state(); + } } void HttpRequestUpdate::update() { @@ -93,35 +93,36 @@ void HttpRequestUpdate::update_task(void *params) { container.reset(); // Release ownership of the container's shared_ptr valid = json::parse_json(response, [this_update](JsonObject root) -> bool { - if (!root["name"].is() || !root["version"].is() || !root["builds"].is()) { + if (!root[ESPHOME_F("name")].is() || !root[ESPHOME_F("version")].is() || + !root[ESPHOME_F("builds")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - this_update->update_info_.title = root["name"].as(); - this_update->update_info_.latest_version = root["version"].as(); + this_update->update_info_.title = root[ESPHOME_F("name")].as(); + this_update->update_info_.latest_version = root[ESPHOME_F("version")].as(); - for (auto build : root["builds"].as()) { - if (!build["chipFamily"].is()) { + for (auto build : root[ESPHOME_F("builds")].as()) { + if (!build[ESPHOME_F("chipFamily")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build["ota"].is()) { + if (build[ESPHOME_F("chipFamily")] == ESPHOME_VARIANT) { + if (!build[ESPHOME_F("ota")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - JsonObject ota = build["ota"].as(); - if (!ota["path"].is() || !ota["md5"].is()) { + JsonObject ota = build[ESPHOME_F("ota")].as(); + if (!ota[ESPHOME_F("path")].is() || !ota[ESPHOME_F("md5")].is()) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - this_update->update_info_.firmware_url = ota["path"].as(); - this_update->update_info_.md5 = ota["md5"].as(); + this_update->update_info_.firmware_url = ota[ESPHOME_F("path")].as(); + this_update->update_info_.md5 = ota[ESPHOME_F("md5")].as(); - if (ota["summary"].is()) - this_update->update_info_.summary = ota["summary"].as(); - if (ota["release_url"].is()) - this_update->update_info_.release_url = ota["release_url"].as(); + if (ota[ESPHOME_F("summary")].is()) + this_update->update_info_.summary = ota[ESPHOME_F("summary")].as(); + if (ota[ESPHOME_F("release_url")].is()) + this_update->update_info_.release_url = ota[ESPHOME_F("release_url")].as(); return true; } diff --git a/esphome/components/http_request/update/http_request_update.h b/esphome/components/http_request/update/http_request_update.h index e05fdb0cc2..cf34ace18e 100644 --- a/esphome/components/http_request/update/http_request_update.h +++ b/esphome/components/http_request/update/http_request_update.h @@ -14,7 +14,7 @@ namespace esphome { namespace http_request { -class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { +class HttpRequestUpdate final : public update::UpdateEntity, public PollingComponent, public ota::OTAStateListener { public: void setup() override; void update() override; @@ -29,6 +29,8 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void on_ota_state(ota::OTAState state, float progress, uint8_t error) override; + protected: HttpRequestComponent *request_parent_; OtaHttpRequestComponent *ota_parent_; diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 0518731a6a..20c731e730 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -1,6 +1,6 @@ from typing import Any -from esphome import pins +from esphome import automation, pins import esphome.codegen as cg from esphome.components import display from esphome.components.esp32 import add_idf_component @@ -15,8 +15,11 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_OE_PIN, + CONF_ROTATION, CONF_UPDATE_INTERVAL, ) +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType import esphome.final_validate as fv from esphome.types import ConfigType @@ -132,9 +135,18 @@ CLOCK_SPEEDS = { "20MHZ": Hub75ClockSpeed.HZ_20M, } +Hub75Rotation = cg.global_ns.enum("Hub75Rotation", is_class=True) +ROTATIONS = { + 0: Hub75Rotation.ROTATE_0, + 90: Hub75Rotation.ROTATE_90, + 180: Hub75Rotation.ROTATE_180, + 270: Hub75Rotation.ROTATE_270, +} + HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display) Hub75Config = cg.global_ns.struct("Hub75Config") Hub75Pins = cg.global_ns.struct("Hub75Pins") +SetBrightnessAction = hub75_ns.class_("SetBrightnessAction", automation.Action) def _merge_board_pins(config: ConfigType) -> ConfigType: @@ -358,6 +370,8 @@ CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(HUB75Display), + # Override rotation - store Hub75Rotation directly (driver handles rotation) + cv.Optional(CONF_ROTATION): cv.enum(ROTATIONS, int=True), # Board preset (optional - provides default pin mappings) cv.Optional(CONF_BOARD): cv.one_of(*BOARDS.keys(), lower=True), # Panel dimensions @@ -375,7 +389,7 @@ CONFIG_SCHEMA = cv.All( # Display configuration cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean, cv.Optional(CONF_BRIGHTNESS): cv.int_range(min=0, max=255), - cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=6, max=12), + cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=4, max=12), cv.Optional(CONF_GAMMA_CORRECT): cv.enum( {"LINEAR": 0, "CIE1931": 1, "GAMMA_2_2": 2}, upper=True ), @@ -487,10 +501,11 @@ def _build_config_struct( Fields must be added in declaration order (see hub75_types.h) to satisfy C++ designated initializer requirements. The order is: 1. fields_before_pins (panel_width through layout) - 2. pins - 3. output_clock_speed - 4. min_refresh_rate - 5. fields_after_min_refresh (latch_blanking through brightness) + 2. rotation + 3. pins + 4. output_clock_speed + 5. min_refresh_rate + 6. fields_after_min_refresh (latch_blanking through brightness) """ fields_before_pins = [ (CONF_PANEL_WIDTH, "panel_width"), @@ -513,6 +528,10 @@ def _build_config_struct( _append_config_fields(config, fields_before_pins, config_fields) + # Rotation - config already contains Hub75Rotation enum from cv.enum + if CONF_ROTATION in config: + config_fields.append(("rotation", config[CONF_ROTATION])) + config_fields.append(("pins", pins_struct)) if CONF_CLOCK_SPEED in config: @@ -528,15 +547,15 @@ def _build_config_struct( async def to_code(config: ConfigType) -> None: add_idf_component( name="esphome/esp-hub75", - ref="0.1.7", + ref="0.2.2", ) - # Set compile-time configuration via defines + # Set compile-time configuration via build flags (so external library sees them) if CONF_BIT_DEPTH in config: - cg.add_define("HUB75_BIT_DEPTH", config[CONF_BIT_DEPTH]) + cg.add_build_flag(f"-DHUB75_BIT_DEPTH={config[CONF_BIT_DEPTH]}") if CONF_GAMMA_CORRECT in config: - cg.add_define("HUB75_GAMMA_MODE", config[CONF_GAMMA_CORRECT]) + cg.add_build_flag(f"-DHUB75_GAMMA_MODE={config[CONF_GAMMA_CORRECT].enum_value}") # Await all pin expressions pin_expressions = { @@ -567,6 +586,11 @@ async def to_code(config: ConfigType) -> None: pins_struct = _build_pins_struct(pin_expressions, e_pin_num) hub75_config = _build_config_struct(config, pins_struct, min_refresh) + # Rotation is handled by the hub75 driver (config_.rotation already set above). + # Force rotation to 0 for ESPHome's Display base class to avoid double-rotation. + if CONF_ROTATION in config: + config[CONF_ROTATION] = 0 + # Create display and register var = cg.new_Pvariable(config[CONF_ID], hub75_config) await display.register_display(var, config) @@ -576,3 +600,27 @@ async def to_code(config: ConfigType) -> None: config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) + + +@automation.register_action( + "hub75.set_brightness", + SetBrightnessAction, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(HUB75Display), + cv.Required(CONF_BRIGHTNESS): cv.templatable(cv.int_range(min=0, max=255)), + }, + key=CONF_BRIGHTNESS, + ), +) +async def hub75_set_brightness_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, cg.uint8) + cg.add(var.set_brightness(template_)) + return var diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index a09094b87c..cf8661b2b3 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -92,14 +92,25 @@ void HUB75Display::fill(Color color) { if (!this->enabled_) [[unlikely]] return; - // Special case: black (off) - use fast hardware clear - if (!color.is_on()) { + // Start with full display rect + display::Rect fill_rect(0, 0, this->get_width_internal(), this->get_height_internal()); + + // Apply clipping using Rect::shrink() to intersect + display::Rect clip = this->get_clipping(); + if (clip.is_set()) { + fill_rect.shrink(clip); + if (!fill_rect.is_set()) + return; // Completely clipped + } + + // Fast path: black filling entire display + if (!color.is_on() && fill_rect.x == 0 && fill_rect.y == 0 && fill_rect.w == this->get_width_internal() && + fill_rect.h == this->get_height_internal()) { driver_->clear(); return; } - // For non-black colors, fall back to base class (pixel-by-pixel) - Display::fill(color); + driver_->fill(fill_rect.x, fill_rect.y, fill_rect.w, fill_rect.h, color.r, color.g, color.b); } void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) { @@ -182,7 +193,7 @@ void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, co } } -void HUB75Display::set_brightness(int brightness) { +void HUB75Display::set_brightness(uint8_t brightness) { this->brightness_ = brightness; this->enabled_ = (brightness > 0); if (this->driver_ != nullptr) { diff --git a/esphome/components/hub75/hub75_component.h b/esphome/components/hub75/hub75_component.h index 49d4274483..ab7e3fc5b1 100644 --- a/esphome/components/hub75/hub75_component.h +++ b/esphome/components/hub75/hub75_component.h @@ -5,6 +5,7 @@ #include #include "esphome/components/display/display_buffer.h" +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -34,22 +35,29 @@ class HUB75Display : public display::Display { display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; // Brightness control (runtime mutable) - void set_brightness(int brightness); + void set_brightness(uint8_t brightness); protected: // Display internal methods - int get_width_internal() override { return config_.panel_width * config_.layout_cols; } - int get_height_internal() override { return config_.panel_height * config_.layout_rows; } + int get_width_internal() override { return this->driver_ != nullptr ? this->driver_->get_width() : 0; } + int get_height_internal() override { return this->driver_ != nullptr ? this->driver_->get_height() : 0; } // Member variables Hub75Driver *driver_{nullptr}; Hub75Config config_; // Immutable configuration // Runtime state (mutable) - int brightness_{128}; + uint8_t brightness_{128}; bool enabled_{false}; }; +template class SetBrightnessAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, brightness) + + void play(const Ts &...x) override { this->parent_->set_brightness(this->brightness_.value(x...)); } +}; + } // namespace esphome::hub75 #endif diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 7706484e97..19efda0b49 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -146,7 +146,7 @@ def _final_validate(config): full_config = fv.full_config.get()[CONF_I2C] if CORE.using_zephyr and len(full_config) > 1: raise cv.Invalid("Second i2c is not implemented on Zephyr yet") - if CORE.using_esp_idf and get_esp32_variant() in ESP32_I2C_CAPABILITIES: + if CORE.is_esp32 and get_esp32_variant() in ESP32_I2C_CAPABILITIES: variant = get_esp32_variant() max_num = ESP32_I2C_CAPABILITIES[variant]["NUM"] if len(full_config) > max_num: @@ -237,10 +237,6 @@ def i2c_device_schema(default_address): """ schema = { cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus), - cv.Optional("multiplexer"): cv.invalid( - "This option has been removed, please see " - "the tca9584a docs for the updated way to use multiplexers" - ), } if default_address is None: schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address @@ -254,7 +250,7 @@ async def register_i2c_device(var, config): Sets the i2c bus to use and the i2c address. - This is a coroutine, you need to await it with a 'yield' expression! + This is a coroutine, you need to await it with an 'await' expression! """ parent = await cg.get_variable(config[CONF_I2C_ID]) cg.add(var.set_i2c_bus(parent)) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 1579020c9b..e728830147 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -12,6 +12,9 @@ namespace i2c { static const char *const TAG = "i2c.arduino"; +// Maximum bytes to log in hex format (truncates larger transfers) +static constexpr size_t I2C_MAX_LOG_BYTES = 32; + void ArduinoI2CBus::setup() { recover_(); @@ -107,7 +110,10 @@ ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffe return ERROR_NOT_INITIALIZED; } - ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)]; + ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count)); +#endif uint8_t status = 0; if (write_count != 0 || read_count == 0) { diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 486dc0b7d8..191c849aa3 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -15,6 +15,9 @@ namespace i2c { static const char *const TAG = "i2c.idf"; +// Maximum bytes to log in hex format (truncates larger transfers) +static constexpr size_t I2C_MAX_LOG_BYTES = 32; + void IDFI2CBus::setup() { static i2c_port_t next_hp_port = I2C_NUM_0; #if SOC_LP_I2C_SUPPORTED @@ -147,7 +150,10 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s jobs[num_jobs++].write.total_bytes = 1; } else { if (write_count != 0) { - ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)]; + ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count)); +#endif jobs[num_jobs++].command = I2C_MASTER_CMD_START; jobs[num_jobs].command = I2C_MASTER_CMD_WRITE; jobs[num_jobs].write.ack_check = true; diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 61c5ca4ec1..d3128c5f4c 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -232,6 +232,8 @@ def validate_use_legacy(value): if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino): raise cv.Invalid("Arduino supports only the legacy i2s driver") _set_use_legacy_driver(value[CONF_USE_LEGACY]) + elif CORE.using_arduino: + _set_use_legacy_driver(True) return value @@ -261,8 +263,7 @@ def _final_validate(_): def use_legacy(): - legacy_driver = _get_use_legacy_driver() - return not (CORE.using_esp_idf and not legacy_driver) + return _get_use_legacy_driver() FINAL_VALIDATE_SCHEMA = _final_validate diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 53e378c41e..c934d12d65 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -340,8 +340,8 @@ void I2SAudioSpeaker::speaker_task(void *params) { const uint32_t read_delay = (this_speaker->current_stream_info_.frames_to_microseconds(frames_written) / 1000) / 2; - uint8_t *new_data = transfer_buffer->get_buffer_end(); // track start of any newly copied bytes size_t bytes_read = transfer_buffer->transfer_data_from_source(pdMS_TO_TICKS(read_delay)); + uint8_t *new_data = transfer_buffer->get_buffer_end() - bytes_read; if (bytes_read > 0) { if (this_speaker->q15_volume_factor_ < INT16_MAX) { diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 2a3d0edca7..a3eff901d3 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -131,6 +131,13 @@ float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWA void ILI9XXXDisplay::fill(Color color) { if (!this->check_buffer_()) return; + + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + uint16_t new_color = 0; this->x_low_ = 0; this->y_low_ = 0; diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index bf25a7cd92..3f8d909824 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -1,6 +1,7 @@ from __future__ import annotations import contextlib +from dataclasses import dataclass import hashlib import io import logging @@ -37,11 +38,21 @@ image_ns = cg.esphome_ns.namespace("image") ImageType = image_ns.enum("ImageType") + +@dataclass(frozen=True) +class ImageMetaData: + width: int + height: int + image_type: str + transparency: str + + CONF_OPAQUE = "opaque" CONF_CHROMA_KEY = "chroma_key" CONF_ALPHA_CHANNEL = "alpha_channel" CONF_INVERT_ALPHA = "invert_alpha" CONF_IMAGES = "images" +KEY_METADATA = "metadata" TRANSPARENCY_TYPES = ( CONF_OPAQUE, @@ -374,23 +385,6 @@ def is_svg_file(file): return " 500 or height > 500): _LOGGER.warning( @@ -751,10 +734,38 @@ async def write_image(config, all_frames=False): return prog_arr, width, height, image_type, trans_value, frame_count +async def _image_to_code(entry): + """ + Convert a single image entry to code and return its metadata. + :param entry: The config entry for the image. + :return: An ImageMetaData object + """ + prog_arr, width, height, image_type, trans_value, _ = await write_image(entry) + cg.new_Pvariable(entry[CONF_ID], prog_arr, width, height, image_type, trans_value) + return ImageMetaData( + width, + height, + entry[CONF_TYPE], + entry[CONF_TRANSPARENCY], + ) + + async def to_code(config): - # By now the config should be a simple list. - for entry in config: - prog_arr, width, height, image_type, trans_value, _ = await write_image(entry) - cg.new_Pvariable( - entry[CONF_ID], prog_arr, width, height, image_type, trans_value - ) + cg.add_define("USE_IMAGE") + # By now the config will be a simple list. + # Use a subkey to allow for other data in the future + CORE.data[DOMAIN] = { + KEY_METADATA: { + entry[CONF_ID].id: await _image_to_code(entry) for entry in config + } + } + + +def get_all_image_metadata() -> dict[str, ImageMetaData]: + """Get all image metadata.""" + return CORE.data.get(DOMAIN, {}).get(KEY_METADATA, {}) + + +def get_image_metadata(image_id: str) -> ImageMetaData | None: + """Get image metadata by ID for use by other components.""" + return get_all_image_metadata().get(image_id) diff --git a/esphome/components/improv_base/improv_base.cpp b/esphome/components/improv_base/improv_base.cpp index 2091390f95..d0340344a6 100644 --- a/esphome/components/improv_base/improv_base.cpp +++ b/esphome/components/improv_base/improv_base.cpp @@ -1,5 +1,6 @@ #include "improv_base.h" +#include #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" @@ -13,37 +14,54 @@ static constexpr size_t DEVICE_NAME_PLACEHOLDER_LEN = sizeof(DEVICE_NAME_PLACEHO static constexpr const char IP_ADDRESS_PLACEHOLDER[] = "{{ip_address}}"; static constexpr size_t IP_ADDRESS_PLACEHOLDER_LEN = sizeof(IP_ADDRESS_PLACEHOLDER) - 1; -static void replace_all_in_place(std::string &str, const char *placeholder, size_t placeholder_len, - const std::string &replacement) { - size_t pos = 0; - const size_t replacement_len = replacement.length(); - while ((pos = str.find(placeholder, pos)) != std::string::npos) { - str.replace(pos, placeholder_len, replacement); - pos += replacement_len; +/// Copy src to dest, returning pointer past last written char. Stops at end or if src is null. +static char *copy_to_buffer(char *dest, const char *end, const char *src) { + if (src == nullptr) { + return dest; } + while (*src != '\0' && dest < end) { + *dest++ = *src++; + } + return dest; } -std::string ImprovBase::get_formatted_next_url_() { - if (this->next_url_.empty()) { - return ""; +size_t ImprovBase::get_formatted_next_url_(char *buffer, size_t buffer_size) { + if (this->next_url_ == nullptr || buffer_size == 0) { + if (buffer_size > 0) { + buffer[0] = '\0'; + } + return 0; } - std::string formatted_url = this->next_url_; - - // Replace all occurrences of {{device_name}} - replace_all_in_place(formatted_url, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN, App.get_name()); - - // Replace all occurrences of {{ip_address}} + // Get IP address once for replacement + const char *ip_str = nullptr; + char ip_buffer[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : network::get_ip_addresses()) { if (ip.is_ip4()) { - replace_all_in_place(formatted_url, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN, ip.str()); + ip.str_to(ip_buffer); + ip_str = ip_buffer; break; } } - // Note: {{esphome_version}} is replaced at code generation time in Python + const char *device_name = App.get_name().c_str(); + char *out = buffer; + const char *end = buffer + buffer_size - 1; - return formatted_url; + // Note: {{esphome_version}} is replaced at code generation time in Python + for (const char *p = this->next_url_; *p != '\0' && out < end;) { + if (strncmp(p, DEVICE_NAME_PLACEHOLDER, DEVICE_NAME_PLACEHOLDER_LEN) == 0) { + out = copy_to_buffer(out, end, device_name); + p += DEVICE_NAME_PLACEHOLDER_LEN; + } else if (ip_str != nullptr && strncmp(p, IP_ADDRESS_PLACEHOLDER, IP_ADDRESS_PLACEHOLDER_LEN) == 0) { + out = copy_to_buffer(out, end, ip_str); + p += IP_ADDRESS_PLACEHOLDER_LEN; + } else { + *out++ = *p++; + } + } + *out = '\0'; + return out - buffer; } #endif diff --git a/esphome/components/improv_base/improv_base.h b/esphome/components/improv_base/improv_base.h index e4138479df..ebc8f38d60 100644 --- a/esphome/components/improv_base/improv_base.h +++ b/esphome/components/improv_base/improv_base.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "esphome/core/defines.h" namespace esphome { @@ -9,13 +9,14 @@ namespace improv_base { class ImprovBase { public: #if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL) - void set_next_url(const std::string &next_url) { this->next_url_ = next_url; } + void set_next_url(const char *next_url) { this->next_url_ = next_url; } #endif protected: #if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL) - std::string get_formatted_next_url_(); - std::string next_url_; + /// Format next_url_ into buffer, replacing placeholders. Returns length written. + size_t get_formatted_next_url_(char *buffer, size_t buffer_size); + const char *next_url_{nullptr}; #endif }; diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 7f88b17e11..9a2ac2f40f 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -26,7 +26,7 @@ def validate_logger(config): logger_conf = fv.full_config.get()[CONF_LOGGER] if logger_conf[CONF_BAUD_RATE] == 0: raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0") - if CORE.using_esp_idf and ( + if CORE.is_esp32 and ( logger_conf[CONF_HARDWARE_UART] == USB_CDC and get_esp32_variant() == VARIANT_ESP32S3 ): diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 281e95d12b..b4d9943955 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -182,15 +182,23 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { std::vector urls; #ifdef USE_IMPROV_SERIAL_NEXT_URL - if (!this->next_url_.empty()) { - urls.push_back(this->get_formatted_next_url_()); + { + char url_buffer[384]; + size_t len = this->get_formatted_next_url_(url_buffer, sizeof(url_buffer)); + if (len > 0) { + urls.emplace_back(url_buffer, len); + } } #endif #ifdef USE_WEBSERVER for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { if (ip.is_ip4()) { - std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); - urls.push_back(webserver_url); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ip.str_to(ip_buf); + // "http://" (7) + IP (40) + ":" (1) + port (5) + null (1) = 54 + char webserver_url[7 + network::IP_ADDRESS_BUFFER_SIZE + 1 + 5 + 1]; + snprintf(webserver_url, sizeof(webserver_url), "http://%s:%u", ip_buf, USE_WEBSERVER_PORT); + urls.emplace_back(webserver_url); break; } } @@ -263,8 +271,10 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command if (std::find(networks.begin(), networks.end(), ssid) != networks.end()) continue; // Send each ssid separately to avoid overflowing the buffer - std::vector data = improv::build_rpc_response( - improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false); + char rssi_buf[5]; // int8_t: -128 to 127, max 4 chars + null + *int8_to_str(rssi_buf, scan.get_rssi()) = '\0'; + std::vector data = + improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false); this->send_response_(data); networks.push_back(ssid); } diff --git a/esphome/components/ina260/ina260.cpp b/esphome/components/ina260/ina260.cpp index 9dd922cec2..4d6acf400c 100644 --- a/esphome/components/ina260/ina260.cpp +++ b/esphome/components/ina260/ina260.cpp @@ -61,13 +61,13 @@ void INA260Component::setup() { } void INA260Component::dump_config() { - ESP_LOGCONFIG(TAG, "INA260:"); + ESP_LOGCONFIG(TAG, + "INA260:\n" + " Manufacture ID: 0x%x\n" + " Device ID: 0x%x", + this->manufacture_id_, this->device_id_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->manufacture_id_); - ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); - LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); diff --git a/esphome/components/ina2xx_base/ina2xx_base.cpp b/esphome/components/ina2xx_base/ina2xx_base.cpp index 4ab02703e8..7185d21810 100644 --- a/esphome/components/ina2xx_base/ina2xx_base.cpp +++ b/esphome/components/ina2xx_base/ina2xx_base.cpp @@ -364,8 +364,10 @@ bool INA2XX::configure_shunt_() { ESP_LOGW(TAG, "Shunt value too high"); } this->shunt_cal_ &= 0x7FFF; - ESP_LOGV(TAG, "Given Rshunt=%f Ohm and Max_current=%.3f", this->shunt_resistance_ohm_, this->max_current_a_); - ESP_LOGV(TAG, "New CURRENT_LSB=%f, SHUNT_CAL=%u", this->current_lsb_, this->shunt_cal_); + ESP_LOGV(TAG, + "Given Rshunt=%f Ohm and Max_current=%.3f\n" + "New CURRENT_LSB=%f, SHUNT_CAL=%u", + this->shunt_resistance_ohm_, this->max_current_a_, this->current_lsb_, this->shunt_cal_); return this->write_unsigned_16_(RegisterMap::REG_SHUNT_CAL, this->shunt_cal_); } diff --git a/esphome/components/infrared/__init__.py b/esphome/components/infrared/__init__.py new file mode 100644 index 0000000000..5c759d6fd9 --- /dev/null +++ b/esphome/components/infrared/__init__.py @@ -0,0 +1,76 @@ +""" +Infrared component for ESPHome. + +WARNING: This component is EXPERIMENTAL. The API (both Python configuration +and C++ interfaces) may change at any time without following the normal +breaking changes policy. Use at your own risk. + +Once the API is considered stable, this warning will be removed. +""" + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.core import CORE, coroutine_with_priority +from esphome.core.entity_helpers import setup_entity +from esphome.coroutine import CoroPriority +from esphome.types import ConfigType + +CODEOWNERS = ["@kbx81"] +AUTO_LOAD = ["remote_base"] + +IS_PLATFORM_COMPONENT = True + +infrared_ns = cg.esphome_ns.namespace("infrared") +Infrared = infrared_ns.class_("Infrared", cg.EntityBase, cg.Component) +InfraredCall = infrared_ns.class_("InfraredCall") +InfraredTraits = infrared_ns.class_("InfraredTraits") + +CONF_INFRARED_ID = "infrared_id" +CONF_SUPPORTS_TRANSMITTER = "supports_transmitter" +CONF_SUPPORTS_RECEIVER = "supports_receiver" + + +def infrared_schema(class_: type[cg.MockObjClass]) -> cv.Schema: + """Create a schema for an infrared platform. + + :param class_: The infrared class to use for this schema. + :return: An extended schema for infrared configuration. + """ + entity_schema = cv.ENTITY_BASE_SCHEMA.extend(cv.COMPONENT_SCHEMA) + return entity_schema.extend( + { + cv.GenerateID(): cv.declare_id(class_), + } + ) + + +async def setup_infrared_core_(var: cg.Pvariable, config: ConfigType) -> None: + """Set up core infrared configuration.""" + await setup_entity(var, config, "infrared") + + +async def register_infrared(var: cg.Pvariable, config: ConfigType) -> None: + """Register an infrared device with the core.""" + cg.add_define("USE_IR_RF") + await cg.register_component(var, config) + await setup_infrared_core_(var, config) + cg.add(cg.App.register_infrared(var)) + CORE.register_platform_component("infrared", var) + + +async def new_infrared(config: ConfigType, *args) -> cg.Pvariable: + """Create a new Infrared instance. + + :param config: Configuration dictionary. + :param args: Additional arguments to pass to new_Pvariable. + :return: The created Infrared instance. + """ + var = cg.new_Pvariable(config[CONF_ID], *args) + await register_infrared(var, config) + return var + + +@coroutine_with_priority(CoroPriority.CORE) +async def to_code(config: ConfigType) -> None: + cg.add_global(infrared_ns.using) diff --git a/esphome/components/infrared/infrared.cpp b/esphome/components/infrared/infrared.cpp new file mode 100644 index 0000000000..5f8d63926a --- /dev/null +++ b/esphome/components/infrared/infrared.cpp @@ -0,0 +1,138 @@ +#include "infrared.h" +#include "esphome/core/log.h" + +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#endif + +namespace esphome::infrared { + +static const char *const TAG = "infrared"; + +// ========== InfraredCall ========== + +InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) { + this->carrier_frequency_ = frequency; + return *this; +} + +InfraredCall &InfraredCall::set_raw_timings(const std::vector &timings) { + this->raw_timings_ = &timings; + this->packed_data_ = nullptr; // Clear packed if vector is set + return *this; +} + +InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count) { + this->packed_data_ = data; + this->packed_length_ = length; + this->packed_count_ = count; + this->raw_timings_ = nullptr; // Clear vector if packed is set + return *this; +} + +InfraredCall &InfraredCall::set_repeat_count(uint32_t count) { + this->repeat_count_ = count; + return *this; +} + +void InfraredCall::perform() { + if (this->parent_ != nullptr) { + this->parent_->control(*this); + } +} + +// ========== Infrared ========== + +void Infrared::setup() { + // Set up traits based on configuration + this->traits_.set_supports_transmitter(this->has_transmitter()); + this->traits_.set_supports_receiver(this->has_receiver()); + + // Register as listener for received IR data + if (this->receiver_ != nullptr) { + this->receiver_->register_listener(this); + } +} + +void Infrared::dump_config() { + ESP_LOGCONFIG(TAG, + "Infrared '%s'\n" + " Supports Transmitter: %s\n" + " Supports Receiver: %s", + this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()), + YESNO(this->traits_.get_supports_receiver())); +} + +InfraredCall Infrared::make_call() { return InfraredCall(this); } + +void Infrared::control(const InfraredCall &call) { + if (this->transmitter_ == nullptr) { + ESP_LOGW(TAG, "No transmitter configured"); + return; + } + + if (!call.has_raw_timings()) { + ESP_LOGE(TAG, "No raw timings provided"); + return; + } + + // Create transmit data object + auto transmit_call = this->transmitter_->transmit(); + auto *transmit_data = transmit_call.get_data(); + + // Set carrier frequency + if (call.get_carrier_frequency().has_value()) { + transmit_data->set_carrier_frequency(call.get_carrier_frequency().value()); + } + + // Set timings based on format + if (call.is_packed()) { + // Zero-copy from packed protobuf data + transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(), + call.get_packed_count()); + ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(), + call.get_repeat_count()); + } else { + // From vector (lambdas/automations) + transmit_data->set_data(call.get_raw_timings()); + ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%u", call.get_raw_timings().size(), + call.get_repeat_count()); + } + + // Set repeat count + if (call.get_repeat_count() > 0) { + transmit_call.set_send_times(call.get_repeat_count()); + } + + // Perform transmission + transmit_call.perform(); +} + +uint32_t Infrared::get_capability_flags() const { + uint32_t flags = 0; + + // Add transmit/receive capability based on traits + if (this->traits_.get_supports_transmitter()) + flags |= InfraredCapability::CAPABILITY_TRANSMITTER; + if (this->traits_.get_supports_receiver()) + flags |= InfraredCapability::CAPABILITY_RECEIVER; + + return flags; +} + +bool Infrared::on_receive(remote_base::RemoteReceiveData data) { + // Forward received IR data to API server +#if defined(USE_API) && defined(USE_IR_RF) + if (api::global_api_server != nullptr) { +#ifdef USE_DEVICES + uint32_t device_id = this->get_device_id(); +#else + uint32_t device_id = 0; +#endif + api::global_api_server->send_infrared_rf_receive_event(device_id, this->get_object_id_hash(), &data.get_raw_data()); + } +#endif + return false; // Don't consume the event, allow other listeners to process it +} + +} // namespace esphome::infrared diff --git a/esphome/components/infrared/infrared.h b/esphome/components/infrared/infrared.h new file mode 100644 index 0000000000..3a891301f4 --- /dev/null +++ b/esphome/components/infrared/infrared.h @@ -0,0 +1,130 @@ +#pragma once + +// WARNING: This component is EXPERIMENTAL. The API may change at any time +// without following the normal breaking changes policy. Use at your own risk. +// Once the API is considered stable, this warning will be removed. + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/components/remote_base/remote_base.h" + +#include + +namespace esphome::infrared { + +/// Capability flags for individual infrared instances +enum InfraredCapability : uint32_t { + CAPABILITY_TRANSMITTER = 1 << 0, // Can transmit signals + CAPABILITY_RECEIVER = 1 << 1, // Can receive signals +}; + +/// Forward declarations +class Infrared; + +/// InfraredCall - Builder pattern for transmitting infrared signals +class InfraredCall { + public: + explicit InfraredCall(Infrared *parent) : parent_(parent) {} + + /// Set the carrier frequency in Hz + InfraredCall &set_carrier_frequency(uint32_t frequency); + /// Set the raw timings (positive = mark, negative = space) + /// Note: The timings vector must outlive the InfraredCall (zero-copy reference) + InfraredCall &set_raw_timings(const std::vector &timings); + /// Set the raw timings from packed protobuf sint32 data (zero-copy from wire) + /// Note: The data must outlive the InfraredCall + InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count); + /// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.) + InfraredCall &set_repeat_count(uint32_t count); + + /// Perform the transmission + void perform(); + + /// Get the carrier frequency + const optional &get_carrier_frequency() const { return this->carrier_frequency_; } + /// Get the raw timings (only valid if set via set_raw_timings, not packed) + const std::vector &get_raw_timings() const { return *this->raw_timings_; } + /// Check if raw timings have been set (either vector or packed) + bool has_raw_timings() const { return this->raw_timings_ != nullptr || this->packed_data_ != nullptr; } + /// Check if using packed data format + bool is_packed() const { return this->packed_data_ != nullptr; } + /// Get packed data (only valid if set via set_raw_timings_packed) + const uint8_t *get_packed_data() const { return this->packed_data_; } + uint16_t get_packed_length() const { return this->packed_length_; } + uint16_t get_packed_count() const { return this->packed_count_; } + /// Get the repeat count + uint32_t get_repeat_count() const { return this->repeat_count_; } + + protected: + uint32_t repeat_count_{1}; + Infrared *parent_; + optional carrier_frequency_; + // Vector-based timings (for lambdas/automations) + const std::vector *raw_timings_{nullptr}; + // Packed protobuf timings (for API zero-copy) + const uint8_t *packed_data_{nullptr}; + uint16_t packed_length_{0}; + uint16_t packed_count_{0}; +}; + +/// InfraredTraits - Describes the capabilities of an infrared implementation +class InfraredTraits { + public: + bool get_supports_transmitter() const { return this->supports_transmitter_; } + void set_supports_transmitter(bool supports) { this->supports_transmitter_ = supports; } + + bool get_supports_receiver() const { return this->supports_receiver_; } + void set_supports_receiver(bool supports) { this->supports_receiver_ = supports; } + + protected: + bool supports_transmitter_{false}; + bool supports_receiver_{false}; +}; + +/// Infrared - Base class for infrared remote control implementations +class Infrared : public Component, public EntityBase, public remote_base::RemoteReceiverListener { + public: + Infrared() = default; + + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } + + /// Set the remote receiver component + void set_receiver(remote_base::RemoteReceiverBase *receiver) { this->receiver_ = receiver; } + /// Set the remote transmitter component + void set_transmitter(remote_base::RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; } + + /// Check if this infrared has a transmitter configured + bool has_transmitter() const { return this->transmitter_ != nullptr; } + /// Check if this infrared has a receiver configured + bool has_receiver() const { return this->receiver_ != nullptr; } + + /// Get the traits for this infrared implementation + InfraredTraits &get_traits() { return this->traits_; } + const InfraredTraits &get_traits() const { return this->traits_; } + + /// Create a call object for transmitting + InfraredCall make_call(); + + /// Get capability flags for this infrared instance + uint32_t get_capability_flags() const; + + /// Called when IR data is received (from RemoteReceiverListener) + bool on_receive(remote_base::RemoteReceiveData data) override; + + protected: + friend class InfraredCall; + + /// Perform the actual transmission (called by InfraredCall) + virtual void control(const InfraredCall &call); + + // Underlying hardware components + remote_base::RemoteReceiverBase *receiver_{nullptr}; + remote_base::RemoteTransmitterBase *transmitter_{nullptr}; + + // Traits describing capabilities + InfraredTraits traits_; +}; + +} // namespace esphome::infrared diff --git a/esphome/components/inkplate/inkplate.cpp b/esphome/components/inkplate/inkplate.cpp index f96fb6905e..c921c643fa 100644 --- a/esphome/components/inkplate/inkplate.cpp +++ b/esphome/components/inkplate/inkplate.cpp @@ -293,6 +293,13 @@ void Inkplate::fill(Color color) { ESP_LOGV(TAG, "Fill called"); uint32_t start_time = millis(); + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time); + return; + } + if (this->greyscale_) { uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; memset(this->buffer_, (fill << 4) | fill, this->get_buffer_length_()); diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index 2ef8cf2649..34d7baf880 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -8,8 +8,9 @@ extern "C" { uint8_t temprature_sens_read(); } #elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) #include "driver/temperature_sensor.h" #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -27,9 +28,9 @@ namespace internal_temperature { static const char *const TAG = "internal_temperature"; #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) static temperature_sensor_handle_t tsensNew = NULL; #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -44,8 +45,9 @@ void InternalTemperatureSensor::update() { temperature = (raw - 32) / 1.8f; success = (raw != 128); #elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature); success = (result == ESP_OK); if (!success) { @@ -81,9 +83,9 @@ void InternalTemperatureSensor::update() { void InternalTemperatureSensor::setup() { #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew); diff --git a/esphome/components/ir_rf_proxy/__init__.py b/esphome/components/ir_rf_proxy/__init__.py new file mode 100644 index 0000000000..bc4079ede7 --- /dev/null +++ b/esphome/components/ir_rf_proxy/__init__.py @@ -0,0 +1,11 @@ +"""IR/RF Proxy component - provides remote_base backend for infrared platform.""" + +import esphome.codegen as cg + +CODEOWNERS = ["@kbx81"] + +# Namespace and constants exported for infrared.py platform +ir_rf_proxy_ns = cg.esphome_ns.namespace("ir_rf_proxy") + +CONF_REMOTE_RECEIVER_ID = "remote_receiver_id" +CONF_REMOTE_TRANSMITTER_ID = "remote_transmitter_id" diff --git a/esphome/components/ir_rf_proxy/infrared.py b/esphome/components/ir_rf_proxy/infrared.py new file mode 100644 index 0000000000..4a4d9fa860 --- /dev/null +++ b/esphome/components/ir_rf_proxy/infrared.py @@ -0,0 +1,77 @@ +"""Infrared platform implementation using remote_base (remote_transmitter/receiver).""" + +from typing import Any + +import esphome.codegen as cg +from esphome.components import infrared, remote_receiver, remote_transmitter +import esphome.config_validation as cv +from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY +import esphome.final_validate as fv + +from . import CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID, ir_rf_proxy_ns + +CODEOWNERS = ["@kbx81"] +DEPENDENCIES = ["infrared"] + +IrRfProxy = ir_rf_proxy_ns.class_("IrRfProxy", infrared.Infrared) + +CONFIG_SCHEMA = cv.All( + infrared.infrared_schema(IrRfProxy).extend( + { + cv.Optional(CONF_FREQUENCY, default=0): cv.frequency, + cv.Optional(CONF_REMOTE_RECEIVER_ID): cv.use_id( + remote_receiver.RemoteReceiverComponent + ), + cv.Optional(CONF_REMOTE_TRANSMITTER_ID): cv.use_id( + remote_transmitter.RemoteTransmitterComponent + ), + } + ), + cv.has_exactly_one_key(CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID), +) + + +def _final_validate(config: dict[str, Any]) -> None: + """Validate that transmitters have a proper carrier duty cycle.""" + # Only validate if this is an infrared (not RF) configuration with a transmitter + if config.get(CONF_FREQUENCY, 0) != 0 or CONF_REMOTE_TRANSMITTER_ID not in config: + return + + # Get the transmitter configuration + transmitter_id = config[CONF_REMOTE_TRANSMITTER_ID] + full_config = fv.full_config.get() + transmitter_path = full_config.get_path_for_id(transmitter_id)[:-1] + transmitter_config = full_config.get_config_for_path(transmitter_path) + + # Check if carrier_duty_percent set to 0 or 100 + # Note: remote_transmitter schema requires this field and validates 1-100%, + # but we double-check here for infrared to provide a helpful error message + duty_percent = transmitter_config.get(CONF_CARRIER_DUTY_PERCENT) + if duty_percent in {0, 100}: + raise cv.Invalid( + f"Transmitter '{transmitter_id}' must have '{CONF_CARRIER_DUTY_PERCENT}' configured with " + "an intermediate value (typically 30-50%) for infrared transmission. If this is an RF " + f"transmitter, configure this infrared with a '{CONF_FREQUENCY}' value greater than 0" + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + +async def to_code(config: dict[str, Any]) -> None: + """Code generation for remote_base infrared platform.""" + # Create and register the infrared entity + var = await infrared.new_infrared(config) + + # Set frequency / 1000; zero indicates infrared hardware + cg.add(var.set_frequency(config[CONF_FREQUENCY] / 1000)) + + # Link transmitter if specified + if CONF_REMOTE_TRANSMITTER_ID in config: + transmitter = await cg.get_variable(config[CONF_REMOTE_TRANSMITTER_ID]) + cg.add(var.set_transmitter(transmitter)) + + # Link receiver if specified + if CONF_REMOTE_RECEIVER_ID in config: + receiver = await cg.get_variable(config[CONF_REMOTE_RECEIVER_ID]) + cg.add(var.set_receiver(receiver)) diff --git a/esphome/components/ir_rf_proxy/ir_rf_proxy.cpp b/esphome/components/ir_rf_proxy/ir_rf_proxy.cpp new file mode 100644 index 0000000000..5239a4667c --- /dev/null +++ b/esphome/components/ir_rf_proxy/ir_rf_proxy.cpp @@ -0,0 +1,23 @@ +#include "ir_rf_proxy.h" +#include "esphome/core/log.h" + +namespace esphome::ir_rf_proxy { + +static const char *const TAG = "ir_rf_proxy"; + +void IrRfProxy::dump_config() { + ESP_LOGCONFIG(TAG, + "IR/RF Proxy '%s'\n" + " Supports Transmitter: %s\n" + " Supports Receiver: %s", + this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()), + YESNO(this->traits_.get_supports_receiver())); + + if (this->is_rf()) { + ESP_LOGCONFIG(TAG, " Hardware Type: RF (%.3f MHz)", this->frequency_khz_ / 1e3f); + } else { + ESP_LOGCONFIG(TAG, " Hardware Type: Infrared"); + } +} + +} // namespace esphome::ir_rf_proxy diff --git a/esphome/components/ir_rf_proxy/ir_rf_proxy.h b/esphome/components/ir_rf_proxy/ir_rf_proxy.h new file mode 100644 index 0000000000..d7c8919def --- /dev/null +++ b/esphome/components/ir_rf_proxy/ir_rf_proxy.h @@ -0,0 +1,32 @@ +#pragma once + +// WARNING: This component is EXPERIMENTAL. The API may change at any time +// without following the normal breaking changes policy. Use at your own risk. +// Once the API is considered stable, this warning will be removed. + +#include "esphome/components/infrared/infrared.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" +#include "esphome/components/remote_receiver/remote_receiver.h" + +namespace esphome::ir_rf_proxy { + +/// IrRfProxy - Infrared platform implementation using remote_transmitter/receiver as backend +class IrRfProxy : public infrared::Infrared { + public: + IrRfProxy() = default; + + void dump_config() override; + + /// Set RF frequency in kHz (0 = infrared, non-zero = RF) + void set_frequency(uint32_t frequency_khz) { this->frequency_khz_ = frequency_khz; } + /// Get RF frequency in kHz + uint32_t get_frequency() const { return this->frequency_khz_; } + /// Check if this is RF mode (non-zero frequency) + bool is_rf() const { return this->frequency_khz_ > 0; } + + protected: + // RF frequency in kHz (Hz / 1000); 0 = infrared, non-zero = RF + uint32_t frequency_khz_{0}; +}; + +} // namespace esphome::ir_rf_proxy diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp index 84181dac48..6fd8b1bd65 100644 --- a/esphome/components/jsn_sr04t/jsn_sr04t.cpp +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -39,7 +39,9 @@ void Jsnsr04tComponent::check_buffer_() { ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters); this->publish_state(meters); } else { - ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); + char hex_buf[format_hex_pretty_size(4)]; + ESP_LOGW(TAG, "Invalid data read from sensor: %s", + format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size())); } } else { ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]); diff --git a/esphome/components/kuntze/kuntze.cpp b/esphome/components/kuntze/kuntze.cpp index 30f98aaa99..1b772d062c 100644 --- a/esphome/components/kuntze/kuntze.cpp +++ b/esphome/components/kuntze/kuntze.cpp @@ -1,4 +1,5 @@ #include "kuntze.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -10,11 +11,17 @@ static const char *const TAG = "kuntze"; static const uint8_t CMD_READ_REG = 0x03; static const uint16_t REGISTER[] = {4136, 4160, 4680, 6000, 4688, 4728, 5832}; +// Maximum bytes to log for Modbus responses (2 registers = 4, plus count = 5) +static constexpr size_t KUNTZE_MAX_LOG_BYTES = 8; + void Kuntze::on_modbus_data(const std::vector &data) { auto get_16bit = [&](int i) -> uint16_t { return (uint16_t(data[i * 2]) << 8) | uint16_t(data[i * 2 + 1]); }; this->waiting_ = false; - ESP_LOGV(TAG, "Data: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(KUNTZE_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Data: %s", format_hex_pretty_to(hex_buf, data.data(), data.size())); float value = (float) get_16bit(0); for (int i = 0; i < data[3]; i++) diff --git a/esphome/components/lc709203f/lc709203f.cpp b/esphome/components/lc709203f/lc709203f.cpp index 7e6ac878f8..8c7018124a 100644 --- a/esphome/components/lc709203f/lc709203f.cpp +++ b/esphome/components/lc709203f/lc709203f.cpp @@ -146,19 +146,14 @@ void Lc709203f::update() { } void Lc709203f::dump_config() { - ESP_LOGCONFIG(TAG, "LC709203F:"); - LOG_I2C_DEVICE(this); - - LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, + "LC709203F:\n" " Pack Size: %d mAH\n" - " Pack APA: 0x%02X", - this->pack_size_, this->apa_); - - // This is only true if the pack_voltage_ is either 0x0000 or 0x0001. The config validator - // should have already verified this. - ESP_LOGCONFIG(TAG, " Pack Rated Voltage: 3.%sV", this->pack_voltage_ == 0x0000 ? "8" : "7"); - + " Pack APA: 0x%02X\n" + " Pack Rated Voltage: 3.%sV", + this->pack_size_, this->apa_, this->pack_voltage_ == 0x0000 ? "8" : "7"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Battery Remaining", this->battery_remaining_sensor_); @@ -188,11 +183,14 @@ uint8_t Lc709203f::get_register_(uint8_t register_to_read, uint16_t *register_va return_code = this->read_register(register_to_read, &read_buffer[3], 3); if (return_code != i2c::NO_ERROR) { // Error on the i2c bus - this->status_set_warning( - str_sprintf("Error code %d when reading from register 0x%02X", return_code, register_to_read).c_str()); + char buf[64]; + snprintf(buf, sizeof(buf), "Error code %d when reading from register 0x%02X", return_code, register_to_read); + this->status_set_warning(buf); } else if (crc8(read_buffer, 5, 0x00, 0x07, true) != read_buffer[5]) { // I2C indicated OK, but the CRC of the data does not matcth. - this->status_set_warning(str_sprintf("CRC error reading from register 0x%02X", register_to_read).c_str()); + char buf[64]; + snprintf(buf, sizeof(buf), "CRC error reading from register 0x%02X", register_to_read); + this->status_set_warning(buf); } else { *register_value = ((uint16_t) read_buffer[4] << 8) | (uint16_t) read_buffer[3]; return i2c::NO_ERROR; @@ -230,8 +228,9 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set) if (return_code == i2c::NO_ERROR) { return return_code; } else { - this->status_set_warning( - str_sprintf("Error code %d when writing to register 0x%02X", return_code, register_to_set).c_str()); + char buf[64]; + snprintf(buf, sizeof(buf), "Error code %d when writing to register 0x%02X", return_code, register_to_set); + this->status_set_warning(buf); } } diff --git a/esphome/components/ld2410/binary_sensor.py b/esphome/components/ld2410/binary_sensor.py index 4e35f67fbe..fb5b5cabff 100644 --- a/esphome/components/ld2410/binary_sensor.py +++ b/esphome/components/ld2410/binary_sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( CONF_HAS_MOVING_TARGET, CONF_HAS_STILL_TARGET, CONF_HAS_TARGET, + CONF_ID, DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY, DEVICE_CLASS_PRESENCE, @@ -19,6 +20,7 @@ DEPENDENCIES = ["ld2410"] CONF_OUT_PIN_PRESENCE_STATUS = "out_pin_presence_status" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( device_class=DEVICE_CLASS_OCCUPANCY, diff --git a/esphome/components/ld2410/button/__init__.py b/esphome/components/ld2410/button/__init__.py index 1cd56082c3..fa6f31ee25 100644 --- a/esphome/components/ld2410/button/__init__.py +++ b/esphome/components/ld2410/button/__init__.py @@ -3,6 +3,7 @@ from esphome.components import button import esphome.config_validation as cv from esphome.const import ( CONF_FACTORY_RESET, + CONF_ID, CONF_RESTART, DEVICE_CLASS_RESTART, ENTITY_CATEGORY_CONFIG, @@ -21,6 +22,7 @@ RestartButton = ld2410_ns.class_("RestartButton", button.Button) CONF_QUERY_PARAMS = "query_params" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_FACTORY_RESET): button.button_schema( FactoryResetButton, diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index bb2e4e2f4c..5294f7cd36 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -117,6 +117,8 @@ constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = { {OUT_PIN_LEVEL_HIGH, "high"}, }; +constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800}; + // Helper functions for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) { for (const auto &entry : arr) { @@ -258,9 +260,10 @@ void LD2410Component::read_all_info() { this->query_parameters_(); this->set_config_mode_(false); #ifdef USE_SELECT - const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); if (this->baud_rate_select_ != nullptr) { - this->baud_rate_select_->publish_state(baud_rate); + if (auto index = ld24xx::find_index(BAUD_RATES, this->parent_->get_baud_rate())) { + this->baud_rate_select_->publish_state(*index); + } } #endif } @@ -413,7 +416,8 @@ bool LD2410Component::handle_ack_data_() { return true; } if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { - ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); + char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)]; + ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE)); return true; } if (this->buffer_data_[COMMAND_STATUS] != 0x01) { @@ -438,7 +442,8 @@ bool LD2410Component::handle_ack_data_() { ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option()); + auto baud = this->baud_rate_select_->current_option(); + ESP_LOGE(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str()); } #endif break; @@ -597,11 +602,17 @@ void LD2410Component::readline_(int readch) { return; // Not enough data to process yet } if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif this->handle_periodic_data_(); this->buffer_pos_ = 0; // Reset position index for next message } else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif if (this->handle_ack_data_()) { this->buffer_pos_ = 0; // Reset position index for next message } else { @@ -756,10 +767,10 @@ void LD2410Component::set_light_out_control() { #endif #ifdef USE_SELECT if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) { - this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option()); + this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str()); } if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) { - this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option()); + this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str()); } #endif this->set_config_mode_(true); diff --git a/esphome/components/ld2410/number/__init__.py b/esphome/components/ld2410/number/__init__.py index ffa4e7e146..01dbcc785d 100644 --- a/esphome/components/ld2410/number/__init__.py +++ b/esphome/components/ld2410/number/__init__.py @@ -31,6 +31,7 @@ TIMEOUT_GROUP = "timeout" CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Inclusive(CONF_TIMEOUT, TIMEOUT_GROUP): number.number_schema( MaxDistanceTimeoutNumber, diff --git a/esphome/components/ld2410/select/__init__.py b/esphome/components/ld2410/select/__init__.py index 686afdef14..9c4f654aa1 100644 --- a/esphome/components/ld2410/select/__init__.py +++ b/esphome/components/ld2410/select/__init__.py @@ -3,6 +3,7 @@ from esphome.components import select import esphome.config_validation as cv from esphome.const import ( CONF_BAUD_RATE, + CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_LIGHTBULB, ICON_RULER, @@ -22,6 +23,7 @@ CONF_OUT_PIN_LEVEL = "out_pin_level" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema( DistanceResolutionSelect, diff --git a/esphome/components/ld2410/sensor.py b/esphome/components/ld2410/sensor.py index 3bd34963bc..459018e263 100644 --- a/esphome/components/ld2410/sensor.py +++ b/esphome/components/ld2410/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ID, CONF_LIGHT, CONF_MOVING_DISTANCE, DEVICE_CLASS_DISTANCE, @@ -28,6 +29,7 @@ CONF_STILL_ENERGY = "still_energy" CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, diff --git a/esphome/components/ld2410/switch/__init__.py b/esphome/components/ld2410/switch/__init__.py index 71b8a40a29..4276b28a71 100644 --- a/esphome/components/ld2410/switch/__init__.py +++ b/esphome/components/ld2410/switch/__init__.py @@ -3,6 +3,7 @@ from esphome.components import switch import esphome.config_validation as cv from esphome.const import ( CONF_BLUETOOTH, + CONF_ID, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG, ICON_BLUETOOTH, @@ -17,6 +18,7 @@ EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch) CONF_ENGINEERING_MODE = "engineering_mode" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema( EngineeringModeSwitch, diff --git a/esphome/components/ld2410/text_sensor.py b/esphome/components/ld2410/text_sensor.py index 5a021d9163..a34c8ec0d2 100644 --- a/esphome/components/ld2410/text_sensor.py +++ b/esphome/components/ld2410/text_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ID, CONF_MAC_ADDRESS, CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, @@ -14,6 +15,7 @@ from . import CONF_LD2410_ID, LD2410Component DEPENDENCIES = ["ld2410"] CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP diff --git a/esphome/components/ld2412/binary_sensor.py b/esphome/components/ld2412/binary_sensor.py index aa1b0d2cd8..98fa5965cd 100644 --- a/esphome/components/ld2412/binary_sensor.py +++ b/esphome/components/ld2412/binary_sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( CONF_HAS_MOVING_TARGET, CONF_HAS_STILL_TARGET, CONF_HAS_TARGET, + CONF_ID, DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY, DEVICE_CLASS_RUNNING, @@ -20,6 +21,7 @@ DEPENDENCIES = ["ld2412"] CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS = "dynamic_background_correction_status" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional( CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS diff --git a/esphome/components/ld2412/button/__init__.py b/esphome/components/ld2412/button/__init__.py index e78cad4b88..e0ca285265 100644 --- a/esphome/components/ld2412/button/__init__.py +++ b/esphome/components/ld2412/button/__init__.py @@ -3,6 +3,7 @@ from esphome.components import button import esphome.config_validation as cv from esphome.const import ( CONF_FACTORY_RESET, + CONF_ID, CONF_RESTART, DEVICE_CLASS_RESTART, ENTITY_CATEGORY_CONFIG, @@ -26,6 +27,7 @@ CONF_QUERY_PARAMS = "query_params" CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_FACTORY_RESET): button.button_schema( FactoryResetButton, diff --git a/esphome/components/ld2412/ld2412.cpp b/esphome/components/ld2412/ld2412.cpp index 0f6fe62d30..c2f441e472 100644 --- a/esphome/components/ld2412/ld2412.cpp +++ b/esphome/components/ld2412/ld2412.cpp @@ -128,6 +128,8 @@ constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = { {OUT_PIN_LEVEL_HIGH, "high"}, }; +constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800}; + // Helper functions for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) { for (const auto &entry : arr) { @@ -293,9 +295,10 @@ void LD2412Component::read_all_info() { #endif this->set_config_mode_(false); #ifdef USE_SELECT - const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); if (this->baud_rate_select_ != nullptr) { - this->baud_rate_select_->publish_state(baud_rate); + if (auto index = ld24xx::find_index(BAUD_RATES, this->parent_->get_baud_rate())) { + this->baud_rate_select_->publish_state(*index); + } } #endif } @@ -457,7 +460,8 @@ bool LD2412Component::handle_ack_data_() { return true; } if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { - ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); + char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)]; + ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE)); return true; } if (this->buffer_data_[COMMAND_STATUS] != 0x01) { @@ -482,7 +486,8 @@ bool LD2412Component::handle_ack_data_() { ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGW(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option()); + auto baud = this->baud_rate_select_->current_option(); + ESP_LOGW(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str()); } #endif break; @@ -670,11 +675,17 @@ void LD2412Component::readline_(int readch) { return; // Not enough data to process yet } if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif this->handle_periodic_data_(); this->buffer_pos_ = 0; // Reset position index for next message } else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif if (this->handle_ack_data_()) { this->buffer_pos_ = 0; // Reset position index for next message } else { @@ -780,7 +791,7 @@ void LD2412Component::set_basic_config() { 1, TOTAL_GATES, DEFAULT_PRESENCE_TIMEOUT, 0, #endif #ifdef USE_SELECT - find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option()), + find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str()), #else 0x01, // Default value if not using select #endif @@ -834,7 +845,7 @@ void LD2412Component::set_light_out_control() { #endif #ifdef USE_SELECT if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) { - this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option()); + this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str()); } #endif uint8_t value[2] = {this->light_function_, this->light_threshold_}; diff --git a/esphome/components/ld2412/number/__init__.py b/esphome/components/ld2412/number/__init__.py index 5b0d6d8749..b6e1c8d039 100644 --- a/esphome/components/ld2412/number/__init__.py +++ b/esphome/components/ld2412/number/__init__.py @@ -31,6 +31,7 @@ TIMEOUT_GROUP = "timeout" CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema( LightThresholdNumber, diff --git a/esphome/components/ld2412/select/__init__.py b/esphome/components/ld2412/select/__init__.py index d71ce460d9..a54cd700ed 100644 --- a/esphome/components/ld2412/select/__init__.py +++ b/esphome/components/ld2412/select/__init__.py @@ -3,6 +3,7 @@ from esphome.components import select import esphome.config_validation as cv from esphome.const import ( CONF_BAUD_RATE, + CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_LIGHTBULB, ICON_RULER, @@ -22,6 +23,7 @@ CONF_OUT_PIN_LEVEL = "out_pin_level" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_BAUD_RATE): select.select_schema( BaudRateSelect, diff --git a/esphome/components/ld2412/sensor.py b/esphome/components/ld2412/sensor.py index 0bfbd9bf1d..f562afe0ee 100644 --- a/esphome/components/ld2412/sensor.py +++ b/esphome/components/ld2412/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ID, CONF_LIGHT, CONF_MOVING_DISTANCE, DEVICE_CLASS_DISTANCE, @@ -28,6 +29,7 @@ CONF_STILL_ENERGY = "still_energy" CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, diff --git a/esphome/components/ld2412/switch/__init__.py b/esphome/components/ld2412/switch/__init__.py index df994687ec..7a87e9e483 100644 --- a/esphome/components/ld2412/switch/__init__.py +++ b/esphome/components/ld2412/switch/__init__.py @@ -3,6 +3,7 @@ from esphome.components import switch import esphome.config_validation as cv from esphome.const import ( CONF_BLUETOOTH, + CONF_ID, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG, ICON_BLUETOOTH, @@ -17,6 +18,7 @@ EngineeringModeSwitch = LD2412_ns.class_("EngineeringModeSwitch", switch.Switch) CONF_ENGINEERING_MODE = "engineering_mode" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_BLUETOOTH): switch.switch_schema( BluetoothSwitch, diff --git a/esphome/components/ld2412/text_sensor.py b/esphome/components/ld2412/text_sensor.py index 1074494933..22fba5193e 100644 --- a/esphome/components/ld2412/text_sensor.py +++ b/esphome/components/ld2412/text_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ID, CONF_MAC_ADDRESS, CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, @@ -14,6 +15,7 @@ from . import CONF_LD2412_ID, LD2412Component DEPENDENCIES = ["ld2412"] CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP diff --git a/esphome/components/ld2450/binary_sensor.py b/esphome/components/ld2450/binary_sensor.py index 37f722b0fa..89e629253a 100644 --- a/esphome/components/ld2450/binary_sensor.py +++ b/esphome/components/ld2450/binary_sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( CONF_HAS_MOVING_TARGET, CONF_HAS_STILL_TARGET, CONF_HAS_TARGET, + CONF_ID, DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY, ) @@ -18,6 +19,7 @@ ICON_SHIELD_ACCOUNT = "mdi:shield-account" ICON_TARGET_ACCOUNT = "mdi:target-account" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( device_class=DEVICE_CLASS_OCCUPANCY, diff --git a/esphome/components/ld2450/button/__init__.py b/esphome/components/ld2450/button/__init__.py index 429aa59389..682487d750 100644 --- a/esphome/components/ld2450/button/__init__.py +++ b/esphome/components/ld2450/button/__init__.py @@ -3,6 +3,7 @@ from esphome.components import button import esphome.config_validation as cv from esphome.const import ( CONF_FACTORY_RESET, + CONF_ID, CONF_RESTART, DEVICE_CLASS_RESTART, ENTITY_CATEGORY_CONFIG, @@ -17,6 +18,7 @@ FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button) RestartButton = ld2450_ns.class_("RestartButton", button.Button) CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Optional(CONF_FACTORY_RESET): button.button_schema( FactoryResetButton, diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index e69ef31d4f..58d469b2a7 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -88,6 +88,9 @@ constexpr StringToUint8 ZONE_TYPE_BY_STR[] = { {"Filter", ZONE_FILTER}, }; +// Baud rates in the same order as BAUD_RATES_BY_STR for index-based lookup +constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800}; + // Helper functions for lookups template uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { for (const auto &entry : arr) { @@ -376,9 +379,10 @@ void LD2450Component::read_all_info() { this->query_zone_(); this->set_config_mode_(false); #ifdef USE_SELECT - const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); - if (this->baud_rate_select_ != nullptr && strcmp(this->baud_rate_select_->current_option(), baud_rate.c_str()) != 0) { - this->baud_rate_select_->publish_state(baud_rate); + if (this->baud_rate_select_ != nullptr) { + if (auto index = ld24xx::find_index(BAUD_RATES, this->parent_->get_baud_rate())) { + this->baud_rate_select_->publish_state(*index); + } } this->publish_zone_type(); #endif @@ -607,7 +611,8 @@ bool LD2450Component::handle_ack_data_() { return true; } if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { - ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); + char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)]; + ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE)); return true; } if (this->buffer_data_[COMMAND_STATUS] != 0x01) { @@ -632,7 +637,8 @@ bool LD2450Component::handle_ack_data_() { ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option()); + auto baud = this->baud_rate_select_->current_option(); + ESP_LOGE(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str()); } #endif break; @@ -709,11 +715,12 @@ bool LD2450Component::handle_ack_data_() { case CMD_QUERY_ZONE: ESP_LOGV(TAG, "Query zone conf"); - this->zone_type_ = std::stoi(std::to_string(this->buffer_data_[10]), nullptr, 16); + this->zone_type_ = this->buffer_data_[10]; this->publish_zone_type(); #ifdef USE_SELECT if (this->zone_type_select_ != nullptr) { - ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->current_option()); + auto zone = this->zone_type_select_->current_option(); + ESP_LOGV(TAG, "Change zone type to: %.*s", (int) zone.size(), zone.c_str()); } #endif if (this->buffer_data_[10] == 0x00) { @@ -758,11 +765,17 @@ void LD2450Component::readline_(int readch) { } if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] && this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) { - ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif this->handle_periodic_data_(); this->buffer_pos_ = 0; // Reset position index for next frame } else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { - ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)]; + ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_)); +#endif if (this->handle_ack_data_()) { this->buffer_pos_ = 0; // Reset position index for next message } else { @@ -805,9 +818,8 @@ void LD2450Component::set_zone_type(const char *state) { // Publish Zone Type to Select component void LD2450Component::publish_zone_type() { #ifdef USE_SELECT - std::string zone_type = find_str(ZONE_TYPE_BY_UINT, this->zone_type_); if (this->zone_type_select_ != nullptr) { - this->zone_type_select_->publish_state(zone_type); + this->zone_type_select_->publish_state(find_str(ZONE_TYPE_BY_UINT, this->zone_type_)); } #endif } diff --git a/esphome/components/ld2450/number/__init__.py b/esphome/components/ld2450/number/__init__.py index d2098f6131..799c0703f2 100644 --- a/esphome/components/ld2450/number/__init__.py +++ b/esphome/components/ld2450/number/__init__.py @@ -28,6 +28,7 @@ ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number) CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Required(CONF_PRESENCE_TIMEOUT): number.number_schema( PresenceTimeoutNumber, diff --git a/esphome/components/ld2450/select/__init__.py b/esphome/components/ld2450/select/__init__.py index 25dd819637..4f237dc94f 100644 --- a/esphome/components/ld2450/select/__init__.py +++ b/esphome/components/ld2450/select/__init__.py @@ -1,7 +1,12 @@ import esphome.codegen as cg from esphome.components import select import esphome.config_validation as cv -from esphome.const import CONF_BAUD_RATE, ENTITY_CATEGORY_CONFIG, ICON_THERMOMETER +from esphome.const import ( + CONF_BAUD_RATE, + CONF_ID, + ENTITY_CATEGORY_CONFIG, + ICON_THERMOMETER, +) from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns @@ -11,6 +16,7 @@ BaudRateSelect = ld2450_ns.class_("BaudRateSelect", select.Select) ZoneTypeSelect = ld2450_ns.class_("ZoneTypeSelect", select.Select) CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Optional(CONF_BAUD_RATE): select.select_schema( BaudRateSelect, diff --git a/esphome/components/ld2450/sensor.py b/esphome/components/ld2450/sensor.py index 4a3597d583..3dee8bf470 100644 --- a/esphome/components/ld2450/sensor.py +++ b/esphome/components/ld2450/sensor.py @@ -4,6 +4,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_ANGLE, CONF_DISTANCE, + CONF_ID, CONF_RESOLUTION, CONF_SPEED, CONF_X, @@ -40,6 +41,7 @@ UNIT_MILLIMETER_PER_SECOND = "mm/s" CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( accuracy_decimals=0, diff --git a/esphome/components/ld2450/switch/__init__.py b/esphome/components/ld2450/switch/__init__.py index 2d76b75781..0c0c92377b 100644 --- a/esphome/components/ld2450/switch/__init__.py +++ b/esphome/components/ld2450/switch/__init__.py @@ -3,6 +3,7 @@ from esphome.components import switch import esphome.config_validation as cv from esphome.const import ( CONF_BLUETOOTH, + CONF_ID, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG, ICON_BLUETOOTH, @@ -17,6 +18,7 @@ MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch) CONF_MULTI_TARGET = "multi_target" CONFIG_SCHEMA = { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Optional(CONF_BLUETOOTH): switch.switch_schema( BluetoothSwitch, diff --git a/esphome/components/ld2450/text_sensor.py b/esphome/components/ld2450/text_sensor.py index 89b6939a29..4e5d7d419b 100644 --- a/esphome/components/ld2450/text_sensor.py +++ b/esphome/components/ld2450/text_sensor.py @@ -3,6 +3,7 @@ from esphome.components import text_sensor import esphome.config_validation as cv from esphome.const import ( CONF_DIRECTION, + CONF_ID, CONF_MAC_ADDRESS, CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, @@ -20,6 +21,7 @@ MAX_TARGETS = 3 CONFIG_SCHEMA = cv.Schema( { + cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, diff --git a/esphome/components/ld24xx/ld24xx.h b/esphome/components/ld24xx/ld24xx.h index cbd86e4e40..fd55167974 100644 --- a/esphome/components/ld24xx/ld24xx.h +++ b/esphome/components/ld24xx/ld24xx.h @@ -39,6 +39,15 @@ namespace esphome::ld24xx { +// Helper to find index of value in constexpr array +template optional find_index(const uint32_t (&arr)[N], uint32_t value) { + for (size_t i = 0; i < N; i++) { + if (arr[i] == value) + return i; + } + return {}; +} + static const char *const UNKNOWN_MAC = "unknown"; static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index aaa4794586..a203dde115 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -130,8 +130,10 @@ void LEDCOutput::setup() { } int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); - ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_); - ESP_LOGV(TAG, "Angle of %.1f° results in hpoint %u", this->phase_angle_, hpoint); + ESP_LOGV(TAG, + "Configured frequency %f with a bit depth of %u bits\n" + "Angle of %.1f° results in hpoint %u", + this->frequency_, this->bit_depth_, this->phase_angle_, hpoint); ledc_channel_config_t chan_conf{}; chan_conf.gpio_num = this->pin_->get_pin(); @@ -147,25 +149,30 @@ void LEDCOutput::setup() { } void LEDCOutput::dump_config() { - ESP_LOGCONFIG(TAG, "Output:"); - LOG_PIN(" Pin ", this->pin_); ESP_LOGCONFIG(TAG, + "Output:\n" " Channel: %u\n" " PWM Frequency: %.1f Hz\n" " Phase angle: %.1f°\n" " Bit depth: %u", this->channel_, this->frequency_, this->phase_angle_, this->bit_depth_); - ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_)); - ESP_LOGV(TAG, " Min frequency for bit depth: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max frequency for bit depth-1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1)); - ESP_LOGV(TAG, " Min frequency for bit depth-1: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max frequency for bit depth+1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1)); - ESP_LOGV(TAG, " Min frequency for bit depth+1: %f", - ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100))); - ESP_LOGV(TAG, " Max res bits: %d", MAX_RES_BITS); - ESP_LOGV(TAG, " Clock frequency: %f", CLOCK_FREQUENCY); + LOG_PIN(" Pin ", this->pin_); + ESP_LOGV(TAG, + " Max frequency for bit depth: %f\n" + " Min frequency for bit depth: %f\n" + " Max frequency for bit depth-1: %f\n" + " Min frequency for bit depth-1: %f\n" + " Max frequency for bit depth+1: %f\n" + " Min frequency for bit depth+1: %f\n" + " Max res bits: %d\n" + " Clock frequency: %f", + ledc_max_frequency_for_bit_depth(this->bit_depth_), + ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100)), + ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1), + ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100)), + ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1), + ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100)), MAX_RES_BITS, + CLOCK_FREQUENCY); } void LEDCOutput::update_frequency(float frequency) { diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 7ce79aa514..7a45b9dc3f 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -50,7 +50,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( ), cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15), cv.Optional(CONF_PHASE_ANGLE): cv.All( - cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0) + cv.angle, cv.float_range(min=0.0, max=360.0) ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 93b66888da..8318722b80 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -32,8 +32,10 @@ from .const import ( CONF_SDK_SILENT, CONF_UART_PORT, FAMILIES, + FAMILY_BK7231N, FAMILY_COMPONENT, FAMILY_FRIENDLY, + FAMILY_RTL8710B, KEY_BOARD, KEY_COMPONENT, KEY_COMPONENT_DATA, @@ -50,6 +52,22 @@ CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["preferences"] IS_TARGET_PLATFORM = True +# BK7231N SDK options to disable unused features. +# Disabling BLE saves ~21KB RAM and ~200KB Flash because BLE init code is +# called unconditionally by the SDK. ESPHome doesn't use BLE on LibreTiny. +# +# This only works on BK7231N (BLE 5.x). Other BK72XX chips using BLE 4.2 +# (BK7231T, BK7231Q, BK7251; BK7252 boards use the BK7251 family) have a bug +# where the BLE library still links and references undefined symbols when +# CFG_SUPPORT_BLE=0. +# +# Other options like CFG_TX_EVM_TEST, CFG_RX_SENSITIVITY_TEST, CFG_SUPPORT_BKREG, +# CFG_SUPPORT_OTA_HTTP, and CFG_USE_SPI_SLAVE were evaluated but provide no # NOLINT +# measurable benefit - the linker already strips unreferenced code via -gc-sections. +_BK7231N_SYS_CONFIG_OPTIONS = [ + "CFG_SUPPORT_BLE=0", +] + def _detect_variant(value): if KEY_LIBRETINY not in CORE.data: @@ -174,9 +192,9 @@ def _notify_old_style(config): # The dev and latest branches will be at *least* this version, which is what matters. ARDUINO_VERSIONS = { - "dev": (cv.Version(1, 9, 1), "https://github.com/libretiny-eu/libretiny.git"), - "latest": (cv.Version(1, 9, 1), "libretiny"), - "recommended": (cv.Version(1, 9, 1), None), + "dev": (cv.Version(1, 9, 2), "https://github.com/libretiny-eu/libretiny.git"), + "latest": (cv.Version(1, 9, 2), "libretiny"), + "recommended": (cv.Version(1, 9, 2), None), } @@ -261,11 +279,23 @@ async def component_to_code(config): cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]]) - # LibreTiny uses MULTI_NO_ATOMICS because platforms like BK7231N (ARM968E-S) lack - # exclusive load/store (no LDREX/STREX). std::atomic RMW operations require libatomic, - # which is not linked to save flash (4-8KB). Even if linked, libatomic would use locks - # (ATOMIC_INT_LOCK_FREE=1), so explicit FreeRTOS mutexes are simpler and equivalent. - cg.add_define(ThreadModel.MULTI_NO_ATOMICS) + # Set threading model based on chip architecture + component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] + if component.supports_atomics: + # RTL87xx (Cortex-M4) and LN882x (Cortex-M4F) have LDREX/STREX + cg.add_define(ThreadModel.MULTI_ATOMICS) + else: + # BK72xx uses ARM968E-S (ARMv5TE) which lacks LDREX/STREX. + # std::atomic RMW operations would require libatomic (not linked to save + # 4-8KB flash). Even if linked, it would use locks, so explicit FreeRTOS + # mutexes are simpler and equivalent. + cg.add_define(ThreadModel.MULTI_NO_ATOMICS) + + # RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake + # required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220) + # RTL8720C (ambz2) requires FreeRTOS 10.x so this only applies to RTL8710B + if config[CONF_FAMILY] == FAMILY_RTL8710B: + cg.add_platformio_option("custom_versions.freertos", "8.2.3") # force using arduino framework cg.add_platformio_option("framework", "arduino") @@ -346,4 +376,10 @@ async def component_to_code(config): cg.add_platformio_option("custom_fw_name", "esphome") cg.add_platformio_option("custom_fw_version", __version__) + # Apply chip-specific SDK options to save RAM/Flash + if config[CONF_FAMILY] == FAMILY_BK7231N: + cg.add_platformio_option( + "custom_options.sys_config#h", _BK7231N_SYS_CONFIG_OPTIONS + ) + await cg.register_component(var, config) diff --git a/esphome/components/libretiny/const.py b/esphome/components/libretiny/const.py index 671992f8bd..bc4ca99ab4 100644 --- a/esphome/components/libretiny/const.py +++ b/esphome/components/libretiny/const.py @@ -11,6 +11,7 @@ class LibreTinyComponent: board_pins: dict[str, dict[str, int]] pin_validation: Callable[[int], int] usage_validation: Callable[[dict], dict] + supports_atomics: bool = False # True for Cortex-M4(F) with LDREX/STREX CONF_LIBRETINY = "libretiny" diff --git a/esphome/components/libretiny/generate_components.py b/esphome/components/libretiny/generate_components.py index c750b79317..41b4389446 100644 --- a/esphome/components/libretiny/generate_components.py +++ b/esphome/components/libretiny/generate_components.py @@ -11,13 +11,27 @@ from black import FileMode, format_str from ltchiptool import Board, Family from ltchiptool.util.lvm import LVM -BASE_CODE_INIT = """ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. -# For custom pin validators, put validate_pin() or validate_usage() -# in gpio.py file in this directory. -# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA -# in schema.py file in this directory. +BASE_CODE_INIT = ''' +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. + +To customize this component: + - Pin validators: Create gpio.py with validate_pin() or validate_usage() + - Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA + +Platform-specific code should be added to the main libretiny component +(__init__.py in esphome/components/libretiny/) rather than here. +""" from esphome import pins from esphome.components import libretiny @@ -31,8 +45,9 @@ from esphome.core import CORE {IMPORTS} -CODEOWNERS = ["@kuba2k2"] +CODEOWNERS = {CODEOWNERS} AUTO_LOAD = ["libretiny"] +IS_TARGET_PLATFORM = True COMPONENT_DATA = LibreTinyComponent( name=COMPONENT_{COMPONENT}, @@ -40,6 +55,7 @@ COMPONENT_DATA = LibreTinyComponent( board_pins={COMPONENT}_BOARD_PINS, pin_validation={PIN_VALIDATION}, usage_validation={USAGE_VALIDATION}, + supports_atomics={SUPPORTS_ATOMICS}, ) @@ -63,11 +79,22 @@ async def to_code(config): @pins.PIN_SCHEMA_REGISTRY.register("{COMPONENT_LOWER}", PIN_SCHEMA) async def pin_to_code(config): return await libretiny.gpio.component_pin_to_code(config) -""" +''' -BASE_CODE_BOARDS = """ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. +BASE_CODE_BOARDS = ''' +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. +""" from esphome.components.libretiny.const import {FAMILIES} @@ -76,7 +103,7 @@ from esphome.components.libretiny.const import {FAMILIES} {COMPONENT}_BOARD_PINS = {PINS_JSON} BOARDS = {COMPONENT}_BOARDS -""" +''' # variable names in component extension code VAR_SCHEMA = "COMPONENT_SCHEMA" @@ -97,6 +124,19 @@ COMPONENT_MAP = { "ln882x": "lightning-ln882x", } +# Components with Cortex-M4(F) have LDREX/STREX for native atomic support. +# BK72xx uses ARM968E-S (ARMv5TE) which lacks these instructions. +COMPONENT_SUPPORTS_ATOMICS = { + "rtl87xx": True, # Cortex-M4 + "ln882x": True, # Cortex-M4F + "bk72xx": False, # ARM968E-S +} + +# CODEOWNERS for each component. If not specified, defaults to @kuba2k2. +COMPONENT_CODEOWNERS = { + "ln882x": ["@lamauny"], +} + def subst(code: str, key: str, value: str) -> str: return code.replace(f"{{{key}}}", value) @@ -140,6 +180,7 @@ def write_component_code( "boards": {"{COMPONENT}_BOARDS", "{COMPONENT}_BOARD_PINS"}, } # substitution values + codeowners = COMPONENT_CODEOWNERS.get(component, ["@kuba2k2"]) values = dict( COMPONENT=component.upper(), COMPONENT_LOWER=component.lower(), @@ -147,6 +188,8 @@ def write_component_code( PIN_SCHEMA=PIN_SCHEMA_BASE, PIN_VALIDATION="None", USAGE_VALIDATION="None", + SUPPORTS_ATOMICS=str(COMPONENT_SUPPORTS_ATOMICS.get(component, False)), + CODEOWNERS=repr(codeowners), ) # parse gpio.py file to find custom validators diff --git a/esphome/components/libretiny/gpio_arduino.cpp b/esphome/components/libretiny/gpio_arduino.cpp index 7a1e014ea4..0b14c77cf2 100644 --- a/esphome/components/libretiny/gpio_arduino.cpp +++ b/esphome/components/libretiny/gpio_arduino.cpp @@ -63,10 +63,8 @@ void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { pinMode(pin_, flags_to_mode(flags)); // NOLINT } -std::string ArduinoInternalGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u", pin_); - return buffer; +size_t ArduinoInternalGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u", this->pin_); } bool ArduinoInternalGPIOPin::digital_read() { diff --git a/esphome/components/libretiny/gpio_arduino.h b/esphome/components/libretiny/gpio_arduino.h index 3674748c18..30c7c33869 100644 --- a/esphome/components/libretiny/gpio_arduino.h +++ b/esphome/components/libretiny/gpio_arduino.h @@ -16,7 +16,7 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index 871b186d8e..68bc279767 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -4,24 +4,29 @@ #include "esphome/core/log.h" #include "esphome/core/preferences.h" #include +#include #include #include -#include namespace esphome { namespace libretiny { static const char *const TAG = "lt.preferences"; +// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding +static constexpr size_t KEY_BUFFER_SIZE = 12; + struct NVSData { - std::string key; + uint32_t key; std::unique_ptr data; size_t len; void set_data(const uint8_t *src, size_t size) { - data = std::make_unique(size); - memcpy(data.get(), src, size); - len = size; + if (!this->data || this->len != size) { + this->data = std::make_unique(size); + this->len = size; + } + memcpy(this->data.get(), src, size); } }; @@ -29,30 +34,30 @@ static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-n class LibreTinyPreferenceBackend : public ESPPreferenceBackend { public: - std::string key; + uint32_t key; fdb_kvdb_t db; fdb_blob_t blob; bool save(const uint8_t *data, size_t len) override { // try find in pending saves and update that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { obj.set_data(data, len); return true; } } NVSData save{}; - save.key = key; + save.key = this->key; save.set_data(data, len); s_pending_save.emplace_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); return true; } bool load(uint8_t *data, size_t len) override { // try find in pending saves and load from that for (auto &obj : s_pending_save) { - if (obj.key == key) { + if (obj.key == this->key) { if (obj.len != len) { // size mismatch return false; @@ -62,13 +67,15 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend { } } - fdb_blob_make(blob, data, len); - size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob); + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); + fdb_blob_make(this->blob, data, len); + size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob); if (actual_len != len) { ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); return false; } else { - ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key.c_str(), len); + ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len); } return true; } @@ -90,16 +97,14 @@ class LibreTinyPreferences : public ESPPreferences { } ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); + return this->make_preference(length, type); } ESPPreferenceObject make_preference(size_t length, uint32_t type) override { auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->db = &db; - pref->blob = &blob; - - uint32_t keyval = type; - pref->key = str_sprintf("%u", keyval); + pref->db = &this->db; + pref->blob = &this->blob; + pref->key = type; return ESPPreferenceObject(pref); } @@ -112,18 +117,20 @@ class LibreTinyPreferences : public ESPPreferences { // goal try write all pending saves even if one fails int cached = 0, written = 0, failed = 0; fdb_err_t last_err = FDB_NO_ERR; - std::string last_key{}; + uint32_t last_key = 0; // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str()); - if (is_changed(&db, save)) { - ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len); - fdb_blob_make(&blob, save.data.get(), save.len); - fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob); + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str); + if (this->is_changed_(&this->db, save, key_str)) { + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len); + fdb_blob_make(&this->blob, save.data.get(), save.len); + fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob); if (err != FDB_NO_ERR) { - ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", save.key.c_str(), save.len, err); + ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.len, err); failed++; last_err = err; last_key = save.key; @@ -131,7 +138,7 @@ class LibreTinyPreferences : public ESPPreferences { } written++; } else { - ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%zu", save.key.c_str(), save.len); + ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.len); cached++; } s_pending_save.erase(s_pending_save.begin() + i); @@ -139,17 +146,18 @@ class LibreTinyPreferences : public ESPPreferences { ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, failed); if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%s", failed, last_err, last_key.c_str()); + ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key); } return failed == 0; } - bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) { + protected: + bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str) { struct fdb_kv kv; - fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv); + fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv); if (kvp == nullptr) { - ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str()); + ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str); return true; } @@ -160,10 +168,10 @@ class LibreTinyPreferences : public ESPPreferences { // Allocate buffer on heap to avoid stack allocation for large data auto stored_data = std::make_unique(kv.value_len); - fdb_blob_make(&blob, stored_data.get(), kv.value_len); - size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob); + fdb_blob_make(&this->blob, stored_data.get(), kv.value_len); + size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob); if (actual_len != kv.value_len) { - ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len); + ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", key_str, actual_len, kv.value_len); return true; } diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.cpp b/esphome/components/libretiny_pwm/libretiny_pwm.cpp index 92e4097c0e..4e4a16d761 100644 --- a/esphome/components/libretiny_pwm/libretiny_pwm.cpp +++ b/esphome/components/libretiny_pwm/libretiny_pwm.cpp @@ -31,9 +31,11 @@ void LibreTinyPWM::setup() { } void LibreTinyPWM::dump_config() { - ESP_LOGCONFIG(TAG, "PWM Output:"); + ESP_LOGCONFIG(TAG, + "PWM Output:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); } void LibreTinyPWM::update_frequency(float frequency) { diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 8161e8b814..234d641f0d 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -1,4 +1,5 @@ #include + #include "light_call.h" #include "light_state.h" #include "esphome/core/log.h" @@ -153,15 +154,15 @@ void LightCall::perform() { } else if (this->has_effect_()) { // EFFECT - const char *effect_s; + StringRef effect_s; if (this->effect_ == 0u) { - effect_s = "None"; + effect_s = StringRef::from_lit("None"); } else { effect_s = this->parent_->effects_[this->effect_ - 1]->get_name(); } if (publish) { - ESP_LOGD(TAG, " Effect: '%s'", effect_s); + ESP_LOGD(TAG, " Effect: '%.*s'", (int) effect_s.size(), effect_s.c_str()); } this->parent_->start_effect_(this->effect_); @@ -511,11 +512,9 @@ LightCall &LightCall::set_effect(const char *effect, size_t len) { } bool found = false; + StringRef effect_ref(effect, len); for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) { - LightEffect *e = this->parent_->effects_[i]; - const char *name = e->get_name(); - - if (strncasecmp(effect, name, len) == 0 && name[len] == '\0') { + if (str_equals_case_insensitive(effect_ref, this->parent_->effects_[i]->get_name())) { this->set_effect(i + 1); found = true; break; diff --git a/esphome/components/light/light_color_values.cpp b/esphome/components/light/light_color_values.cpp new file mode 100644 index 0000000000..2f22bb3c68 --- /dev/null +++ b/esphome/components/light/light_color_values.cpp @@ -0,0 +1,28 @@ +#include "light_color_values.h" + +#include + +namespace esphome::light { + +LightColorValues LightColorValues::lerp(const LightColorValues &start, const LightColorValues &end, float completion) { + // Directly interpolate the raw values to avoid getter/setter overhead. + // This is safe because: + // - All LightColorValues have their values clamped when set via the setters + // - std::lerp guarantees output is in the same range as inputs + // - Therefore the output doesn't need clamping, so we can skip the setters + LightColorValues v; + v.color_mode_ = end.color_mode_; + v.state_ = std::lerp(start.state_, end.state_, completion); + v.brightness_ = std::lerp(start.brightness_, end.brightness_, completion); + v.color_brightness_ = std::lerp(start.color_brightness_, end.color_brightness_, completion); + v.red_ = std::lerp(start.red_, end.red_, completion); + v.green_ = std::lerp(start.green_, end.green_, completion); + v.blue_ = std::lerp(start.blue_, end.blue_, completion); + v.white_ = std::lerp(start.white_, end.white_, completion); + v.color_temperature_ = std::lerp(start.color_temperature_, end.color_temperature_, completion); + v.cold_white_ = std::lerp(start.cold_white_, end.cold_white_, completion); + v.warm_white_ = std::lerp(start.warm_white_, end.warm_white_, completion); + return v; +} + +} // namespace esphome::light diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index bedfad2c35..97756b9f26 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -82,26 +82,7 @@ class LightColorValues { * @param completion The completion value. 0 -> start, 1 -> end. * @return The linearly interpolated LightColorValues. */ - static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) { - // Directly interpolate the raw values to avoid getter/setter overhead. - // This is safe because: - // - All LightColorValues have their values clamped when set via the setters - // - std::lerp guarantees output is in the same range as inputs - // - Therefore the output doesn't need clamping, so we can skip the setters - LightColorValues v; - v.color_mode_ = end.color_mode_; - v.state_ = std::lerp(start.state_, end.state_, completion); - v.brightness_ = std::lerp(start.brightness_, end.brightness_, completion); - v.color_brightness_ = std::lerp(start.color_brightness_, end.color_brightness_, completion); - v.red_ = std::lerp(start.red_, end.red_, completion); - v.green_ = std::lerp(start.green_, end.green_, completion); - v.blue_ = std::lerp(start.blue_, end.blue_, completion); - v.white_ = std::lerp(start.white_, end.white_, completion); - v.color_temperature_ = std::lerp(start.color_temperature_, end.color_temperature_, completion); - v.cold_white_ = std::lerp(start.cold_white_, end.cold_white_, completion); - v.warm_white_ = std::lerp(start.warm_white_, end.warm_white_, completion); - return v; - } + static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion); /** Normalize the color (RGB/W) component. * diff --git a/esphome/components/light/light_effect.h b/esphome/components/light/light_effect.h index aa1f6f7899..a89e3fec5a 100644 --- a/esphome/components/light/light_effect.h +++ b/esphome/components/light/light_effect.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/string_ref.h" namespace esphome::light { @@ -23,9 +24,9 @@ class LightEffect { /** * Returns the name of this effect. - * The returned pointer is valid for the lifetime of the program and must not be freed. + * The underlying data is valid for the lifetime of the program (static string from codegen). */ - const char *get_name() const { return this->name_; } + StringRef get_name() const { return StringRef(this->name_); } /// Internal method called by the LightState when this light effect is registered in it. virtual void init() {} diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index 3365d1f417..f370980737 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -36,7 +36,7 @@ static const char *get_color_mode_json_str(ColorMode mode) { void LightJSONSchema::dump_json(LightState &state, JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson if (state.supports_effects()) { - root[ESPHOME_F("effect")] = state.get_effect_name(); + root[ESPHOME_F("effect")] = state.get_effect_name().c_str(); root[ESPHOME_F("effect_index")] = state.get_current_effect_index(); root[ESPHOME_F("effect_count")] = state.get_effect_count(); } @@ -160,7 +160,7 @@ void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject if (root[ESPHOME_F("effect")].is()) { const char *effect = root[ESPHOME_F("effect")]; - call.set_effect(effect); + call.set_effect(effect, strlen(effect)); } if (root[ESPHOME_F("effect_index")].is()) { diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 5a50bae50b..91bb2e2f1f 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -162,20 +162,12 @@ void LightState::publish_state() { LightOutput *LightState::get_output() const { return this->output_; } -static constexpr const char *EFFECT_NONE = "None"; static constexpr auto EFFECT_NONE_REF = StringRef::from_lit("None"); -std::string LightState::get_effect_name() { +StringRef LightState::get_effect_name() { if (this->active_effect_index_ > 0) { return this->effects_[this->active_effect_index_ - 1]->get_name(); } - return EFFECT_NONE; -} - -StringRef LightState::get_effect_name_ref() { - if (this->active_effect_index_ > 0) { - return StringRef(this->effects_[this->active_effect_index_ - 1]->get_name()); - } return EFFECT_NONE_REF; } diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index a21c2c7693..83b9226d03 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -140,9 +140,7 @@ class LightState : public EntityBase, public Component { LightOutput *get_output() const; /// Return the name of the current effect, or if no effect is active "None". - std::string get_effect_name(); - /// Return the name of the current effect as StringRef (for API usage) - StringRef get_effect_name_ref(); + StringRef get_effect_name(); /** Add a listener for remote values changes. * Listener is notified when the light's remote values change (state, brightness, color, etc.) @@ -191,11 +189,11 @@ class LightState : public EntityBase, public Component { /// Get effect index by name. Returns 0 if effect not found. uint32_t get_effect_index(const std::string &effect_name) const { - if (strcasecmp(effect_name.c_str(), "none") == 0) { + if (str_equals_case_insensitive(effect_name, "none")) { return 0; } for (size_t i = 0; i < this->effects_.size(); i++) { - if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name()) == 0) { + if (str_equals_case_insensitive(effect_name, this->effects_[i]->get_name())) { return i + 1; // Effects are 1-indexed in active_effect_index_ } } @@ -218,7 +216,7 @@ class LightState : public EntityBase, public Component { if (index > this->effects_.size()) { return ""; // Invalid index } - return this->effects_[index - 1]->get_name(); + return std::string(this->effects_[index - 1]->get_name()); } /// The result of all the current_values_as_* methods have gamma correction applied. diff --git a/esphome/components/ln882x/__init__.py b/esphome/components/ln882x/__init__.py index 6a76218f87..5c637bdf62 100644 --- a/esphome/components/ln882x/__init__.py +++ b/esphome/components/ln882x/__init__.py @@ -1,9 +1,23 @@ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. -# For custom pin validators, put validate_pin() or validate_usage() -# in gpio.py file in this directory. -# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA -# in schema.py file in this directory. +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. + +To customize this component: + - Pin validators: Create gpio.py with validate_pin() or validate_usage() + - Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA + +Platform-specific code should be added to the main libretiny component +(__init__.py in esphome/components/libretiny/) rather than here. +""" from esphome import pins from esphome.components import libretiny @@ -27,6 +41,7 @@ COMPONENT_DATA = LibreTinyComponent( board_pins=LN882X_BOARD_PINS, pin_validation=None, usage_validation=None, + supports_atomics=True, ) diff --git a/esphome/components/ln882x/boards.py b/esphome/components/ln882x/boards.py index 43f25994a7..600371951d 100644 --- a/esphome/components/ln882x/boards.py +++ b/esphome/components/ln882x/boards.py @@ -1,9 +1,28 @@ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. +""" from esphome.components.libretiny.const import FAMILY_LN882H LN882X_BOARDS = { + "generic-ln882hki": { + "name": "Generic - LN882HKI", + "family": FAMILY_LN882H, + }, + "wb02a": { + "name": "WB02A Wi-Fi/BLE Module", + "family": FAMILY_LN882H, + }, "wl2s": { "name": "WL2S Wi-Fi/BLE Module", "family": FAMILY_LN882H, @@ -12,13 +31,195 @@ LN882X_BOARDS = { "name": "LN-02 Wi-Fi/BLE Module", "family": FAMILY_LN882H, }, - "generic-ln882hki": { - "name": "Generic - LN882HKI", - "family": FAMILY_LN882H, - }, } LN882X_BOARD_PINS = { + "generic-ln882hki": { + "WIRE0_SCL_0": 0, + "WIRE0_SCL_1": 1, + "WIRE0_SCL_2": 2, + "WIRE0_SCL_3": 3, + "WIRE0_SCL_4": 4, + "WIRE0_SCL_5": 5, + "WIRE0_SCL_6": 6, + "WIRE0_SCL_7": 7, + "WIRE0_SCL_8": 8, + "WIRE0_SCL_9": 9, + "WIRE0_SCL_10": 10, + "WIRE0_SCL_11": 11, + "WIRE0_SCL_12": 12, + "WIRE0_SCL_13": 19, + "WIRE0_SCL_14": 20, + "WIRE0_SCL_15": 21, + "WIRE0_SCL_16": 22, + "WIRE0_SCL_17": 23, + "WIRE0_SCL_18": 24, + "WIRE0_SCL_19": 25, + "WIRE0_SDA_0": 0, + "WIRE0_SDA_1": 1, + "WIRE0_SDA_2": 2, + "WIRE0_SDA_3": 3, + "WIRE0_SDA_4": 4, + "WIRE0_SDA_5": 5, + "WIRE0_SDA_6": 6, + "WIRE0_SDA_7": 7, + "WIRE0_SDA_8": 8, + "WIRE0_SDA_9": 9, + "WIRE0_SDA_10": 10, + "WIRE0_SDA_11": 11, + "WIRE0_SDA_12": 12, + "WIRE0_SDA_13": 19, + "WIRE0_SDA_14": 20, + "WIRE0_SDA_15": 21, + "WIRE0_SDA_16": 22, + "WIRE0_SDA_17": 23, + "WIRE0_SDA_18": 24, + "WIRE0_SDA_19": 25, + "SERIAL0_RX": 3, + "SERIAL0_TX": 2, + "SERIAL1_RX": 24, + "SERIAL1_TX": 25, + "ADC2": 0, + "ADC3": 1, + "ADC4": 4, + "ADC5": 19, + "ADC6": 20, + "ADC7": 21, + "PA00": 0, + "PA0": 0, + "PA01": 1, + "PA1": 1, + "PA02": 2, + "PA2": 2, + "PA03": 3, + "PA3": 3, + "PA04": 4, + "PA4": 4, + "PA05": 5, + "PA5": 5, + "PA06": 6, + "PA6": 6, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PB03": 19, + "PB3": 19, + "PB04": 20, + "PB4": 20, + "PB05": 21, + "PB5": 21, + "PB06": 22, + "PB6": 22, + "PB07": 23, + "PB7": 23, + "PB08": 24, + "PB8": 24, + "PB09": 25, + "PB9": 25, + "RX0": 3, + "RX1": 24, + "TX0": 2, + "TX1": 25, + "D0": 0, + "D1": 1, + "D2": 2, + "D3": 3, + "D4": 4, + "D5": 5, + "D6": 6, + "D7": 7, + "D8": 8, + "D9": 9, + "D10": 10, + "D11": 11, + "D12": 12, + "D13": 19, + "D14": 20, + "D15": 21, + "D16": 22, + "D17": 23, + "D18": 24, + "D19": 25, + "A2": 0, + "A3": 1, + "A4": 4, + "A5": 19, + "A6": 20, + "A7": 21, + }, + "wb02a": { + "WIRE0_SCL_0": 7, + "WIRE0_SCL_1": 5, + "WIRE0_SCL_2": 3, + "WIRE0_SCL_3": 10, + "WIRE0_SCL_4": 2, + "WIRE0_SCL_5": 1, + "WIRE0_SCL_6": 4, + "WIRE0_SCL_7": 5, + "WIRE0_SCL_8": 9, + "WIRE0_SCL_9": 24, + "WIRE0_SCL_10": 25, + "WIRE0_SDA_0": 7, + "WIRE0_SDA_1": 5, + "WIRE0_SDA_2": 3, + "WIRE0_SDA_3": 10, + "WIRE0_SDA_4": 2, + "WIRE0_SDA_5": 1, + "WIRE0_SDA_6": 4, + "WIRE0_SDA_7": 5, + "WIRE0_SDA_8": 9, + "WIRE0_SDA_9": 24, + "WIRE0_SDA_10": 25, + "SERIAL0_RX": 3, + "SERIAL0_TX": 2, + "SERIAL1_RX": 24, + "SERIAL1_TX": 25, + "ADC3": 1, + "ADC4": 4, + "PA01": 1, + "PA1": 1, + "PA02": 2, + "PA2": 2, + "PA03": 3, + "PA3": 3, + "PA04": 4, + "PA4": 4, + "PA05": 5, + "PA5": 5, + "PA07": 7, + "PA7": 7, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PB08": 24, + "PB8": 24, + "PB09": 25, + "PB9": 25, + "RX0": 3, + "RX1": 24, + "SCL0": 25, + "SDA0": 25, + "TX0": 2, + "TX1": 25, + "D0": 7, + "D1": 5, + "D2": 3, + "D3": 10, + "D4": 2, + "D5": 1, + "D6": 4, + "D7": 9, + "D8": 24, + "D9": 25, + "A0": 1, + "A1": 4, + }, "wl2s": { "WIRE0_SCL_0": 7, "WIRE0_SCL_1": 12, @@ -161,125 +362,6 @@ LN882X_BOARD_PINS = { "A1": 1, "A2": 0, }, - "generic-ln882hki": { - "WIRE0_SCL_0": 0, - "WIRE0_SCL_1": 1, - "WIRE0_SCL_2": 2, - "WIRE0_SCL_3": 3, - "WIRE0_SCL_4": 4, - "WIRE0_SCL_5": 5, - "WIRE0_SCL_6": 6, - "WIRE0_SCL_7": 7, - "WIRE0_SCL_8": 8, - "WIRE0_SCL_9": 9, - "WIRE0_SCL_10": 10, - "WIRE0_SCL_11": 11, - "WIRE0_SCL_12": 12, - "WIRE0_SCL_13": 19, - "WIRE0_SCL_14": 20, - "WIRE0_SCL_15": 21, - "WIRE0_SCL_16": 22, - "WIRE0_SCL_17": 23, - "WIRE0_SCL_18": 24, - "WIRE0_SCL_19": 25, - "WIRE0_SDA_0": 0, - "WIRE0_SDA_1": 1, - "WIRE0_SDA_2": 2, - "WIRE0_SDA_3": 3, - "WIRE0_SDA_4": 4, - "WIRE0_SDA_5": 5, - "WIRE0_SDA_6": 6, - "WIRE0_SDA_7": 7, - "WIRE0_SDA_8": 8, - "WIRE0_SDA_9": 9, - "WIRE0_SDA_10": 10, - "WIRE0_SDA_11": 11, - "WIRE0_SDA_12": 12, - "WIRE0_SDA_13": 19, - "WIRE0_SDA_14": 20, - "WIRE0_SDA_15": 21, - "WIRE0_SDA_16": 22, - "WIRE0_SDA_17": 23, - "WIRE0_SDA_18": 24, - "WIRE0_SDA_19": 25, - "SERIAL0_RX": 3, - "SERIAL0_TX": 2, - "SERIAL1_RX": 24, - "SERIAL1_TX": 25, - "ADC2": 0, - "ADC3": 1, - "ADC4": 4, - "ADC5": 19, - "ADC6": 20, - "ADC7": 21, - "PA00": 0, - "PA0": 0, - "PA01": 1, - "PA1": 1, - "PA02": 2, - "PA2": 2, - "PA03": 3, - "PA3": 3, - "PA04": 4, - "PA4": 4, - "PA05": 5, - "PA5": 5, - "PA06": 6, - "PA6": 6, - "PA07": 7, - "PA7": 7, - "PA08": 8, - "PA8": 8, - "PA09": 9, - "PA9": 9, - "PA10": 10, - "PA11": 11, - "PA12": 12, - "PB03": 19, - "PB3": 19, - "PB04": 20, - "PB4": 20, - "PB05": 21, - "PB5": 21, - "PB06": 22, - "PB6": 22, - "PB07": 23, - "PB7": 23, - "PB08": 24, - "PB8": 24, - "PB09": 25, - "PB9": 25, - "RX0": 3, - "RX1": 24, - "TX0": 2, - "TX1": 25, - "D0": 0, - "D1": 1, - "D2": 2, - "D3": 3, - "D4": 4, - "D5": 5, - "D6": 6, - "D7": 7, - "D8": 8, - "D9": 9, - "D10": 10, - "D11": 11, - "D12": 12, - "D13": 19, - "D14": 20, - "D15": 21, - "D16": 22, - "D17": 23, - "D18": 24, - "D19": 25, - "A2": 0, - "A3": 1, - "A4": 4, - "A5": 19, - "A6": 20, - "A7": 21, - }, } BOARDS = LN882X_BOARDS diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index 4001a182b8..f77b11b145 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -174,7 +174,7 @@ class Lock : public EntityBase { */ virtual void control(const LockCall &call) = 0; - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; Deduplicator publish_dedup_; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index fb0ce92cc9..cadd0a14ae 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -226,8 +226,13 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault( CONF_TASK_LOG_BUFFER_SIZE, esp32=768, # Default: 768 bytes (~5-6 messages with 70-byte text plus thread names) + bk72xx=768, + ln882x=768, + rtl87xx=768, ): cv.All( - cv.only_on_esp32, + cv.only_on( + [PLATFORM_ESP32, PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX] + ), cv.validate_bytes, cv.Any( cv.int_(0), # Disabled @@ -241,9 +246,12 @@ CONFIG_SCHEMA = cv.All( CONF_HARDWARE_UART, esp8266=UART0, esp32=UART0, + esp32_c2=UART0, esp32_c3=USB_SERIAL_JTAG, esp32_c5=USB_SERIAL_JTAG, esp32_c6=USB_SERIAL_JTAG, + esp32_c61=USB_SERIAL_JTAG, + esp32_h2=USB_SERIAL_JTAG, esp32_p4=USB_SERIAL_JTAG, esp32_s2=USB_CDC, esp32_s3=USB_SERIAL_JTAG, @@ -303,10 +311,15 @@ async def to_code(config): ) if CORE.is_esp32: cg.add(log.create_pthread_key()) + if CORE.is_esp32 or CORE.is_libretiny: task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE] if task_log_buffer_size > 0: cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") cg.add(log.init_log_buffer(task_log_buffer_size)) + elif CORE.is_host: + cg.add(log.create_pthread_key()) + cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER") + cg.add(log.init_log_buffer(64)) # Fixed 64 slots for host cg.add(log.set_log_level(initial_level)) if CONF_HARDWARE_UART in config: @@ -334,6 +347,18 @@ async def to_code(config): is_at_least_very_verbose = this_severity >= very_verbose_severity has_serial_logging = baud_rate != 0 + # Add defines for which Serial object is needed (allows linker to exclude unused) + if CORE.is_esp8266: + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + hw_uart = config.get(CONF_HARDWARE_UART, UART0) + if has_serial_logging and hw_uart in (UART0, UART0_SWAP): + cg.add_define("USE_ESP8266_LOGGER_SERIAL") + enable_serial() + elif has_serial_logging and hw_uart == UART1: + cg.add_define("USE_ESP8266_LOGGER_SERIAL1") + enable_serial1() + if ( (CORE.is_esp8266 or CORE.is_rp2040) and has_serial_logging @@ -383,7 +408,7 @@ async def to_code(config): except cv.Invalid: pass - if CORE.using_zephyr: + if CORE.is_nrf52: if config[CONF_HARDWARE_UART] == UART0: zephyr_add_overlay("""&uart0 { status = "okay";};""") if config[CONF_HARDWARE_UART] == UART1: @@ -396,6 +421,7 @@ async def to_code(config): await cg.register_component(log, config) for conf in config.get(CONF_ON_MESSAGE, []): + request_log_listener() # Each on_message trigger needs a listener slot trigger = cg.new_Pvariable( conf[CONF_TRIGGER_ID], log, LOG_LEVEL_SEVERITY.index(conf[CONF_LEVEL]) ) @@ -505,16 +531,23 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.LN882X_ARDUINO, }, "logger_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR}, - "task_log_buffer.cpp": { + "task_log_buffer_esp32.cpp": { PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, }, + "task_log_buffer_host.cpp": {PlatformFramework.HOST_NATIVE}, + "task_log_buffer_libretiny.cpp": { + PlatformFramework.BK72XX_ARDUINO, + PlatformFramework.RTL87XX_ARDUINO, + PlatformFramework.LN882X_ARDUINO, + }, } ) # Keys for CORE.data storage DOMAIN = "logger" KEY_LEVEL_LISTENERS = "level_listeners" +KEY_LOG_LISTENERS = "log_listeners" def request_logger_level_listeners() -> None: @@ -527,8 +560,26 @@ def request_logger_level_listeners() -> None: CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True +def request_log_listener() -> None: + """Request a log listener slot. + + Components that need to receive log messages should call this function + during their code generation. This increments the listener count used + to size the StaticVector. + """ + data = CORE.data.setdefault(DOMAIN, {}) + data[KEY_LOG_LISTENERS] = data.get(KEY_LOG_LISTENERS, 0) + 1 + + @coroutine_with_priority(CoroPriority.FINAL) async def final_step(): """Final code generation step to configure optional logger features.""" - if CORE.data.get(DOMAIN, {}).get(KEY_LEVEL_LISTENERS, False): + domain_data = CORE.data.get(DOMAIN, {}) + if domain_data.get(KEY_LEVEL_LISTENERS, False): cg.add_define("USE_LOGGER_LEVEL_LISTENERS") + + # Only generate log listener code if any component needs it + log_listener_count = domain_data.get(KEY_LOG_LISTENERS, 0) + if log_listener_count > 0: + cg.add_define("USE_LOG_LISTENERS") + cg.add_define("ESPHOME_LOG_MAX_LISTENERS", log_listener_count) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 21e2b44808..34430dbafa 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -12,41 +12,77 @@ namespace esphome::logger { static const char *const TAG = "logger"; -#ifdef USE_ESP32 -// Implementation for ESP32 (multi-task platform with task-specific tracking) -// Main task always uses direct buffer access for console output and callbacks +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) +// Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS) +// Main thread/task always uses direct buffer access for console output and callbacks // -// For non-main tasks: +// For non-main threads/tasks: // - WITH task log buffer: Prefer sending to ring buffer for async processing // - Avoids allocating stack memory for console output in normal operation -// - Prevents console corruption from concurrent writes by multiple tasks +// - Prevents console corruption from concurrent writes by multiple threads // - Messages are serialized through main loop for proper console output // - Fallback to emergency console logging only if ring buffer is full // - WITHOUT task log buffer: Only emergency console output, no callbacks +// +// Optimized for the common case: 99.9% of logs come from the main thread void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT if (level > this->level_for(tag)) return; +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + // Get task handle once - used for both main task check and passing to non-main thread handler TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); - bool is_main_task = (current_task == main_task_); + const bool is_main_task = (current_task == this->main_task_); +#else // USE_HOST + const bool is_main_task = pthread_equal(pthread_self(), this->main_thread_); +#endif - // Check and set recursion guard - uses pthread TLS for per-task state - if (this->check_and_set_task_log_recursion_(is_main_task)) { - return; // Recursion detected - } - - // Main task uses the shared buffer for efficiency - if (is_main_task) { + // Fast path: main thread, no recursion (99.9% of all logs) + if (is_main_task && !this->main_task_recursion_guard_) [[likely]] { + RecursionGuard guard(this->main_task_recursion_guard_); + // Format and send to both console and callbacks this->log_message_to_buffer_and_send_(level, tag, line, format, args); - this->reset_task_log_recursion_(is_main_task); return; } + // Main task with recursion - silently drop to prevent infinite loop + if (is_main_task) { + return; + } + + // Non-main thread handling (~0.1% of logs) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + this->log_vprintf_non_main_thread_(level, tag, line, format, args, current_task); +#else // USE_HOST + this->log_vprintf_non_main_thread_(level, tag, line, format, args); +#endif +} + +// Handles non-main thread logging only +// Kept separate from hot path to improve instruction cache performance +#if defined(USE_ESP32) || defined(USE_LIBRETINY) +void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, + TaskHandle_t current_task) { +#else // USE_HOST +void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) { +#endif + // Check if already in recursion for this non-main thread/task + if (this->is_non_main_task_recursive_()) { + return; + } + + // RAII guard - automatically resets on any return path + auto guard = this->make_non_main_task_guard_(); + bool message_sent = false; #ifdef USE_ESPHOME_TASK_LOG_BUFFER - // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered + // For non-main threads/tasks, queue the message for callbacks +#if defined(USE_ESP32) || defined(USE_LIBRETINY) message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), current_task, format, args); +#else // USE_HOST + message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), format, args); +#endif if (message_sent) { // Enable logger loop to process the buffered message // This is safe to call from any context including ISRs @@ -54,39 +90,41 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch } #endif // USE_ESPHOME_TASK_LOG_BUFFER - // Emergency console logging for non-main tasks when ring buffer is full or disabled + // Emergency console logging for non-main threads when ring buffer is full or disabled // This is a fallback mechanism to ensure critical log messages are visible - // Note: This may cause interleaved/corrupted console output if multiple tasks + // Note: This may cause interleaved/corrupted console output if multiple threads // log simultaneously, but it's better than losing important messages entirely +#ifdef USE_HOST + if (!message_sent) { + // Host always has console output - no baud_rate check needed + static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 512; +#else if (!message_sent && this->baud_rate_ > 0) { // If logging is enabled, write to console // Maximum size for console log messages (includes null terminator) static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; +#endif char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety uint16_t buffer_at = 0; // Initialize buffer position this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); - // Add newline if platform needs it (ESP32 doesn't add via write_msg_) - this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); + // Add newline before writing to console + this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); this->write_msg_(console_buffer, buffer_at); } - // Reset the recursion guard for this task - this->reset_task_log_recursion_(is_main_task); + // RAII guard automatically resets on return } #else -// Implementation for all other platforms +// Implementation for all other platforms (single-task, no threading) void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT if (level > this->level_for(tag) || global_recursion_guard_) return; - global_recursion_guard_ = true; - + RecursionGuard guard(global_recursion_guard_); // Format and send to both console and callbacks this->log_message_to_buffer_and_send_(level, tag, line, format, args); - - global_recursion_guard_ = false; } -#endif // !USE_ESP32 +#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. @@ -115,7 +153,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas if (level > this->level_for(tag) || global_recursion_guard_) return; - global_recursion_guard_ = true; + RecursionGuard guard(global_recursion_guard_); this->tx_buffer_at_ = 0; // Copy format string from progmem @@ -125,9 +163,8 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++); } - // Buffer full from copying format + // Buffer full from copying format - RAII guard handles cleanup on return if (this->tx_buffer_at_ >= this->tx_buffer_size_) { - global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning return; } @@ -141,13 +178,13 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position // Listeners get message first (before console write) +#ifdef USE_LOG_LISTENERS for (auto *listener : this->log_listeners_) listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length); +#endif // Write to console starting at the msg_start this->write_tx_buffer_to_console_(msg_start, &msg_length); - - global_recursion_guard_ = false; } #endif // USE_STORE_LOG_STR_IN_FLASH @@ -167,15 +204,26 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate this->main_task_ = xTaskGetCurrentTaskHandle(); #elif defined(USE_ZEPHYR) this->main_task_ = k_current_get(); +#elif defined(USE_HOST) + this->main_thread_ = pthread_self(); #endif } #ifdef USE_ESPHOME_TASK_LOG_BUFFER void Logger::init_log_buffer(size_t total_buffer_size) { +#ifdef USE_HOST + // Host uses slot count instead of byte size + this->log_buffer_ = esphome::make_unique(total_buffer_size); +#elif defined(USE_ESP32) this->log_buffer_ = esphome::make_unique(total_buffer_size); +#elif defined(USE_LIBRETINY) + this->log_buffer_ = esphome::make_unique(total_buffer_size); +#endif - // Start with loop disabled when using task buffer (unless using USB CDC) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + // Start with loop disabled when using task buffer (unless using USB CDC on ESP32) // The loop will be enabled automatically when messages arrive this->disable_loop_when_buffer_empty_(); +#endif } #endif @@ -187,41 +235,48 @@ void Logger::process_messages_() { #ifdef USE_ESPHOME_TASK_LOG_BUFFER // Process any buffered messages when available if (this->log_buffer_->has_messages()) { +#ifdef USE_HOST + logger::TaskLogBufferHost::LogMessage *message; + while (this->log_buffer_->get_message_main_loop(&message)) { + const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, message->text, + message->text_length); + this->log_buffer_->release_message_main_loop(); + this->write_tx_buffer_to_console_(); + } +#elif defined(USE_ESP32) logger::TaskLogBuffer::LogMessage *message; const char *text; void *received_token; - - // Process messages from the buffer while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) { - this->tx_buffer_at_ = 0; - // Use the thread name that was stored when the message was created - // This avoids potential crashes if the task no longer exists const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; - this->write_header_to_buffer_(message->level, message->tag, message->line, thread_name, this->tx_buffer_, - &this->tx_buffer_at_, this->tx_buffer_size_); - this->write_body_to_buffer_(text, message->text_length, this->tx_buffer_, &this->tx_buffer_at_, - this->tx_buffer_size_); - this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); - this->tx_buffer_[this->tx_buffer_at_] = '\0'; - size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_ - for (auto *listener : this->log_listeners_) - listener->on_log(message->level, message->tag, this->tx_buffer_, msg_len); - // At this point all the data we need from message has been transferred to the tx_buffer - // so we can release the message to allow other tasks to use it as soon as possible. + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, + message->text_length); + // Release the message to allow other tasks to use it as soon as possible this->log_buffer_->release_message_main_loop(received_token); - - // Write to console from the main loop to prevent corruption from concurrent writes - // This ensures all log messages appear on the console in a clean, serialized manner - // Note: Messages may appear slightly out of order due to async processing, but - // this is preferred over corrupted/interleaved console output this->write_tx_buffer_to_console_(); } - } else { +#elif defined(USE_LIBRETINY) + logger::TaskLogBufferLibreTiny::LogMessage *message; + const char *text; + while (this->log_buffer_->borrow_message_main_loop(&message, &text)) { + const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr; + this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name, text, + message->text_length); + // Release the message to allow other tasks to use it as soon as possible + this->log_buffer_->release_message_main_loop(); + this->write_tx_buffer_to_console_(); + } +#endif + } +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + else { // No messages to process, disable loop if appropriate // This reduces overhead when there's no async logging activity this->disable_loop_when_buffer_empty_(); } #endif +#endif // USE_ESPHOME_TASK_LOG_BUFFER } void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } @@ -271,7 +326,11 @@ void Logger::dump_config() { #endif #ifdef USE_ESPHOME_TASK_LOG_BUFFER if (this->log_buffer_) { - ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u", this->log_buffer_->size()); +#ifdef USE_HOST + ESP_LOGCONFIG(TAG, " Task Log Buffer Slots: %u", static_cast(this->log_buffer_->size())); +#else + ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast(this->log_buffer_->size())); +#endif } #endif diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 8abc1196e1..3e8538c2ae 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -2,7 +2,7 @@ #include #include -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_HOST) #include #endif #include "esphome/core/automation.h" @@ -12,7 +12,13 @@ #include "esphome/core/log.h" #ifdef USE_ESPHOME_TASK_LOG_BUFFER -#include "task_log_buffer.h" +#ifdef USE_HOST +#include "task_log_buffer_host.h" +#elif defined(USE_ESP32) +#include "task_log_buffer_esp32.h" +#elif defined(USE_LIBRETINY) +#include "task_log_buffer_libretiny.h" +#endif #endif #ifdef USE_ARDUINO @@ -117,17 +123,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128; // "0x" + 2 hex digits per byte + '\0' static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1; -// Platform-specific: does write_msg_ add its own newline? -// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, LibreTiny) -// Allows single write call with newline included for efficiency -// true: write_msg_ adds newline itself via puts()/println() (other platforms) -// Newline should NOT be added to buffer -#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_LIBRETINY) -static constexpr bool WRITE_MSG_ADDS_NEWLINE = false; -#else -static constexpr bool WRITE_MSG_ADDS_NEWLINE = true; -#endif - #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) /** Enum for logging UART selection * @@ -192,6 +187,9 @@ class Logger : public Component { uart_port_t get_uart_num() const { return uart_num_; } void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); } #endif +#ifdef USE_HOST + void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); } +#endif #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } /// Get the UART used by the logger. @@ -214,8 +212,13 @@ class Logger : public Component { inline uint8_t level_for(const char *tag); +#ifdef USE_LOG_LISTENERS /// Register a log listener to receive log messages void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); } +#else + /// No-op when log listeners are disabled + void add_log_listener(LogListener *listener) {} +#endif #ifdef USE_LOGGER_LEVEL_LISTENERS /// Register a listener for log level changes @@ -231,6 +234,31 @@ class Logger : public Component { #endif protected: + // RAII guard for recursion flags - sets flag on construction, clears on destruction + class RecursionGuard { + public: + explicit RecursionGuard(bool &flag) : flag_(flag) { flag_ = true; } + ~RecursionGuard() { flag_ = false; } + RecursionGuard(const RecursionGuard &) = delete; + RecursionGuard &operator=(const RecursionGuard &) = delete; + RecursionGuard(RecursionGuard &&) = delete; + RecursionGuard &operator=(RecursionGuard &&) = delete; + + private: + bool &flag_; + }; + +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) + // Handles non-main thread logging only (~0.1% of calls) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + // ESP32/LibreTiny: Pass task handle to avoid calling xTaskGetCurrentTaskHandle() twice + void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args, + TaskHandle_t current_task); +#else // USE_HOST + // Host: No task handle parameter needed (not used in send_message_thread_safe) + void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args); +#endif +#endif void process_messages_(); void write_msg_(const char *msg, size_t len); @@ -239,7 +267,7 @@ class Logger : public Component { inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, va_list args, char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_HOST) this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size); #elif defined(USE_ZEPHYR) char buff[MAX_POINTER_REPRESENTATION]; @@ -259,22 +287,20 @@ class Logger : public Component { } } - // Helper to add newline to buffer for platforms that need it + // Helper to add newline to buffer before writing to console // Modifies buffer_at to include the newline - inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { - if constexpr (!WRITE_MSG_ADDS_NEWLINE) { - // Add newline - don't need to maintain null termination - // write_msg_ now always receives explicit length, so we can safely overwrite the null terminator - // This is safe because: - // 1. Callbacks already received the message (before we add newline) - // 2. write_msg_ receives the length explicitly (doesn't need null terminator) - if (*buffer_at < buffer_size) { - buffer[(*buffer_at)++] = '\n'; - } else if (buffer_size > 0) { - // Buffer was full - replace last char with newline to ensure it's visible - buffer[buffer_size - 1] = '\n'; - *buffer_at = buffer_size; - } + inline void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { + // Add newline - don't need to maintain null termination + // write_msg_ receives explicit length, so we can safely overwrite the null terminator + // This is safe because: + // 1. Callbacks already received the message (before we add newline) + // 2. write_msg_ receives the length explicitly (doesn't need null terminator) + if (*buffer_at < buffer_size) { + buffer[(*buffer_at)++] = '\n'; + } else if (buffer_size > 0) { + // Buffer was full - replace last char with newline to ensure it's visible + buffer[buffer_size - 1] = '\n'; + *buffer_at = buffer_size; } } @@ -283,7 +309,7 @@ class Logger : public Component { inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) { if (this->baud_rate_ > 0) { uint16_t *len_ptr = length ? length : &this->tx_buffer_at_; - this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); + this->add_newline_to_buffer_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset); this->write_msg_(this->tx_buffer_ + offset, *len_ptr); } } @@ -297,13 +323,33 @@ class Logger : public Component { this->tx_buffer_size_); // Listeners get message WITHOUT newline (for API/MQTT/syslog) +#ifdef USE_LOG_LISTENERS for (auto *listener : this->log_listeners_) listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); +#endif // Console gets message WITH newline (if platform needs it) this->write_tx_buffer_to_console_(); } +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + // Helper to format a pre-formatted message from the task log buffer and notify listeners + // Used by process_messages_ to avoid code duplication between ESP32 and host platforms + inline void HOT format_buffered_message_and_notify_(uint8_t level, const char *tag, uint16_t line, + const char *thread_name, const char *text, size_t text_length) { + this->tx_buffer_at_ = 0; + this->write_header_to_buffer_(level, tag, line, thread_name, this->tx_buffer_, &this->tx_buffer_at_, + this->tx_buffer_size_); + this->write_body_to_buffer_(text, text_length, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); + this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); + this->tx_buffer_[this->tx_buffer_at_] = '\0'; +#ifdef USE_LOG_LISTENERS + for (auto *listener : this->log_listeners_) + listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); +#endif + } +#endif + // Write the body of the log message to the buffer inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { @@ -336,7 +382,10 @@ class Logger : public Component { const device *uart_dev_{nullptr}; #endif #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) - void *main_task_ = nullptr; // Only used for thread name identification + void *main_task_{nullptr}; // Main thread/task for fast path comparison +#endif +#ifdef USE_HOST + pthread_t main_thread_{}; // Main thread for pthread_equal() comparison #endif #ifdef USE_ESP32 // Task-specific recursion guards: @@ -345,17 +394,30 @@ class Logger : public Component { pthread_key_t log_recursion_key_; // 4 bytes uart_port_t uart_num_; // 4 bytes (enum defaults to int size) #endif +#ifdef USE_HOST + // Thread-specific recursion guards using pthread TLS + pthread_key_t log_recursion_key_; +#endif // Large objects (internally aligned) #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS std::map log_levels_{}; #endif - std::vector log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) +#ifdef USE_LOG_LISTENERS + StaticVector + log_listeners_; // Log message listeners (API, MQTT, syslog, etc.) +#endif #ifdef USE_LOGGER_LEVEL_LISTENERS std::vector level_listeners_; // Log level change listeners #endif #ifdef USE_ESPHOME_TASK_LOG_BUFFER +#ifdef USE_HOST + std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer +#elif defined(USE_ESP32) std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer +#elif defined(USE_LIBRETINY) + std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer +#endif #endif // Group smaller types together at the end @@ -368,8 +430,11 @@ class Logger : public Component { #ifdef USE_LIBRETINY UARTSelection uart_{UART_SELECTION_DEFAULT}; #endif -#ifdef USE_ESP32 +#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) bool main_task_recursion_guard_{false}; +#ifdef USE_LIBRETINY + bool non_main_task_recursion_guard_{false}; // Shared guard for all non-main tasks on LibreTiny +#endif #else bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms #endif @@ -405,29 +470,58 @@ class Logger : public Component { } #endif -#ifdef USE_ESP32 - inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) { - if (is_main_task) { - const bool was_recursive = main_task_recursion_guard_; - main_task_recursion_guard_ = true; - return was_recursive; +#if defined(USE_ESP32) || defined(USE_HOST) + // RAII guard for non-main task recursion using pthread TLS + class NonMainTaskRecursionGuard { + public: + explicit NonMainTaskRecursionGuard(pthread_key_t key) : key_(key) { + pthread_setspecific(key_, reinterpret_cast(1)); } + ~NonMainTaskRecursionGuard() { pthread_setspecific(key_, nullptr); } + NonMainTaskRecursionGuard(const NonMainTaskRecursionGuard &) = delete; + NonMainTaskRecursionGuard &operator=(const NonMainTaskRecursionGuard &) = delete; + NonMainTaskRecursionGuard(NonMainTaskRecursionGuard &&) = delete; + NonMainTaskRecursionGuard &operator=(NonMainTaskRecursionGuard &&) = delete; - intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_); - if (current != 0) - return true; + private: + pthread_key_t key_; + }; - pthread_setspecific(log_recursion_key_, (void *) 1); - return false; - } + // Check if non-main task is already in recursion (via TLS) + inline bool HOT is_non_main_task_recursive_() const { return pthread_getspecific(log_recursion_key_) != nullptr; } - inline void HOT reset_task_log_recursion_(bool is_main_task) { - if (is_main_task) { - main_task_recursion_guard_ = false; - return; + // Create RAII guard for non-main task recursion + inline NonMainTaskRecursionGuard make_non_main_task_guard_() { return NonMainTaskRecursionGuard(log_recursion_key_); } + +#elif defined(USE_LIBRETINY) + // LibreTiny doesn't have FreeRTOS TLS, so use a simple approach: + // - Main task uses dedicated boolean (same as ESP32) + // - Non-main tasks share a single recursion guard + // This is safe because: + // - Recursion from logging within logging is the main concern + // - Cross-task "recursion" is prevented by the buffer mutex anyway + // - Missing a recursive call from another task is acceptable (falls back to direct output) + + // Check if non-main task is already in recursion + inline bool HOT is_non_main_task_recursive_() const { return non_main_task_recursion_guard_; } + + // Create RAII guard for non-main task recursion (uses shared boolean for all non-main tasks) + inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); } +#endif + +#ifdef USE_HOST + const char *HOT get_thread_name_() { + pthread_t current_thread = pthread_self(); + if (pthread_equal(current_thread, main_thread_)) { + return nullptr; // Main thread } - - pthread_setspecific(log_recursion_key_, (void *) 0); + // For non-main threads, return the thread name + // We store it in thread-local storage to avoid allocation + static thread_local char thread_name_buf[32]; + if (pthread_getname_np(current_thread, thread_name_buf, sizeof(thread_name_buf)) == 0) { + return thread_name_buf; + } + return nullptr; } #endif @@ -488,7 +582,7 @@ class Logger : public Component { buffer[pos++] = '0' + (remainder - tens * 10); buffer[pos++] = ']'; -#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) || defined(USE_HOST) if (thread_name != nullptr) { write_ansi_color_for_level(buffer, pos, 1); // Always use bold red for thread name buffer[pos++] = '['; @@ -533,8 +627,8 @@ class Logger : public Component { this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); } -#ifdef USE_ESP32 - // Disable loop when task buffer is empty (with USB CDC check) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + // Disable loop when task buffer is empty (with USB CDC check on ESP32) inline void disable_loop_when_buffer_empty_() { // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() // concurrently. If that happens between our check and disable_loop(), the enable request diff --git a/esphome/components/logger/logger_esp8266.cpp b/esphome/components/logger/logger_esp8266.cpp index 0fc73b747a..6cee1baca5 100644 --- a/esphome/components/logger/logger_esp8266.cpp +++ b/esphome/components/logger/logger_esp8266.cpp @@ -7,26 +7,21 @@ namespace esphome::logger { static const char *const TAG = "logger"; void Logger::pre_setup() { - if (this->baud_rate_ > 0) { - switch (this->uart_) { - case UART_SELECTION_UART0: - case UART_SELECTION_UART0_SWAP: - this->hw_serial_ = &Serial; - Serial.begin(this->baud_rate_); - if (this->uart_ == UART_SELECTION_UART0_SWAP) { - Serial.swap(); - } - Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); - break; - case UART_SELECTION_UART1: - this->hw_serial_ = &Serial1; - Serial1.begin(this->baud_rate_); - Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); - break; - } - } else { - uart_set_debug(UART_NO); +#if defined(USE_ESP8266_LOGGER_SERIAL) + this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + Serial.swap(); } + Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#elif defined(USE_ESP8266_LOGGER_SERIAL1) + this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); + Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#else + // No serial logging - disable debug output + uart_set_debug(UART_NO); +#endif global_logger = this; @@ -39,15 +34,16 @@ void HOT Logger::write_msg_(const char *msg, size_t len) { } const LogString *Logger::get_uart_selection_() { - switch (this->uart_) { - case UART_SELECTION_UART0: - return LOG_STR("UART0"); - case UART_SELECTION_UART1: - return LOG_STR("UART1"); - case UART_SELECTION_UART0_SWAP: - default: - return LOG_STR("UART0_SWAP"); +#if defined(USE_ESP8266_LOGGER_SERIAL) + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + return LOG_STR("UART0_SWAP"); } + return LOG_STR("UART0"); +#elif defined(USE_ESP8266_LOGGER_SERIAL1) + return LOG_STR("UART1"); +#else + return LOG_STR("NONE"); +#endif } } // namespace esphome::logger diff --git a/esphome/components/logger/logger_host.cpp b/esphome/components/logger/logger_host.cpp index c5e1e6f865..874cdabd22 100644 --- a/esphome/components/logger/logger_host.cpp +++ b/esphome/components/logger/logger_host.cpp @@ -3,16 +3,24 @@ namespace esphome::logger { -void HOT Logger::write_msg_(const char *msg, size_t) { - time_t rawtime; - struct tm *timeinfo; - char buffer[80]; +void HOT Logger::write_msg_(const char *msg, size_t len) { + static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]" + // tx_buffer_size_ defaults to 512, so 768 covers default + headroom + char buffer[TIMESTAMP_LEN + 768]; + time_t rawtime; time(&rawtime); - timeinfo = localtime(&rawtime); - strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo); - fputs(buffer, stdout); - puts(msg); + struct tm timeinfo; + localtime_r(&rawtime, &timeinfo); // Thread-safe version + size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", &timeinfo); + + // Copy message (with newline already included by caller) + size_t copy_len = std::min(len, sizeof(buffer) - pos); + memcpy(buffer + pos, msg, copy_len); + pos += copy_len; + + // Single write for everything + fwrite(buffer, 1, pos, stdout); } void Logger::pre_setup() { global_logger = this; } diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp index 4a8535c8e4..be8252f56a 100644 --- a/esphome/components/logger/logger_rp2040.cpp +++ b/esphome/components/logger/logger_rp2040.cpp @@ -27,7 +27,10 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); } +void HOT Logger::write_msg_(const char *msg, size_t len) { + // Single write with newline already in buffer (added by caller) + this->hw_serial_->write(msg, len); +} const LogString *Logger::get_uart_selection_() { switch (this->uart_) { diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index ec2ff3013c..41f53beec0 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace esphome::logger { @@ -14,7 +15,7 @@ static const char *const TAG = "logger"; #ifdef USE_LOGGER_USB_CDC void Logger::loop() { - if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) { + if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) { return; } static bool opened = false; @@ -62,18 +63,19 @@ void Logger::pre_setup() { ESP_LOGI(TAG, "Log initialized"); } -void HOT Logger::write_msg_(const char *msg, size_t) { +void HOT Logger::write_msg_(const char *msg, size_t len) { + // Single write with newline already in buffer (added by caller) #ifdef CONFIG_PRINTK - printk("%s\n", msg); + // Requires the debug component and an active SWD connection. + // It is used for pyocd rtt -t nrf52840 + k_str_out(const_cast(msg), len); #endif - if (nullptr == this->uart_dev_) { + if (this->uart_dev_ == nullptr) { return; } - while (*msg) { - uart_poll_out(this->uart_dev_, *msg); - ++msg; + for (size_t i = 0; i < len; ++i) { + uart_poll_out(this->uart_dev_, msg[i]); } - uart_poll_out(this->uart_dev_, '\n'); } const LogString *Logger::get_uart_selection_() { diff --git a/esphome/components/logger/task_log_buffer.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp similarity index 98% rename from esphome/components/logger/task_log_buffer.cpp rename to esphome/components/logger/task_log_buffer_esp32.cpp index b5dd9f0239..b9dfe45b7f 100644 --- a/esphome/components/logger/task_log_buffer.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -1,5 +1,6 @@ +#ifdef USE_ESP32 -#include "task_log_buffer.h" +#include "task_log_buffer_esp32.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -134,3 +135,4 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin } // namespace esphome::logger #endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_ESP32 diff --git a/esphome/components/logger/task_log_buffer.h b/esphome/components/logger/task_log_buffer_esp32.h similarity index 77% rename from esphome/components/logger/task_log_buffer.h rename to esphome/components/logger/task_log_buffer_esp32.h index fdda07190d..fde9bd60d5 100644 --- a/esphome/components/logger/task_log_buffer.h +++ b/esphome/components/logger/task_log_buffer_esp32.h @@ -1,5 +1,7 @@ #pragma once +#ifdef USE_ESP32 + #include "esphome/core/defines.h" #include "esphome/core/helpers.h" @@ -13,6 +15,22 @@ namespace esphome::logger { +/** + * @brief Task log buffer for ESP32 platform using FreeRTOS ring buffer. + * + * Threading Model: Multi-Producer Single-Consumer (MPSC) + * - Multiple FreeRTOS tasks can safely call send_message_thread_safe() concurrently + * - Only the main loop task calls borrow_message_main_loop() and release_message_main_loop() + * + * This uses the FreeRTOS ring buffer (RINGBUF_TYPE_NOSPLIT) which provides + * built-in thread-safety for the MPSC pattern. The ring buffer ensures + * message integrity - each message is stored contiguously. + * + * Design: + * - Variable-size messages with header + text stored contiguously + * - FreeRTOS ring buffer handles synchronization internally + * - Atomic counter for fast has_messages() check without ring buffer lock + */ class TaskLogBuffer { public: // Structure for a log message header (text data follows immediately after) @@ -65,3 +83,4 @@ class TaskLogBuffer { } // namespace esphome::logger #endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_ESP32 diff --git a/esphome/components/logger/task_log_buffer_host.cpp b/esphome/components/logger/task_log_buffer_host.cpp new file mode 100644 index 0000000000..0660aeb061 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_host.cpp @@ -0,0 +1,157 @@ +#ifdef USE_HOST + +#include "task_log_buffer_host.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +#include "esphome/core/log.h" +#include +#include + +namespace esphome::logger { + +TaskLogBufferHost::TaskLogBufferHost(size_t slot_count) : slot_count_(slot_count) { + // Allocate message slots + this->slots_ = std::make_unique(slot_count); +} + +TaskLogBufferHost::~TaskLogBufferHost() { + // unique_ptr handles cleanup automatically +} + +int TaskLogBufferHost::acquire_write_slot_() { + // Try to reserve a slot using compare-and-swap + size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed); + + while (true) { + // Calculate next index (with wrap-around) + size_t next_reserve = (current_reserve + 1) % this->slot_count_; + + // Check if buffer would be full + // Buffer is full when next write position equals read position + size_t current_read = this->read_index_.load(std::memory_order_acquire); + if (next_reserve == current_read) { + return -1; // Buffer full + } + + // Try to claim this slot + if (this->reserve_index_.compare_exchange_weak(current_reserve, next_reserve, std::memory_order_acq_rel, + std::memory_order_relaxed)) { + return static_cast(current_reserve); + } + // If CAS failed, current_reserve was updated, retry with new value + } +} + +void TaskLogBufferHost::commit_write_slot_(int slot_index) { + // Mark the slot as ready for reading + this->slots_[slot_index].ready.store(true, std::memory_order_release); + + // Try to advance the write_index if we're the next expected commit + // This ensures messages are read in order + size_t expected = slot_index; + size_t next = (slot_index + 1) % this->slot_count_; + + // We only advance write_index if this slot is the next one expected + // This handles out-of-order commits correctly + while (true) { + if (!this->write_index_.compare_exchange_weak(expected, next, std::memory_order_release, + std::memory_order_relaxed)) { + // Someone else advanced it or we're not next in line, that's fine + break; + } + + // Successfully advanced, check if next slot is also ready + expected = next; + next = (next + 1) % this->slot_count_; + if (!this->slots_[expected].ready.load(std::memory_order_acquire)) { + break; + } + } +} + +bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format, + va_list args) { + // Acquire a slot + int slot_index = this->acquire_write_slot_(); + if (slot_index < 0) { + return false; // Buffer full + } + + LogMessage &msg = this->slots_[slot_index]; + + // Fill in the message header + msg.level = level; + msg.tag = tag; + msg.line = line; + + // Get thread name using pthread + char thread_name_buf[LogMessage::MAX_THREAD_NAME_SIZE]; + // pthread_getname_np works the same on Linux and macOS + if (pthread_getname_np(pthread_self(), thread_name_buf, sizeof(thread_name_buf)) == 0) { + strncpy(msg.thread_name, thread_name_buf, sizeof(msg.thread_name) - 1); + msg.thread_name[sizeof(msg.thread_name) - 1] = '\0'; + } else { + msg.thread_name[0] = '\0'; + } + + // Format the message text + int ret = vsnprintf(msg.text, sizeof(msg.text), format, args); + if (ret < 0) { + // Formatting error - still commit the slot but with empty text + msg.text[0] = '\0'; + msg.text_length = 0; + } else { + msg.text_length = static_cast(std::min(static_cast(ret), sizeof(msg.text) - 1)); + } + + // Remove trailing newlines + while (msg.text_length > 0 && msg.text[msg.text_length - 1] == '\n') { + msg.text_length--; + } + msg.text[msg.text_length] = '\0'; + + // Commit the slot + this->commit_write_slot_(slot_index); + + return true; +} + +bool TaskLogBufferHost::get_message_main_loop(LogMessage **message) { + if (message == nullptr) { + return false; + } + + size_t current_read = this->read_index_.load(std::memory_order_relaxed); + size_t current_write = this->write_index_.load(std::memory_order_acquire); + + // Check if buffer is empty + if (current_read == current_write) { + return false; + } + + // Check if the slot is ready (should always be true if write_index advanced) + LogMessage &msg = this->slots_[current_read]; + if (!msg.ready.load(std::memory_order_acquire)) { + return false; + } + + *message = &msg; + return true; +} + +void TaskLogBufferHost::release_message_main_loop() { + size_t current_read = this->read_index_.load(std::memory_order_relaxed); + + // Clear the ready flag + this->slots_[current_read].ready.store(false, std::memory_order_release); + + // Advance read index + size_t next_read = (current_read + 1) % this->slot_count_; + this->read_index_.store(next_read, std::memory_order_release); +} + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_HOST diff --git a/esphome/components/logger/task_log_buffer_host.h b/esphome/components/logger/task_log_buffer_host.h new file mode 100644 index 0000000000..d421d50ec6 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_host.h @@ -0,0 +1,122 @@ +#pragma once + +#ifdef USE_HOST + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +#include +#include +#include +#include +#include +#include + +namespace esphome::logger { + +/** + * @brief Lock-free task log buffer for host platform. + * + * Threading Model: Multi-Producer Single-Consumer (MPSC) + * - Multiple threads can safely call send_message_thread_safe() concurrently + * - Only the main loop thread calls get_message_main_loop() and release_message_main_loop() + * + * Producers (multiple threads) Consumer (main loop only) + * │ │ + * ▼ ▼ + * acquire_write_slot_() get_message_main_loop() + * CAS on reserve_index_ read write_index_ + * │ check ready flag + * ▼ │ + * write to slot (exclusive) ▼ + * │ read slot data + * ▼ │ + * commit_write_slot_() ▼ + * set ready=true release_message_main_loop() + * advance write_index_ set ready=false + * advance read_index_ + * + * This implements a lock-free ring buffer for log messages on the host platform. + * It uses atomic compare-and-swap (CAS) operations for thread-safe slot reservation + * without requiring mutexes in the hot path. + * + * Design: + * - Fixed number of pre-allocated message slots to avoid dynamic allocation + * - Each slot contains a header and fixed-size text buffer + * - Atomic CAS for slot reservation allows multiple producers without locks + * - Single consumer (main loop) processes messages in order + */ +class TaskLogBufferHost { + public: + // Default number of message slots - host has plenty of memory + static constexpr size_t DEFAULT_SLOT_COUNT = 64; + + // Structure for a log message (fixed size for lock-free operation) + struct LogMessage { + // Size constants + static constexpr size_t MAX_THREAD_NAME_SIZE = 32; + static constexpr size_t MAX_TEXT_SIZE = 512; + + const char *tag; // Pointer to static tag string + char thread_name[MAX_THREAD_NAME_SIZE]; // Thread name (copied) + char text[MAX_TEXT_SIZE + 1]; // Message text with null terminator + uint16_t text_length; // Actual length of text + uint16_t line; // Source line number + uint8_t level; // Log level + std::atomic ready; // Message is ready to be consumed + + LogMessage() : tag(nullptr), text_length(0), line(0), level(0), ready(false) { + thread_name[0] = '\0'; + text[0] = '\0'; + } + }; + + /// Constructor that takes the number of message slots + explicit TaskLogBufferHost(size_t slot_count); + ~TaskLogBufferHost(); + + // NOT thread-safe - get next message from buffer, only call from main loop + // Returns true if a message was retrieved, false if buffer is empty + bool get_message_main_loop(LogMessage **message); + + // NOT thread-safe - release the message after processing, only call from main loop + void release_message_main_loop(); + + // Thread-safe - send a message to the buffer from any thread + // Returns true if message was queued, false if buffer is full + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format, va_list args); + + // Check if there are messages ready to be processed + inline bool HOT has_messages() const { + return read_index_.load(std::memory_order_acquire) != write_index_.load(std::memory_order_acquire); + } + + // Get the buffer size (number of slots) + inline size_t size() const { return slot_count_; } + + private: + // Acquire a slot for writing (thread-safe) + // Returns slot index or -1 if buffer is full + int acquire_write_slot_(); + + // Commit a slot after writing (thread-safe) + void commit_write_slot_(int slot_index); + + std::unique_ptr slots_; // Pre-allocated message slots + size_t slot_count_; // Number of slots + + // Lock-free indices using atomics + // - reserve_index_: Next slot to reserve (producers CAS this to claim slots) + // - write_index_: Boundary of committed/ready slots (consumer reads up to this) + // - read_index_: Next slot to read (only consumer modifies this) + std::atomic reserve_index_{0}; // Next slot to reserve for writing + std::atomic write_index_{0}; // Last committed slot boundary + std::atomic read_index_{0}; // Next slot to read from +}; + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_HOST diff --git a/esphome/components/logger/task_log_buffer_libretiny.cpp b/esphome/components/logger/task_log_buffer_libretiny.cpp new file mode 100644 index 0000000000..580066e621 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_libretiny.cpp @@ -0,0 +1,206 @@ +#ifdef USE_LIBRETINY + +#include "task_log_buffer_libretiny.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + +namespace esphome::logger { + +TaskLogBufferLibreTiny::TaskLogBufferLibreTiny(size_t total_buffer_size) { + this->size_ = total_buffer_size; + // Allocate memory for the circular buffer using ESPHome's RAM allocator + RAMAllocator allocator; + this->storage_ = allocator.allocate(this->size_); + // Create mutex for thread-safe access + this->mutex_ = xSemaphoreCreateMutex(); +} + +TaskLogBufferLibreTiny::~TaskLogBufferLibreTiny() { + if (this->mutex_ != nullptr) { + vSemaphoreDelete(this->mutex_); + this->mutex_ = nullptr; + } + if (this->storage_ != nullptr) { + RAMAllocator allocator; + allocator.deallocate(this->storage_, this->size_); + this->storage_ = nullptr; + } +} + +size_t TaskLogBufferLibreTiny::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 + // But for contiguous, just from head to end (minus 1 to avoid head==tail ambiguity) + size_t space_to_end = this->size_ - this->head_; + if (this->tail_ == 0) { + // Can't use the last byte or head would equal tail + return space_to_end > 0 ? space_to_end - 1 : 0; + } + return space_to_end; + } else { + // tail is ahead of head + // Available contiguous space is from head to tail - 1 + return this->tail_ - this->head_ - 1; + } +} + +bool TaskLogBufferLibreTiny::borrow_message_main_loop(LogMessage **message, const char **text) { + if (message == nullptr || text == nullptr) { + return false; + } + + // Check if buffer was initialized successfully + if (this->mutex_ == nullptr || this->storage_ == nullptr) { + return false; + } + + // Try to take mutex without blocking - if busy, we'll get messages next loop iteration + if (xSemaphoreTake(this->mutex_, 0) != pdTRUE) { + return false; + } + + if (this->head_ == this->tail_) { + xSemaphoreGive(this->mutex_); + return false; + } + + // Read message header from tail + LogMessage *msg = reinterpret_cast(this->storage_ + this->tail_); + + // Check for padding marker (indicates wrap-around) + // We check the level field since valid levels are 0-7, and 0xFF indicates padding + if (msg->level == PADDING_MARKER_LEVEL) { + // Skip to start of buffer and re-read + this->tail_ = 0; + msg = reinterpret_cast(this->storage_); + } + *message = msg; + *text = msg->text_data(); + this->current_message_size_ = message_total_size(msg->text_length); + + // Keep mutex held until release_message_main_loop() + return true; +} + +void TaskLogBufferLibreTiny::release_message_main_loop() { + // Advance tail past the current message + this->tail_ += this->current_message_size_; + + // Handle wrap-around if we've reached the end + if (this->tail_ >= this->size_) { + this->tail_ = 0; + } + + this->message_count_--; + this->current_message_size_ = 0; + + xSemaphoreGive(this->mutex_); +} + +bool TaskLogBufferLibreTiny::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, + TaskHandle_t task_handle, const char *format, va_list args) { + // First, calculate the exact length needed using a null buffer (no actual writing) + va_list args_copy; + va_copy(args_copy, args); + int ret = vsnprintf(nullptr, 0, format, args_copy); + va_end(args_copy); + + if (ret <= 0) { + return false; // Formatting error or empty message + } + + // Calculate actual text length (capped to maximum size) + static constexpr size_t MAX_TEXT_SIZE = 255; + size_t text_length = (static_cast(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret; + + // Calculate total size needed (header + text length + null terminator) + size_t total_size = message_total_size(text_length); + + // Check if buffer was initialized successfully + if (this->mutex_ == nullptr || this->storage_ == nullptr) { + return false; // Buffer not initialized, fall back to direct output + } + + // Try to acquire mutex without blocking - don't block logging tasks + if (xSemaphoreTake(this->mutex_, 0) != pdTRUE) { + return false; // Mutex busy, fall back to direct output + } + + // Check if we have enough contiguous space + size_t contiguous = this->available_contiguous_space(); + + if (contiguous < total_size) { + // Not enough contiguous space at end + // Check if we can wrap around + size_t space_at_start = (this->head_ >= this->tail_) ? this->tail_ : 0; + if (space_at_start > 0) { + space_at_start--; // Leave 1 byte gap to distinguish full from empty + } + + // 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; + + 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(this->storage_ + this->head_); + padding->level = PADDING_MARKER_LEVEL; + this->head_ = 0; + } else { + // Not enough space anywhere, or can't safely write padding marker + xSemaphoreGive(this->mutex_); + return false; + } + } + + // Write message header + LogMessage *msg = reinterpret_cast(this->storage_ + this->head_); + msg->level = level; + msg->tag = tag; + msg->line = line; + + // Store the thread name now to avoid crashes if task is deleted before processing + const char *thread_name = pcTaskGetTaskName(task_handle); + if (thread_name != nullptr) { + strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1); + msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; + } else { + msg->thread_name[0] = '\0'; + } + + // Format the message text directly into the buffer + char *text_area = msg->text_data(); + ret = vsnprintf(text_area, text_length + 1, format, args); + + if (ret <= 0) { + xSemaphoreGive(this->mutex_); + return false; + } + + // Remove trailing newlines + while (text_length > 0 && text_area[text_length - 1] == '\n') { + text_length--; + } + + msg->text_length = text_length; + + // Advance head + this->head_ += total_size; + + // Handle wrap-around (shouldn't happen due to contiguous space check, but be safe) + if (this->head_ >= this->size_) { + this->head_ = 0; + } + + this->message_count_++; + + xSemaphoreGive(this->mutex_); + return true; +} + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_LIBRETINY diff --git a/esphome/components/logger/task_log_buffer_libretiny.h b/esphome/components/logger/task_log_buffer_libretiny.h new file mode 100644 index 0000000000..bf6b2d2fa4 --- /dev/null +++ b/esphome/components/logger/task_log_buffer_libretiny.h @@ -0,0 +1,103 @@ +#pragma once + +#ifdef USE_LIBRETINY + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER +#include +#include +#include +#include +#include +#include + +namespace esphome::logger { + +/** + * @brief Task log buffer for LibreTiny platform using mutex-protected circular buffer. + * + * Why This Is Critical: + * Without thread-safe logging, when a non-main task logs a message, it would directly + * call the logger which builds a protobuf message in a shared buffer. If this happens + * while the main loop is also using that buffer (e.g., sending API responses), the + * buffer gets corrupted, sending garbage to all connected API clients and breaking + * their connections. This buffer ensures log messages from other tasks are queued + * safely and processed only from the main loop. + * + * Threading Model: Multi-Producer Single-Consumer (MPSC) + * - Multiple FreeRTOS tasks can safely call send_message_thread_safe() concurrently + * - Only the main loop task calls borrow_message_main_loop() and release_message_main_loop() + * + * This uses a simple circular buffer protected by a FreeRTOS mutex. Unlike ESP32, + * LibreTiny lacks hardware atomic support (ARM968E-S has no LDREX/STREX), so we use + * a volatile counter for fast has_messages() checks instead of atomics. + * + * Design: + * - Variable-size messages with header + text stored contiguously (NOSPLIT style) + * - FreeRTOS mutex protects all buffer operations + * - Volatile counter enables fast has_messages() without lock overhead + * - If message doesn't fit at end, padding is added and message wraps to start + */ +class TaskLogBufferLibreTiny { + public: + // Structure for a log message header (text data follows immediately after) + struct LogMessage { + const char *tag; // We store the pointer, assuming tags are static + char thread_name[16]; // Store thread name directly (only used for non-main threads) + uint16_t text_length; // Length of the message text (up to ~64KB) + uint16_t line; // Source code line number + uint8_t level; // Log level (0-7) + + // Methods for accessing message contents + inline char *text_data() { return reinterpret_cast(this) + sizeof(LogMessage); } + inline const char *text_data() const { return reinterpret_cast(this) + sizeof(LogMessage); } + }; + + // Padding marker level to indicate wrap-around point (stored in LogMessage.level field) + // Valid log levels are 0-7, so 0xFF cannot be a real message + static constexpr uint8_t PADDING_MARKER_LEVEL = 0xFF; + + // Constructor that takes a total buffer size + explicit TaskLogBufferLibreTiny(size_t total_buffer_size); + ~TaskLogBufferLibreTiny(); + + // NOT thread-safe - borrow a message from the buffer, only call from main loop + bool borrow_message_main_loop(LogMessage **message, const char **text); + + // NOT thread-safe - release a message buffer, only call from main loop + void release_message_main_loop(); + + // Thread-safe - send a message to the buffer from any thread + bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle, + const char *format, va_list args); + + // Fast check using volatile counter - no lock needed + // Worst case: miss a message for one loop iteration (~8ms at 7000 loops/min) + inline bool HOT has_messages() const { return this->message_count_ != 0; } + + // Get the total buffer size in bytes + inline size_t size() const { return this->size_; } + + private: + // Calculate total size needed for a message (header + text + null terminator) + 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; + + uint8_t *storage_{nullptr}; // Pointer to allocated memory + size_t size_{0}; // Size of allocated memory + size_t head_{0}; // Write position + size_t tail_{0}; // Read position + + SemaphoreHandle_t mutex_{nullptr}; // FreeRTOS mutex for thread safety + volatile uint16_t message_count_{0}; // Fast check counter (dirty read OK) + size_t current_message_size_{0}; // Size of currently borrowed message +}; + +} // namespace esphome::logger + +#endif // USE_ESPHOME_TASK_LOG_BUFFER +#endif // USE_LIBRETINY diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 19c258fcd5..c9cad1ac90 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -256,9 +256,11 @@ async def to_code(configs): True, type=lv_font_t.operator("ptr").operator("const"), ) + # static=False because LV_FONT_CUSTOM_DECLARE creates an extern declaration cg.new_variable( globfont_id, MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(), + static=False, ) add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT) else: diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index 2a1da2383c..b79d1e88dd 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -353,7 +353,7 @@ def lv_Pvariable(type, name) -> MockObj: """ if isinstance(name, str): name = ID(name, True, type) - decl = VariableDeclarationExpression(type, "*", name) + decl = VariableDeclarationExpression(type, "*", name, static=True) CORE.add_global(decl) var = MockObj(name, "->") CORE.register_variable(name, var) @@ -369,7 +369,7 @@ def lv_variable(type, name) -> MockObj: """ if isinstance(name, str): name = ID(name, True, type) - decl = VariableDeclarationExpression(type, "", name) + decl = VariableDeclarationExpression(type, "", name, static=True) CORE.add_global(decl) var = MockObj(name, ".") CORE.register_variable(name, var) diff --git a/esphome/components/m5stack_8angle/m5stack_8angle.cpp b/esphome/components/m5stack_8angle/m5stack_8angle.cpp index c542b4459e..5a9a5e8c9d 100644 --- a/esphome/components/m5stack_8angle/m5stack_8angle.cpp +++ b/esphome/components/m5stack_8angle/m5stack_8angle.cpp @@ -26,9 +26,11 @@ void M5Stack8AngleComponent::setup() { } void M5Stack8AngleComponent::dump_config() { - ESP_LOGCONFIG(TAG, "M5STACK_8ANGLE:"); + ESP_LOGCONFIG(TAG, + "M5STACK_8ANGLE:\n" + " Firmware version: %d", + this->fw_version_); LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Firmware version: %d ", this->fw_version_); } float M5Stack8AngleComponent::read_knob_pos(uint8_t channel, AnalogBits bits) { diff --git a/esphome/components/mapping/__init__.py b/esphome/components/mapping/__init__.py index 94c7c10a82..a36b414fd5 100644 --- a/esphome/components/mapping/__init__.py +++ b/esphome/components/mapping/__init__.py @@ -133,7 +133,7 @@ async def to_code(config): value_type, ) var = MockObj(varid, ".") - decl = VariableDeclarationExpression(varid.type, "", varid) + decl = VariableDeclarationExpression(varid.type, "", varid, static=True) add_global(decl) CORE.register_variable(varid, var) diff --git a/esphome/components/max6956/max6956.cpp b/esphome/components/max6956/max6956.cpp index a377a1a192..13fe5a5323 100644 --- a/esphome/components/max6956/max6956.cpp +++ b/esphome/components/max6956/max6956.cpp @@ -161,10 +161,8 @@ void MAX6956GPIOPin::setup() { pin_mode(flags_); } void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string MAX6956GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_); - return buffer; +size_t MAX6956GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via Max6956", this->pin_); } } // namespace max6956 diff --git a/esphome/components/max6956/max6956.h b/esphome/components/max6956/max6956.h index 0a1fd5e4b5..0c609b0b43 100644 --- a/esphome/components/max6956/max6956.h +++ b/esphome/components/max6956/max6956.h @@ -76,7 +76,7 @@ class MAX6956GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MAX6956 *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index be86cb2256..87c2668962 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -99,10 +99,8 @@ void MCP23016GPIOPin::setup() { pin_mode(flags_); } void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string MCP23016GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via MCP23016", pin_); - return buffer; +size_t MCP23016GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via MCP23016", this->pin_); } } // namespace mcp23016 diff --git a/esphome/components/mcp23016/mcp23016.h b/esphome/components/mcp23016/mcp23016.h index 781c207de0..c2bc885c95 100644 --- a/esphome/components/mcp23016/mcp23016.h +++ b/esphome/components/mcp23016/mcp23016.h @@ -60,7 +60,7 @@ class MCP23016GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MCP23016 *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index 81324e794f..302f6b8280 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -16,8 +16,8 @@ template bool MCP23XXXGPIOPin::digital_read() { template void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -template std::string MCP23XXXGPIOPin::dump_summary() const { - return str_snprintf("%u via MCP23XXX", 15, pin_); +template size_t MCP23XXXGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via MCP23XXX", this->pin_); } template class MCP23XXXGPIOPin<8>; diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h index cf0ef5d41c..fb992466d5 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.h +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -36,7 +36,7 @@ template class MCP23XXXGPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MCP23XXXBase *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/mcp3204/mcp3204.cpp b/esphome/components/mcp3204/mcp3204.cpp index f0dd171a14..abefcad0eb 100644 --- a/esphome/components/mcp3204/mcp3204.cpp +++ b/esphome/components/mcp3204/mcp3204.cpp @@ -11,9 +11,11 @@ float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; } void MCP3204::setup() { this->spi_setup(); } void MCP3204::dump_config() { - ESP_LOGCONFIG(TAG, "MCP3204:"); + ESP_LOGCONFIG(TAG, + "MCP3204:\n" + " Reference Voltage: %.2fV", + this->reference_voltage_); LOG_PIN(" CS Pin:", this->cs_); - ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); } float MCP3204::read_data(uint8_t pin, bool differential) { diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp index 4c4abef4a7..e673537be1 100644 --- a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp @@ -11,8 +11,10 @@ float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; } void MCP3204Sensor::dump_config() { LOG_SENSOR("", "MCP3204 Sensor", this); - ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); - ESP_LOGCONFIG(TAG, " Differential Mode: %s", YESNO(this->differential_mode_)); + ESP_LOGCONFIG(TAG, + " Pin: %u\n" + " Differential Mode: %s", + this->pin_, YESNO(this->differential_mode_)); LOG_UPDATE_INTERVAL(this); } float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_, this->differential_mode_); } diff --git a/esphome/components/mcp9600/mcp9600.cpp b/esphome/components/mcp9600/mcp9600.cpp index e1a88988c4..ff411bef7a 100644 --- a/esphome/components/mcp9600/mcp9600.cpp +++ b/esphome/components/mcp9600/mcp9600.cpp @@ -63,12 +63,12 @@ void MCP9600Component::setup() { } void MCP9600Component::dump_config() { - ESP_LOGCONFIG(TAG, "MCP9600:"); + ESP_LOGCONFIG(TAG, + "MCP9600:\n" + " Device ID: 0x%x", + this->device_id_); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_); - LOG_SENSOR(" ", "Hot Junction Temperature", this->hot_junction_sensor_); LOG_SENSOR(" ", "Cold Junction Temperature", this->cold_junction_sensor_); diff --git a/esphome/components/md5/__init__.py b/esphome/components/md5/__init__.py index 1af9ee0b29..1710b00e66 100644 --- a/esphome/components/md5/__init__.py +++ b/esphome/components/md5/__init__.py @@ -1,7 +1,17 @@ import esphome.codegen as cg +from esphome.core import CORE +from esphome.helpers import IS_MACOS CODEOWNERS = ["@esphome/core"] async def to_code(config): cg.add_define("USE_MD5") + + # Add OpenSSL library for host platform + if CORE.is_host: + if IS_MACOS: + # macOS needs special handling for Homebrew OpenSSL + cg.add_build_flag("-I/opt/homebrew/opt/openssl/include") + cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib") + cg.add_build_flag("-lcrypto") diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index 866f00eda4..26554e4d3c 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -39,6 +39,44 @@ void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_ void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); } #endif // USE_RP2040 +#ifdef USE_HOST +MD5Digest::~MD5Digest() { + if (this->ctx_) { + EVP_MD_CTX_free(this->ctx_); + } +} + +void MD5Digest::init() { + if (this->ctx_) { + EVP_MD_CTX_free(this->ctx_); + } + this->ctx_ = EVP_MD_CTX_new(); + EVP_DigestInit_ex(this->ctx_, EVP_md5(), nullptr); + this->calculated_ = false; + memset(this->digest_, 0, 16); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { + if (!this->ctx_) { + this->init(); + } + EVP_DigestUpdate(this->ctx_, data, len); +} + +void MD5Digest::calculate() { + if (!this->ctx_) { + this->init(); + } + if (!this->calculated_) { + unsigned int len = 16; + EVP_DigestFinal_ex(this->ctx_, this->digest_, &len); + this->calculated_ = true; + } +} +#else +MD5Digest::~MD5Digest() = default; +#endif // USE_HOST + } // namespace md5 } // namespace esphome #endif diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index b0da2c0a3b..6ff651b02e 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -5,6 +5,10 @@ #include "esphome/core/hash_base.h" +#ifdef USE_HOST +#include +#endif + #ifdef USE_ESP32 #include "esp_rom_md5.h" #define MD5_CTX_TYPE md5_context_t @@ -31,7 +35,7 @@ namespace md5 { class MD5Digest : public HashBase { public: MD5Digest() = default; - ~MD5Digest() override = default; + ~MD5Digest() override; /// Initialize a new MD5 digest computation. void init() override; @@ -47,7 +51,12 @@ class MD5Digest : public HashBase { size_t get_size() const override { return 16; } protected: +#ifdef USE_HOST + EVP_MD_CTX *ctx_{nullptr}; + bool calculated_{false}; +#else MD5_CTX_TYPE ctx_{}; +#endif }; } // namespace md5 diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 99b728b249..3088d8ad7e 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -157,14 +157,12 @@ async def to_code(config): return if CORE.using_arduino: - if CORE.is_esp32: - cg.add_library("ESPmDNS", None) - elif CORE.is_esp8266: + if CORE.is_esp8266: cg.add_library("ESP8266mDNS", None) elif CORE.is_rp2040: cg.add_library("LEAmDNS", None) - if CORE.using_esp_idf: + if CORE.is_esp32: add_idf_component(name="espressif/mdns", ref="1.9.1") cg.add_define("USE_MDNS") diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 2f1c99115f..b753e2d088 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -157,7 +157,7 @@ class MediaPlayer : public EntityBase { virtual void control(const MediaPlayerCall &call) = 0; - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; }; } // namespace media_player diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index c3c8120362..00e6e14d85 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -13,6 +13,9 @@ static const uint8_t MHZ19_COMMAND_GET_PPM[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x static const uint8_t MHZ19_COMMAND_ABC_ENABLE[] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00}; static const uint8_t MHZ19_COMMAND_ABC_DISABLE[] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00}; static const uint8_t MHZ19_COMMAND_CALIBRATE_ZERO[] = {0xFF, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x07, 0xD0}; +static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88}; +static const uint8_t MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM[] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x27, 0x10}; uint8_t mhz19_checksum(const uint8_t *command) { uint8_t sum = 0; @@ -28,6 +31,8 @@ void MHZ19Component::setup() { } else if (this->abc_boot_logic_ == MHZ19_ABC_DISABLED) { this->abc_disable(); } + + this->range_set(this->detection_range_); } void MHZ19Component::update() { @@ -86,6 +91,26 @@ void MHZ19Component::abc_disable() { this->mhz19_write_command_(MHZ19_COMMAND_ABC_DISABLE, nullptr); } +void MHZ19Component::range_set(MHZ19DetectionRange detection_ppm) { + switch (detection_ppm) { + case MHZ19_DETECTION_RANGE_DEFAULT: + ESP_LOGV(TAG, "Using previously set detection range (no change)"); + break; + case MHZ19_DETECTION_RANGE_0_2000PPM: + ESP_LOGD(TAG, "Setting detection range to 0 to 2000ppm"); + this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_2000PPM, nullptr); + break; + case MHZ19_DETECTION_RANGE_0_5000PPM: + ESP_LOGD(TAG, "Setting detection range to 0 to 5000ppm"); + this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_5000PPM, nullptr); + break; + case MHZ19_DETECTION_RANGE_0_10000PPM: + ESP_LOGD(TAG, "Setting detection range to 0 to 10000ppm"); + this->mhz19_write_command_(MHZ19_COMMAND_DETECTION_RANGE_0_10000PPM, nullptr); + break; + } +} + bool MHZ19Component::mhz19_write_command_(const uint8_t *command, uint8_t *response) { // Empty RX Buffer while (this->available()) @@ -99,7 +124,9 @@ bool MHZ19Component::mhz19_write_command_(const uint8_t *command, uint8_t *respo return this->read_array(response, MHZ19_RESPONSE_LENGTH); } + float MHZ19Component::get_setup_priority() const { return setup_priority::DATA; } + void MHZ19Component::dump_config() { ESP_LOGCONFIG(TAG, "MH-Z19:"); LOG_SENSOR(" ", "CO2", this->co2_sensor_); @@ -113,6 +140,23 @@ void MHZ19Component::dump_config() { } ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_); + + const char *range_str; + switch (this->detection_range_) { + case MHZ19_DETECTION_RANGE_DEFAULT: + range_str = "default"; + break; + case MHZ19_DETECTION_RANGE_0_2000PPM: + range_str = "0 to 2000ppm"; + break; + case MHZ19_DETECTION_RANGE_0_5000PPM: + range_str = "0 to 5000ppm"; + break; + case MHZ19_DETECTION_RANGE_0_10000PPM: + range_str = "0 to 10000ppm"; + break; + } + ESP_LOGCONFIG(TAG, " Detection range: %s", range_str); } } // namespace mhz19 diff --git a/esphome/components/mhz19/mhz19.h b/esphome/components/mhz19/mhz19.h index be36886d62..5898bab649 100644 --- a/esphome/components/mhz19/mhz19.h +++ b/esphome/components/mhz19/mhz19.h @@ -8,7 +8,18 @@ namespace esphome { namespace mhz19 { -enum MHZ19ABCLogic { MHZ19_ABC_NONE = 0, MHZ19_ABC_ENABLED, MHZ19_ABC_DISABLED }; +enum MHZ19ABCLogic { + MHZ19_ABC_NONE = 0, + MHZ19_ABC_ENABLED, + MHZ19_ABC_DISABLED, +}; + +enum MHZ19DetectionRange { + MHZ19_DETECTION_RANGE_DEFAULT = 0, + MHZ19_DETECTION_RANGE_0_2000PPM, + MHZ19_DETECTION_RANGE_0_5000PPM, + MHZ19_DETECTION_RANGE_0_10000PPM, +}; class MHZ19Component : public PollingComponent, public uart::UARTDevice { public: @@ -21,11 +32,13 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { void calibrate_zero(); void abc_enable(); void abc_disable(); + void range_set(MHZ19DetectionRange detection_ppm); void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; } void set_abc_enabled(bool abc_enabled) { abc_boot_logic_ = abc_enabled ? MHZ19_ABC_ENABLED : MHZ19_ABC_DISABLED; } void set_warmup_seconds(uint32_t seconds) { warmup_seconds_ = seconds; } + void set_detection_range(MHZ19DetectionRange detection_range) { detection_range_ = detection_range; } protected: bool mhz19_write_command_(const uint8_t *command, uint8_t *response); @@ -33,37 +46,32 @@ class MHZ19Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr}; MHZ19ABCLogic abc_boot_logic_{MHZ19_ABC_NONE}; + uint32_t warmup_seconds_; + + MHZ19DetectionRange detection_range_{MHZ19_DETECTION_RANGE_DEFAULT}; }; -template class MHZ19CalibrateZeroAction : public Action { +template class MHZ19CalibrateZeroAction : public Action, public Parented { public: - MHZ19CalibrateZeroAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - - void play(const Ts &...x) override { this->mhz19_->calibrate_zero(); } - - protected: - MHZ19Component *mhz19_; + void play(const Ts &...x) override { this->parent_->calibrate_zero(); } }; -template class MHZ19ABCEnableAction : public Action { +template class MHZ19ABCEnableAction : public Action, public Parented { public: - MHZ19ABCEnableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} - - void play(const Ts &...x) override { this->mhz19_->abc_enable(); } - - protected: - MHZ19Component *mhz19_; + void play(const Ts &...x) override { this->parent_->abc_enable(); } }; -template class MHZ19ABCDisableAction : public Action { +template class MHZ19ABCDisableAction : public Action, public Parented { public: - MHZ19ABCDisableAction(MHZ19Component *mhz19) : mhz19_(mhz19) {} + void play(const Ts &...x) override { this->parent_->abc_disable(); } +}; - void play(const Ts &...x) override { this->mhz19_->abc_disable(); } +template class MHZ19DetectionRangeSetAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(MHZ19DetectionRange, detection_range) - protected: - MHZ19Component *mhz19_; + void play(const Ts &...x) override { this->parent_->range_set(this->detection_range_.value(x...)); } }; } // namespace mhz19 diff --git a/esphome/components/mhz19/sensor.py b/esphome/components/mhz19/sensor.py index 106636a6ba..1f698be404 100644 --- a/esphome/components/mhz19/sensor.py +++ b/esphome/components/mhz19/sensor.py @@ -19,14 +19,33 @@ DEPENDENCIES = ["uart"] CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration" CONF_WARMUP_TIME = "warmup_time" +CONF_DETECTION_RANGE = "detection_range" mhz19_ns = cg.esphome_ns.namespace("mhz19") MHZ19Component = mhz19_ns.class_("MHZ19Component", cg.PollingComponent, uart.UARTDevice) MHZ19CalibrateZeroAction = mhz19_ns.class_( - "MHZ19CalibrateZeroAction", automation.Action + "MHZ19CalibrateZeroAction", automation.Action, cg.Parented.template(MHZ19Component) ) -MHZ19ABCEnableAction = mhz19_ns.class_("MHZ19ABCEnableAction", automation.Action) -MHZ19ABCDisableAction = mhz19_ns.class_("MHZ19ABCDisableAction", automation.Action) +MHZ19ABCEnableAction = mhz19_ns.class_( + "MHZ19ABCEnableAction", automation.Action, cg.Parented.template(MHZ19Component) +) +MHZ19ABCDisableAction = mhz19_ns.class_( + "MHZ19ABCDisableAction", automation.Action, cg.Parented.template(MHZ19Component) +) +MHZ19DetectionRangeSetAction = mhz19_ns.class_( + "MHZ19DetectionRangeSetAction", + automation.Action, + cg.Parented.template(MHZ19Component), +) + +mhz19_detection_range = mhz19_ns.enum("MHZ19DetectionRange") +MHZ19_DETECTION_RANGE_ENUM = { + 2000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_2000PPM, + 5000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_5000PPM, + 10000: mhz19_detection_range.MHZ19_DETECTION_RANGE_0_10000PPM, +} + +_validate_ppm = cv.float_with_unit("parts per million", "ppm") CONFIG_SCHEMA = ( cv.Schema( @@ -49,6 +68,9 @@ CONFIG_SCHEMA = ( cv.Optional( CONF_WARMUP_TIME, default="75s" ): cv.positive_time_period_seconds, + cv.Optional(CONF_DETECTION_RANGE): cv.All( + _validate_ppm, cv.enum(MHZ19_DETECTION_RANGE_ENUM) + ), } ) .extend(cv.polling_component_schema("60s")) @@ -78,8 +100,11 @@ async def to_code(config): cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME])) + if CONF_DETECTION_RANGE in config: + cg.add(var.set_detection_range(config[CONF_DETECTION_RANGE])) -CALIBRATION_ACTION_SCHEMA = maybe_simple_id( + +NO_ARGS_ACTION_SCHEMA = maybe_simple_id( { cv.Required(CONF_ID): cv.use_id(MHZ19Component), } @@ -87,14 +112,37 @@ CALIBRATION_ACTION_SCHEMA = maybe_simple_id( @automation.register_action( - "mhz19.calibrate_zero", MHZ19CalibrateZeroAction, CALIBRATION_ACTION_SCHEMA + "mhz19.calibrate_zero", MHZ19CalibrateZeroAction, NO_ARGS_ACTION_SCHEMA ) @automation.register_action( - "mhz19.abc_enable", MHZ19ABCEnableAction, CALIBRATION_ACTION_SCHEMA + "mhz19.abc_enable", MHZ19ABCEnableAction, NO_ARGS_ACTION_SCHEMA ) @automation.register_action( - "mhz19.abc_disable", MHZ19ABCDisableAction, CALIBRATION_ACTION_SCHEMA + "mhz19.abc_disable", MHZ19ABCDisableAction, NO_ARGS_ACTION_SCHEMA ) -async def mhz19_calibration_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) +async def mhz19_no_args_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +RANGE_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(MHZ19Component), + cv.Required(CONF_DETECTION_RANGE): cv.All( + _validate_ppm, cv.enum(MHZ19_DETECTION_RANGE_ENUM) + ), + } +) + + +@automation.register_action( + "mhz19.detection_range_set", MHZ19DetectionRangeSetAction, RANGE_ACTION_SCHEMA +) +async def mhz19_detection_range_set_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + detection_range = config.get(CONF_DETECTION_RANGE) + template_ = await cg.templatable(detection_range, args, mhz19_detection_range) + cg.add(var.set_detection_range(template_)) + return var diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 575fb97799..74696584da 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -7,7 +7,7 @@ from urllib.parse import urljoin from esphome import automation, external_files, git from esphome.automation import register_action, register_condition import esphome.codegen as cg -from esphome.components import esp32, microphone, socket +from esphome.components import esp32, microphone, ota, socket import esphome.config_validation as cv from esphome.const import ( CONF_FILE, @@ -368,7 +368,7 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_with_esp_idf, + cv.only_on_esp32, ) @@ -452,7 +452,7 @@ async def to_code(config): cg.add(var.set_microphone_source(mic_source)) cg.add_define("USE_MICRO_WAKE_WORD") - cg.add_define("USE_OTA_STATE_CALLBACK") + ota.request_ota_state_listeners() esp32.add_idf_component(name="espressif/esp-tflite-micro", ref="1.3.3~1") diff --git a/esphome/components/micro_wake_word/automation.h b/esphome/components/micro_wake_word/automation.h index e1795a7e64..218ce9e4bc 100644 --- a/esphome/components/micro_wake_word/automation.h +++ b/esphome/components/micro_wake_word/automation.h @@ -3,7 +3,7 @@ #include "micro_wake_word.h" #include "streaming_model.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 namespace esphome { namespace micro_wake_word { diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index ec8fa34da4..d7e80efc84 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -1,6 +1,6 @@ #include "micro_wake_word.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/application.h" #include "esphome/core/hal.h" @@ -119,18 +119,21 @@ void MicroWakeWord::setup() { } }); -#ifdef USE_OTA - ota::get_global_ota_callback()->add_on_state_callback( - [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { - if (state == ota::OTA_STARTED) { - this->suspend_task_(); - } else if (state == ota::OTA_ERROR) { - this->resume_task_(); - } - }); +#ifdef USE_OTA_STATE_LISTENER + ota::get_global_ota_callback()->add_global_state_listener(this); #endif } +#ifdef USE_OTA_STATE_LISTENER +void MicroWakeWord::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { + this->suspend_task_(); + } else if (state == ota::OTA_ERROR) { + this->resume_task_(); + } +} +#endif + void MicroWakeWord::inference_task(void *params) { MicroWakeWord *this_mww = (MicroWakeWord *) params; @@ -470,4 +473,4 @@ bool MicroWakeWord::update_model_probabilities_(const int8_t audio_features[PREP } // namespace micro_wake_word } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h index d46c40e48b..b427e4dfcb 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.h +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "preprocessor_settings.h" #include "streaming_model.h" @@ -9,8 +9,13 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/ring_buffer.h" +#ifdef USE_OTA_STATE_LISTENER +#include "esphome/components/ota/ota_backend.h" +#endif + #include #include @@ -26,13 +31,22 @@ enum State { STOPPED, }; -class MicroWakeWord : public Component { +class MicroWakeWord : public Component +#ifdef USE_OTA_STATE_LISTENER + , + public ota::OTAGlobalStateListener +#endif +{ public: void setup() override; void loop() override; float get_setup_priority() const override; void dump_config() override; +#ifdef USE_OTA_STATE_LISTENER + void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; +#endif + void start(); void stop(); @@ -126,4 +140,4 @@ class MicroWakeWord : public Component { } // namespace micro_wake_word } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/micro_wake_word/preprocessor_settings.h b/esphome/components/micro_wake_word/preprocessor_settings.h index 3de21de92e..c9d195b49b 100644 --- a/esphome/components/micro_wake_word/preprocessor_settings.h +++ b/esphome/components/micro_wake_word/preprocessor_settings.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include diff --git a/esphome/components/micro_wake_word/streaming_model.cpp b/esphome/components/micro_wake_word/streaming_model.cpp index 2b073cce56..47d2c70e13 100644 --- a/esphome/components/micro_wake_word/streaming_model.cpp +++ b/esphome/components/micro_wake_word/streaming_model.cpp @@ -1,6 +1,6 @@ #include "streaming_model.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/helpers.h" #include "esphome/core/log.h" diff --git a/esphome/components/micro_wake_word/streaming_model.h b/esphome/components/micro_wake_word/streaming_model.h index b7b22b9700..0811bfb19b 100644 --- a/esphome/components/micro_wake_word/streaming_model.h +++ b/esphome/components/micro_wake_word/streaming_model.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "preprocessor_settings.h" diff --git a/esphome/components/microphone/__init__.py b/esphome/components/microphone/__init__.py index 1fc0df88a3..ce31484413 100644 --- a/esphome/components/microphone/__init__.py +++ b/esphome/components/microphone/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_GAIN_FACTOR, CONF_ID, CONF_MICROPHONE, + CONF_ON_DATA, CONF_TRIGGER_ID, ) from esphome.core import CORE @@ -19,8 +20,6 @@ CODEOWNERS = ["@jesserockz", "@kahrendt"] IS_PLATFORM_COMPONENT = True -CONF_ON_DATA = "on_data" - microphone_ns = cg.esphome_ns.namespace("microphone") Microphone = microphone_ns.class_("Microphone") diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index a6a8d52549..bc750e3713 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -65,12 +65,14 @@ void AirConditioner::control(const ClimateCall &call) { if (call.get_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_preset().value()); } else if (call.has_custom_preset()) { - ctrl.preset = Converters::to_midea_preset(call.get_custom_preset()); + // get_custom_preset() returns StringRef pointing to null-terminated string literals from codegen + ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().c_str()); } if (call.get_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value()); } else if (call.has_custom_fan_mode()) { - ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode()); + // get_custom_fan_mode() returns StringRef pointing to null-terminated string literals from codegen + ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().c_str()); } this->base_.control(ctrl); } diff --git a/esphome/components/midea_ir/midea_ir.cpp b/esphome/components/midea_ir/midea_ir.cpp index c269b2f7d9..eaee1c731c 100644 --- a/esphome/components/midea_ir/midea_ir.cpp +++ b/esphome/components/midea_ir/midea_ir.cpp @@ -165,7 +165,8 @@ bool MideaIR::on_receive(remote_base::RemoteReceiveData data) { } bool MideaIR::on_midea_(const MideaData &data) { - ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_string().c_str()); + char buf[MideaData::TO_STR_BUFFER_SIZE]; + ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_str(buf)); if (data.type() == MideaData::MIDEA_TYPE_CONTROL) { const ControlData status = data; if (status.get_mode() != climate::CLIMATE_MODE_FAN_ONLY) diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index 90c4cc082e..c288b33cd2 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -165,8 +165,8 @@ def model_schema(config): ) return cv.All( schema, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32P4]), - cv.only_with_esp_idf, ) diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index cae8647398..18cafab684 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -1,10 +1,14 @@ #ifdef USE_ESP32_VARIANT_ESP32P4 #include #include "mipi_dsi.h" +#include "esphome/core/helpers.h" namespace esphome { namespace mipi_dsi { +// Maximum bytes to log for init commands (truncated if larger) +static constexpr size_t MIPI_DSI_MAX_CMD_LOG_BYTES = 64; + static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) { auto *sem = static_cast(user_ctx); BaseType_t need_yield = pdFALSE; @@ -121,8 +125,11 @@ void MIPI_DSI::setup() { } } const auto *ptr = vec.data() + index; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(MIPI_DSI_MAX_CMD_LOG_BYTES)]; +#endif ESP_LOGVV(TAG, "Command %02X, length %d, byte(s) %s", cmd, num_args, - format_hex_pretty(ptr, num_args, '.', false).c_str()); + format_hex_pretty_to(hex_buf, ptr, num_args, '.')); err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args); if (err != ESP_OK) { this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err); @@ -293,6 +300,13 @@ void MIPI_DSI::draw_pixel_at(int x, int y, Color color) { void MIPI_DSI::fill(Color color) { if (!this->check_buffer_()) return; + + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + switch (this->color_depth_) { case display::COLOR_BITNESS_565: { auto *ptr_16 = reinterpret_cast(this->buffer_); diff --git a/esphome/components/mipi_rgb/display.py b/esphome/components/mipi_rgb/display.py index 61dbeb8ed4..96e167b2e6 100644 --- a/esphome/components/mipi_rgb/display.py +++ b/esphome/components/mipi_rgb/display.py @@ -224,8 +224,8 @@ def _config_schema(config): schema = model_schema(config) return cv.All( schema, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, )(config) diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index d5d1caf6d2..ef96da8a1c 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -1,5 +1,6 @@ #ifdef USE_ESP32_VARIANT_ESP32S3 #include "mipi_rgb.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" #include "esp_lcd_panel_rgb.h" @@ -8,6 +9,9 @@ namespace esphome { namespace mipi_rgb { static const uint8_t DELAY_FLAG = 0xFF; + +// Maximum bytes to log for init commands (truncated if larger) +static constexpr size_t MIPI_RGB_MAX_CMD_LOG_BYTES = 64; static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes @@ -91,8 +95,9 @@ void MipiRgbSpi::write_init_sequence_() { delay(120); // NOLINT } const auto *ptr = vec.data() + index; + char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)]; ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args, - format_hex_pretty(ptr, num_args, '.', false).c_str()); + format_hex_pretty_to(hex_buf, ptr, num_args, '.')); index += num_args; this->write_command_(cmd); while (num_args-- != 0) @@ -300,6 +305,13 @@ void MipiRgb::draw_pixel_at(int x, int y, Color color) { void MipiRgb::fill(Color color) { if (!this->check_buffer_()) return; + + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + auto *ptr_16 = reinterpret_cast(this->buffer_); uint8_t hi_byte = static_cast(color.r & 0xF8) | (color.g >> 5); uint8_t lo_byte = static_cast((color.g & 0x1C) << 3) | (color.b >> 3); diff --git a/esphome/components/mipi_spi/display.py b/esphome/components/mipi_spi/display.py index 50ea826eab..69bf133c68 100644 --- a/esphome/components/mipi_spi/display.py +++ b/esphome/components/mipi_spi/display.py @@ -224,7 +224,7 @@ def model_schema(config): } ) if bus_mode != TYPE_SINGLE: - return cv.All(schema, cv.only_with_esp_idf) + return cv.All(schema, cv.only_on_esp32) return schema diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index 1953aef035..a59cb8104b 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -5,11 +5,15 @@ #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" #include "esphome/components/display/display_color_utils.h" +#include "esphome/core/helpers.h" namespace esphome { namespace mipi_spi { constexpr static const char *const TAG = "display.mipi_spi"; + +// Maximum bytes to log for commands (truncated if larger) +static constexpr size_t MIPI_SPI_MAX_CMD_LOG_BYTES = 64; static constexpr uint8_t SW_RESET_CMD = 0x01; static constexpr uint8_t SLEEP_OUT = 0x11; static constexpr uint8_t NORON = 0x13; @@ -241,7 +245,10 @@ class MipiSpi : public display::Display, // Writes a command to the display, with the given bytes. void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { - esph_log_v(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty(bytes, len).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MIPI_SPI_MAX_CMD_LOG_BYTES)]; + esph_log_v(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len)); +#endif if constexpr (BUS_TYPE == BUS_TYPE_QUAD) { this->enable(); this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len); @@ -562,6 +569,12 @@ class MipiSpiBuffer : public MipiSpiget_clipping().is_set()) { + display::Display::fill(color); + return; + } + this->x_low_ = 0; this->y_low_ = this->start_line_; this->x_high_ = WIDTH - 1; diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 10ab4f3b5c..d80b7aeff5 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -1,4 +1,5 @@ #include "mitsubishi.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -6,6 +7,9 @@ namespace mitsubishi { static const char *const TAG = "mitsubishi.climate"; +// IR frame size for Mitsubishi climate +static constexpr size_t MITSUBISHI_FRAME_SIZE = 18; + const uint8_t MITSUBISHI_OFF = 0x00; const uint8_t MITSUBISHI_MODE_AUTO = 0x20; @@ -388,7 +392,10 @@ bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) { break; } - ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty(state_frame, 18).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MITSUBISHI_FRAME_SIZE)]; +#endif + ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty_to(hex_buf, state_frame, MITSUBISHI_FRAME_SIZE)); this->publish_state(); return true; diff --git a/esphome/components/mixer/speaker/__init__.py b/esphome/components/mixer/speaker/__init__.py index 46729f8eda..c4069851af 100644 --- a/esphome/components/mixer/speaker/__init__.py +++ b/esphome/components/mixer/speaker/__init__.py @@ -93,9 +93,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_NUM_CHANNELS): cv.int_range(min=1, max=2), cv.Optional(CONF_QUEUE_MODE, default=False): cv.boolean, - cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf - ), + cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean, } ), cv.only_on([PLATFORM_ESP32]), diff --git a/esphome/components/mmc5603/mmc5603.cpp b/esphome/components/mmc5603/mmc5603.cpp index f0d1044f3f..d6321eae8f 100644 --- a/esphome/components/mmc5603/mmc5603.cpp +++ b/esphome/components/mmc5603/mmc5603.cpp @@ -83,6 +83,7 @@ void MMC5603Component::dump_config() { ESP_LOGE(TAG, "The ID registers don't match - Is this really an MMC5603?"); } LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Auto set/reset: %s", ONOFF(this->auto_set_reset_)); LOG_SENSOR(" ", "X Axis", this->x_sensor_); LOG_SENSOR(" ", "Y Axis", this->y_sensor_); @@ -93,7 +94,8 @@ void MMC5603Component::dump_config() { float MMC5603Component::get_setup_priority() const { return setup_priority::DATA; } void MMC5603Component::update() { - if (!this->write_byte(MMC56X3_CTRL0_REG, 0x01)) { + uint8_t ctrl0 = (this->auto_set_reset_) ? 0x21 : 0x01; + if (!this->write_byte(MMC56X3_CTRL0_REG, ctrl0)) { this->status_set_warning(); return; } diff --git a/esphome/components/mmc5603/mmc5603.h b/esphome/components/mmc5603/mmc5603.h index cd0893053c..09718bd3b7 100644 --- a/esphome/components/mmc5603/mmc5603.h +++ b/esphome/components/mmc5603/mmc5603.h @@ -25,6 +25,7 @@ class MMC5603Component : public PollingComponent, public i2c::I2CDevice { void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; } + void set_auto_set_reset(bool auto_set_reset) { auto_set_reset_ = auto_set_reset; } protected: MMC5603Datarate datarate_; @@ -32,6 +33,7 @@ class MMC5603Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *y_sensor_{nullptr}; sensor::Sensor *z_sensor_{nullptr}; sensor::Sensor *heading_sensor_{nullptr}; + bool auto_set_reset_{true}; enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, diff --git a/esphome/components/mmc5603/sensor.py b/esphome/components/mmc5603/sensor.py index 3223225271..5b3982cee6 100644 --- a/esphome/components/mmc5603/sensor.py +++ b/esphome/components/mmc5603/sensor.py @@ -16,6 +16,8 @@ from esphome.const import ( UNIT_MICROTESLA, ) +CONF_AUTO_SET_RESET = "auto_set_reset" + DEPENDENCIES = ["i2c"] mmc5603_ns = cg.esphome_ns.namespace("mmc5603") @@ -54,6 +56,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, cv.Optional(CONF_HEADING): heading_schema, + cv.Optional(CONF_AUTO_SET_RESET, default=True): cv.boolean, } ) .extend(cv.polling_component_schema("60s")) @@ -88,3 +91,5 @@ async def to_code(config): if CONF_HEADING in config: sens = await sensor.new_sensor(config[CONF_HEADING]) cg.add(var.set_heading_sensor(sens)) + if CONF_AUTO_SET_RESET in config: + cg.add(var.set_auto_set_reset(config[CONF_AUTO_SET_RESET])) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 20271b4bdb..5e9387b843 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -8,6 +8,9 @@ namespace modbus { static const char *const TAG = "modbus"; +// Maximum bytes to log for Modbus frames (truncated if larger) +static constexpr size_t MODBUS_MAX_LOG_BYTES = 64; + void Modbus::setup() { if (this->flow_control_pin_ != nullptr) { this->flow_control_pin_->setup(); @@ -193,12 +196,12 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { } void Modbus::dump_config() { - ESP_LOGCONFIG(TAG, "Modbus:"); - LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); ESP_LOGCONFIG(TAG, + "Modbus:\n" " Send Wait Time: %d ms\n" " CRC Disabled: %s", this->send_wait_time_, YESNO(this->disable_crc_)); + LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); } float Modbus::get_setup_priority() const { // After UART bus @@ -255,7 +258,10 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address this->flow_control_pin_->digital_write(false); waiting_for_response = address; last_send_ = millis(); - ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data.data(), data.size())); } // Helper function for lambdas @@ -276,7 +282,10 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; - ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty_to(hex_buf, payload.data(), payload.size())); last_send_ = millis(); } diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index ea8467d5a3..4a3ec1fc41 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -1,5 +1,6 @@ #include #include "modbus_number.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -7,6 +8,9 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; +// Maximum uint16_t registers to log in verbose hex output +static constexpr size_t MODBUS_NUMBER_MAX_LOG_REGISTERS = 32; + void ModbusNumber::parse_and_publish(const std::vector &data) { float result = payload_to_float(data, *this) / this->multiply_by_; @@ -47,7 +51,11 @@ void ModbusNumber::control(float value) { } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus Number write raw: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_uint16_size(MODBUS_NUMBER_MAX_LOG_REGISTERS)]; +#endif + ESP_LOGV(TAG, "Modbus Number write raw: %s", + format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); write_cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, write_cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index 45e786a704..f02d9397ca 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -7,6 +7,9 @@ namespace modbus_controller { static const char *const TAG = "modbus_controller.output"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MODBUS_OUTPUT_MAX_LOG_BYTES = 64; + /** Write a value to the device * */ @@ -80,7 +83,11 @@ void ModbusBinaryOutput::write_state(bool state) { } } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus binary output write raw: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_OUTPUT_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus binary output write raw: %s", + format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index 21c4c1718d..68aa37c9ed 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -1,11 +1,15 @@ #include "modbus_switch.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { namespace modbus_controller { static const char *const TAG = "modbus_controller.switch"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MODBUS_SWITCH_MAX_LOG_BYTES = 64; + void ModbusSwitch::setup() { optional initial_state = Switch::get_initial_state_with_restore_mode(); if (initial_state.has_value()) { @@ -71,7 +75,11 @@ void ModbusSwitch::write_state(bool state) { } } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus Switch write raw: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MODBUS_SWITCH_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Modbus Switch write raw: %s", + format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/mopeka_ble/mopeka_ble.cpp b/esphome/components/mopeka_ble/mopeka_ble.cpp index 07c8ac5d71..b926beaff2 100644 --- a/esphome/components/mopeka_ble/mopeka_ble.cpp +++ b/esphome/components/mopeka_ble/mopeka_ble.cpp @@ -36,6 +36,7 @@ static const uint8_t MANUFACTURER_NRF52_DATA_LENGTH = 10; */ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; // Fetch information about BLE device. const auto &service_uuids = device.get_service_uuids(); if (service_uuids.size() != 1) { @@ -62,7 +63,7 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const bool sync_button_pressed = (manu_data.data[3] & 0x80) != 0; if (this->show_sensors_without_sync_ || sync_button_pressed) { - ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str().c_str()); + ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str_to(addr_buf)); } // Is the device maybe a Mopeka Pro (NRF52) sensor. @@ -78,7 +79,7 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const bool sync_button_pressed = (manu_data.data[2] & 0x80) != 0; if (this->show_sensors_without_sync_ || sync_button_pressed) { - ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str().c_str()); + ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str_to(addr_buf)); } } diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index 42d61f81a3..9bc9900a5a 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -31,7 +31,8 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); const auto &manu_datas = device.get_manufacturer_datas(); diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index 0d8340f95f..6322b550c9 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -13,11 +13,16 @@ static const uint16_t SERVICE_UUID = 0xADA0; static const uint8_t MANUFACTURER_DATA_LENGTH = 23; static const uint16_t MANUFACTURER_ID = 0x000D; +// Maximum bytes to log in very verbose hex output +static constexpr size_t MOPEKA_MAX_LOG_BYTES = 32; + void MopekaStdCheck::dump_config() { - ESP_LOGCONFIG(TAG, "Mopeka Std Check"); - ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100); - ESP_LOGCONFIG(TAG, " Tank distance empty: %" PRIi32 "mm", this->empty_mm_); - ESP_LOGCONFIG(TAG, " Tank distance full: %" PRIi32 "mm", this->full_mm_); + ESP_LOGCONFIG(TAG, + "Mopeka Std Check\n" + " Propane Butane mix: %.0f%%\n" + " Tank distance empty: %" PRIi32 "mm\n" + " Tank distance full: %" PRIi32 "mm", + this->propane_butane_mix_ * 100, this->empty_mm_, this->full_mm_); LOG_SENSOR(" ", "Level", this->level_); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -30,15 +35,17 @@ void MopekaStdCheck::dump_config() { * update the sensor state data. */ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - { - // Validate address. - if (device.address_uint64() != this->address_) { - return false; - } - - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + // Validate address. + if (device.address_uint64() != this->address_) { + return false; } + // Stack buffer for MAC address formatting - reused throughout function + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); + { // Validate service uuid const auto &service_uuids = device.get_service_uuids(); @@ -54,16 +61,20 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const auto &manu_datas = device.get_manufacturer_datas(); if (manu_datas.size() != 1) { - ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", addr_str, manu_datas.size()); return false; } const auto &manu_data = manu_datas[0]; - ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(MOPEKA_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", addr_str, + format_hex_pretty_to(hex_buf, manu_data.data.data(), manu_data.data.size())); if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { - ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", addr_str, manu_data.data.size()); return false; } @@ -73,21 +84,21 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF; if (static_cast(hardware_id) != STANDARD && static_cast(hardware_id) != XL && static_cast(hardware_id) != ETRAILER && static_cast(hardware_id) != STANDARD_ALT) { - ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id); + ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", addr_str, hardware_id); return false; } - ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate); - ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed); + ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", addr_str, mopeka_data->slow_update_rate); + ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", addr_str, mopeka_data->sync_pressed); for (u_int8_t i = 0; i < 3; i++) { - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1, - mopeka_data->val[i].value_0, mopeka_data->val[i].time_0); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2, - mopeka_data->val[i].value_1, mopeka_data->val[i].time_1); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3, - mopeka_data->val[i].value_2, mopeka_data->val[i].time_2); - ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4, - mopeka_data->val[i].value_3, mopeka_data->val[i].time_3); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 1, mopeka_data->val[i].value_0, + mopeka_data->val[i].time_0); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 2, mopeka_data->val[i].value_1, + mopeka_data->val[i].time_1); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 3, mopeka_data->val[i].value_2, + mopeka_data->val[i].time_2); + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 4, mopeka_data->val[i].value_3, + mopeka_data->val[i].time_3); } // Get battery level first @@ -154,12 +165,12 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } - ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(), - number_of_usable_values, best_value, best_time); + ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", addr_str, number_of_usable_values, best_value, + best_time); if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) { // At least two measurement values must be present. - ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str()); + ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", addr_str); if (this->distance_ != nullptr) { this->distance_->publish_state(0); } @@ -168,7 +179,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } else { float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c); - ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound); + ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", addr_str, lpg_speed_of_sound); uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f; diff --git a/esphome/components/mpr121/mpr121.cpp b/esphome/components/mpr121/mpr121.cpp index 5a8a8e7205..4b358e384c 100644 --- a/esphome/components/mpr121/mpr121.cpp +++ b/esphome/components/mpr121/mpr121.cpp @@ -153,10 +153,8 @@ void MPR121GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_ - 4, value != this->inverted_); } -std::string MPR121GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "ELE%u on MPR121", this->pin_); - return buffer; +size_t MPR121GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "ELE%u on MPR121", this->pin_); } } // namespace mpr121 diff --git a/esphome/components/mpr121/mpr121.h b/esphome/components/mpr121/mpr121.h index 6dd2c38309..085018fff0 100644 --- a/esphome/components/mpr121/mpr121.h +++ b/esphome/components/mpr121/mpr121.h @@ -109,7 +109,7 @@ class MPR121GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(MPR121Component *parent) { this->parent_ = parent; } void set_pin(uint8_t pin) { this->pin_ = pin; } diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 237ed2ce38..f53df5564c 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -77,6 +77,13 @@ CONF_DISCOVER_IP = "discover_ip" CONF_IDF_SEND_ASYNC = "idf_send_async" CONF_WAIT_FOR_CONNECTION = "wait_for_connection" +# Max lengths for stack-based topic building. +# These values are used in cv.Length() validators below to ensure the C++ code +# in mqtt_component.cpp can safely use fixed-size stack buffers without overflow. +# If you change these, update the corresponding constants in mqtt_component.cpp. +TOPIC_PREFIX_MAX_LEN = 64 # Default is device name, typically short +DISCOVERY_PREFIX_MAX_LEN = 64 # Default is "homeassistant" (13 chars) + def validate_message_just_topic(value): value = cv.publish_topic(value) @@ -106,6 +113,7 @@ MQTT_MESSAGE_SCHEMA = cv.Any( mqtt_ns = cg.esphome_ns.namespace("mqtt") MQTTMessage = mqtt_ns.struct("MQTTMessage") +MQTTClientDisconnectReason = mqtt_ns.enum("MQTTClientDisconnectReason") MQTTClientComponent = mqtt_ns.class_("MQTTClientComponent", cg.Component) MQTTPublishAction = mqtt_ns.class_("MQTTPublishAction", automation.Action) MQTTPublishJsonAction = mqtt_ns.class_("MQTTPublishJsonAction", automation.Action) @@ -117,9 +125,11 @@ MQTTMessageTrigger = mqtt_ns.class_( MQTTJsonMessageTrigger = mqtt_ns.class_( "MQTTJsonMessageTrigger", automation.Trigger.template(cg.JsonObjectConst) ) -MQTTConnectTrigger = mqtt_ns.class_("MQTTConnectTrigger", automation.Trigger.template()) +MQTTConnectTrigger = mqtt_ns.class_( + "MQTTConnectTrigger", automation.Trigger.template(cg.bool_) +) MQTTDisconnectTrigger = mqtt_ns.class_( - "MQTTDisconnectTrigger", automation.Trigger.template() + "MQTTDisconnectTrigger", automation.Trigger.template(MQTTClientDisconnectReason) ) MQTTComponent = mqtt_ns.class_("MQTTComponent", cg.Component) MQTTConnectedCondition = mqtt_ns.class_("MQTTConnectedCondition", Condition) @@ -233,11 +243,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PASSWORD, default=""): cv.string, cv.Optional(CONF_CLEAN_SESSION, default=False): cv.boolean, cv.Optional(CONF_CLIENT_ID): cv.string, - cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf + cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32=False): cv.All( + cv.boolean, cv.only_on_esp32 ), cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All( - cv.string, cv.only_with_esp_idf + cv.string, cv.only_on_esp32 ), cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All( cv.string, cv.only_on_esp32 @@ -245,17 +255,17 @@ CONFIG_SCHEMA = cv.All( cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All( cv.string, cv.only_on_esp32 ), - cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf + cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32=False): cv.All( + cv.boolean, cv.only_on_esp32 ), cv.Optional(CONF_DISCOVERY, default=True): cv.Any( cv.boolean, cv.one_of("CLEAN", upper=True) ), cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, cv.Optional(CONF_DISCOVER_IP, default=True): cv.boolean, - cv.Optional( - CONF_DISCOVERY_PREFIX, default="homeassistant" - ): cv.publish_topic, + cv.Optional(CONF_DISCOVERY_PREFIX, default="homeassistant"): cv.All( + cv.publish_topic, cv.Length(max=DISCOVERY_PREFIX_MAX_LEN) + ), cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum( MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS ), @@ -266,7 +276,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA, - cv.Optional(CONF_TOPIC_PREFIX, default=lambda: CORE.name): cv.publish_topic, + cv.Optional(CONF_TOPIC_PREFIX, default=lambda: CORE.name): cv.All( + cv.publish_topic, cv.Length(max=TOPIC_PREFIX_MAX_LEN) + ), cv.Optional(CONF_LOG_TOPIC): cv.Any( None, MQTT_MESSAGE_BASE.extend( @@ -338,6 +350,7 @@ def exp_mqtt_message(config): async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + # Add required libraries for ESP8266 and LibreTiny if CORE.is_esp8266 or CORE.is_libretiny: # https://github.com/heman/async-mqtt-client/blob/master/library.json @@ -420,6 +433,8 @@ async def to_code(config): cg.add(var.disable_log_message()) else: cg.add(var.set_log_message_template(exp_mqtt_message(log_topic))) + # Request a log listener slot only when log topic is enabled + logger.request_log_listener() if CONF_LEVEL in log_topic: cg.add(var.set_log_level(logger.LOG_LEVELS[log_topic[CONF_LEVEL]])) @@ -466,11 +481,15 @@ async def to_code(config): for conf in config.get(CONF_ON_CONNECT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_automation( + trigger, [(cg.bool_, "session_present")], conf + ) for conf in config.get(CONF_ON_DISCONNECT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_automation( + trigger, [(MQTTClientDisconnectReason, "reason")], conf + ) cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) @@ -556,9 +575,13 @@ async def register_mqtt_component(var, config): if not config.get(CONF_DISCOVERY, True): cg.add(var.disable_discovery()) if CONF_STATE_TOPIC in config: - cg.add(var.set_custom_state_topic(config[CONF_STATE_TOPIC])) + state_topic = await cg.templatable(config[CONF_STATE_TOPIC], [], cg.std_string) + cg.add(var.set_custom_state_topic(state_topic)) if CONF_COMMAND_TOPIC in config: - cg.add(var.set_custom_command_topic(config[CONF_COMMAND_TOPIC])) + command_topic = await cg.templatable( + config[CONF_COMMAND_TOPIC], [], cg.std_string + ) + cg.add(var.set_custom_command_topic(command_topic)) if CONF_COMMAND_RETAIN in config: cg.add(var.set_command_retain(config[CONF_COMMAND_RETAIN])) if CONF_AVAILABILITY in config: diff --git a/esphome/components/mqtt/custom_mqtt_device.cpp b/esphome/components/mqtt/custom_mqtt_device.cpp index 787cc1153f..7ff65bb42c 100644 --- a/esphome/components/mqtt/custom_mqtt_device.cpp +++ b/esphome/components/mqtt/custom_mqtt_device.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.custom"; @@ -13,13 +12,14 @@ bool CustomMQTTDevice::publish(const std::string &topic, const std::string &payl return global_mqtt_client->publish(topic, payload, qos, retain); } bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t number_decimals) { - auto str = value_accuracy_to_string(value, number_decimals); - return this->publish(topic, str); + char buf[VALUE_ACCURACY_MAX_LEN]; + size_t len = value_accuracy_to_buf(buf, value, number_decimals); + return global_mqtt_client->publish(topic, buf, len); } bool CustomMQTTDevice::publish(const std::string &topic, int value) { char buffer[24]; - sprintf(buffer, "%d", value); - return this->publish(topic, buffer); + int len = snprintf(buffer, sizeof(buffer), "%d", value); + return global_mqtt_client->publish(topic, buffer, len); } bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) { return global_mqtt_client->publish_json(topic, f, qos, retain); @@ -29,7 +29,6 @@ bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_b } bool CustomMQTTDevice::is_connected() { return global_mqtt_client != nullptr && global_mqtt_client->is_connected(); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/custom_mqtt_device.h b/esphome/components/mqtt/custom_mqtt_device.h index 0852a17cf1..09ed7bd6d1 100644 --- a/esphome/components/mqtt/custom_mqtt_device.h +++ b/esphome/components/mqtt/custom_mqtt_device.h @@ -6,8 +6,7 @@ #include "esphome/core/component.h" #include "mqtt_client.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /** This class is a helper class for custom components that communicate using * MQTT. It has 5 helper functions that you can use (square brackets indicate optional): @@ -214,7 +213,6 @@ void CustomMQTTDevice::subscribe_json(const std::string &topic, void (T::*callba global_mqtt_client->subscribe_json(topic, f, qos); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index dd3df5f8aa..6245d10882 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_ALARM_CONTROL_PANEL -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.alarm_control_panel"; @@ -59,28 +58,28 @@ void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendD JsonArray supported_features = root[MQTT_SUPPORTED_FEATURES].to(); const uint32_t acp_supported_features = this->alarm_control_panel_->get_supported_features(); if (acp_supported_features & ACP_FEAT_ARM_AWAY) { - supported_features.add("arm_away"); + supported_features.add(ESPHOME_F("arm_away")); } if (acp_supported_features & ACP_FEAT_ARM_HOME) { - supported_features.add("arm_home"); + supported_features.add(ESPHOME_F("arm_home")); } if (acp_supported_features & ACP_FEAT_ARM_NIGHT) { - supported_features.add("arm_night"); + supported_features.add(ESPHOME_F("arm_night")); } if (acp_supported_features & ACP_FEAT_ARM_VACATION) { - supported_features.add("arm_vacation"); + supported_features.add(ESPHOME_F("arm_vacation")); } if (acp_supported_features & ACP_FEAT_ARM_CUSTOM_BYPASS) { - supported_features.add("arm_custom_bypass"); + supported_features.add(ESPHOME_F("arm_custom_bypass")); } if (acp_supported_features & ACP_FEAT_TRIGGER) { - supported_features.add("trigger"); + supported_features.add(ESPHOME_F("trigger")); } root[MQTT_CODE_DISARM_REQUIRED] = this->alarm_control_panel_->get_requires_code(); root[MQTT_CODE_ARM_REQUIRED] = this->alarm_control_panel_->get_requires_code_to_arm(); } -std::string MQTTAlarmControlPanelComponent::component_type() const { return "alarm_control_panel"; } +MQTT_COMPONENT_TYPE(MQTTAlarmControlPanelComponent, "alarm_control_panel") const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return this->alarm_control_panel_; } bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); } @@ -123,8 +122,7 @@ bool MQTTAlarmControlPanelComponent::publish_state() { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.h b/esphome/components/mqtt/mqtt_alarm_control_panel.h index 4ad37b7314..89a0ff1be8 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.h +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.h @@ -8,8 +8,7 @@ #include "mqtt_component.h" #include "esphome/components/alarm_control_panel/alarm_control_panel.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent { public: @@ -26,14 +25,13 @@ class MQTTAlarmControlPanelComponent : public mqtt::MQTTComponent { void dump_config() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; alarm_control_panel::AlarmControlPanel *alarm_control_panel_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_backend.h b/esphome/components/mqtt/mqtt_backend.h index 0c1720ec34..a7e3f1013d 100644 --- a/esphome/components/mqtt/mqtt_backend.h +++ b/esphome/components/mqtt/mqtt_backend.h @@ -6,8 +6,7 @@ #include "esphome/components/network/ip_address.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { enum class MQTTClientDisconnectReason : int8_t { TCP_DISCONNECTED = 0, @@ -67,6 +66,5 @@ class MQTTBackend { virtual void loop() {} }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index dcc51ed60e..c12c79499f 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -8,8 +8,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.idf"; @@ -166,10 +165,12 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) { case MQTT_EVENT_ERROR: ESP_LOGE(TAG, "MQTT_EVENT_ERROR"); if (event.error_handle.error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { - ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle.esp_tls_last_esp_err); - ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle.esp_tls_stack_err); - ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle.esp_transport_sock_errno, - strerror(event.error_handle.esp_transport_sock_errno)); + ESP_LOGE(TAG, + "Last error code reported from esp-tls: 0x%x\n" + "Last tls stack error number: 0x%x\n" + "Last captured errno : %d (%s)", + event.error_handle.esp_tls_last_esp_err, event.error_handle.esp_tls_stack_err, + event.error_handle.esp_transport_sock_errno, strerror(event.error_handle.esp_transport_sock_errno)); } else if (event.error_handle.error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle.connect_return_code); } else { @@ -232,16 +233,6 @@ void MQTTBackendESP32::esphome_mqtt_task(void *params) { this_mqtt->mqtt_event_pool_.release(elem); } } - - // Clean up any remaining items in the queue - struct QueueElement *elem; - while ((elem = this_mqtt->mqtt_queue_.pop()) != nullptr) { - this_mqtt->mqtt_event_pool_.release(elem); - } - - // Note: EventPool destructor will clean up the pool itself - // Task will delete itself - vTaskDelete(nullptr); } bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, bool retain, const char *payload, @@ -278,7 +269,6 @@ bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, } #endif // USE_MQTT_IDF_ENQUEUE -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_ESP32 #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index a24e75eaf9..bd2d2a67b2 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -15,8 +15,7 @@ #include "esphome/core/lock_free_queue.h" #include "esphome/core/event_pool.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { struct Event { esp_mqtt_event_id_t event_id; @@ -273,8 +272,7 @@ class MQTTBackendESP32 final : public MQTTBackend { #endif }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif diff --git a/esphome/components/mqtt/mqtt_backend_esp8266.h b/esphome/components/mqtt/mqtt_backend_esp8266.h index a979634bf4..470d1e6a8b 100644 --- a/esphome/components/mqtt/mqtt_backend_esp8266.h +++ b/esphome/components/mqtt/mqtt_backend_esp8266.h @@ -6,8 +6,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBackendESP8266 final : public MQTTBackend { public: @@ -67,8 +66,7 @@ class MQTTBackendESP8266 final : public MQTTBackend { AsyncMqttClient mqtt_client_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // defined(USE_ESP8266) #endif diff --git a/esphome/components/mqtt/mqtt_backend_libretiny.h b/esphome/components/mqtt/mqtt_backend_libretiny.h index 2578ae9941..24bf018a90 100644 --- a/esphome/components/mqtt/mqtt_backend_libretiny.h +++ b/esphome/components/mqtt/mqtt_backend_libretiny.h @@ -6,8 +6,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBackendLibreTiny final : public MQTTBackend { public: @@ -67,8 +66,7 @@ class MQTTBackendLibreTiny final : public MQTTBackend { AsyncMqttClient mqtt_client_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // defined(USE_LIBRETINY) #endif diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 479cee205a..a37043406b 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -6,12 +6,11 @@ #ifdef USE_MQTT #ifdef USE_BINARY_SENSOR -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.binary_sensor"; -std::string MQTTBinarySensorComponent::component_type() const { return "binary_sensor"; } +MQTT_COMPONENT_TYPE(MQTTBinarySensorComponent, "binary_sensor") const EntityBase *MQTTBinarySensorComponent::get_entity() const { return this->binary_sensor_; } void MQTTBinarySensorComponent::setup() { @@ -57,8 +56,7 @@ bool MQTTBinarySensorComponent::publish_state(bool state) { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_binary_sensor.h b/esphome/components/mqtt/mqtt_binary_sensor.h index f6579fcd19..5917a9966c 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.h +++ b/esphome/components/mqtt/mqtt_binary_sensor.h @@ -7,8 +7,7 @@ #include "mqtt_component.h" #include "esphome/components/binary_sensor/binary_sensor.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTBinarySensorComponent : public mqtt::MQTTComponent { public: @@ -30,14 +29,13 @@ class MQTTBinarySensorComponent : public mqtt::MQTTComponent { bool publish_state(bool state); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; binary_sensor::BinarySensor *binary_sensor_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index f8eb0eab2d..718fe93016 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_BUTTON -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.button"; @@ -40,11 +39,10 @@ void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } -std::string MQTTButtonComponent::component_type() const { return "button"; } +MQTT_COMPONENT_TYPE(MQTTButtonComponent, "button") const EntityBase *MQTTButtonComponent::get_entity() const { return this->button_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_button.h b/esphome/components/mqtt/mqtt_button.h index 42389caecc..a2db64d39d 100644 --- a/esphome/components/mqtt/mqtt_button.h +++ b/esphome/components/mqtt/mqtt_button.h @@ -8,8 +8,7 @@ #include "esphome/components/button/button.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTButtonComponent : public mqtt::MQTTComponent { public: @@ -27,14 +26,13 @@ class MQTTButtonComponent : public mqtt::MQTTComponent { protected: /// "button" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; button::Button *button_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index ba701b90a3..0ab5b238b5 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -22,15 +22,15 @@ #include "esphome/components/dashboard_import/dashboard_import.h" #endif -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt"; MQTTClientComponent::MQTTClientComponent() { global_mqtt_client = this; - const std::string mac_addr = get_mac_address(); - this->credentials_.client_id = make_name_with_suffix(App.get_name(), '-', mac_addr.c_str(), mac_addr.size()); + char mac_addr[MAC_ADDRESS_BUFFER_SIZE]; + get_mac_address_into_buffer(mac_addr); + this->credentials_.client_id = make_name_with_suffix(App.get_name(), '-', mac_addr, MAC_ADDRESS_BUFFER_SIZE - 1); } // Connection @@ -95,45 +95,48 @@ void MQTTClientComponent::send_device_info_() { index++; } } - root["name"] = App.get_name(); + root[ESPHOME_F("name")] = App.get_name(); if (!App.get_friendly_name().empty()) { - root["friendly_name"] = App.get_friendly_name(); + root[ESPHOME_F("friendly_name")] = App.get_friendly_name(); } #ifdef USE_API - root["port"] = api::global_api_server->get_port(); + root[ESPHOME_F("port")] = api::global_api_server->get_port(); #endif - root["version"] = ESPHOME_VERSION; - root["mac"] = get_mac_address(); + root[ESPHOME_F("version")] = ESPHOME_VERSION; + char mac_buf[MAC_ADDRESS_BUFFER_SIZE]; + get_mac_address_into_buffer(mac_buf); + root[ESPHOME_F("mac")] = mac_buf; #ifdef USE_ESP8266 - root["platform"] = "ESP8266"; + root[ESPHOME_F("platform")] = ESPHOME_F("ESP8266"); #endif #ifdef USE_ESP32 - root["platform"] = "ESP32"; + root[ESPHOME_F("platform")] = ESPHOME_F("ESP32"); #endif #ifdef USE_LIBRETINY - root["platform"] = lt_cpu_get_model_name(); + root[ESPHOME_F("platform")] = lt_cpu_get_model_name(); #endif - root["board"] = ESPHOME_BOARD; + root[ESPHOME_F("board")] = ESPHOME_BOARD; #if defined(USE_WIFI) - root["network"] = "wifi"; + root[ESPHOME_F("network")] = ESPHOME_F("wifi"); #elif defined(USE_ETHERNET) - root["network"] = "ethernet"; + root[ESPHOME_F("network")] = ESPHOME_F("ethernet"); #endif #ifdef ESPHOME_PROJECT_NAME - root["project_name"] = ESPHOME_PROJECT_NAME; - root["project_version"] = ESPHOME_PROJECT_VERSION; + root[ESPHOME_F("project_name")] = ESPHOME_PROJECT_NAME; + root[ESPHOME_F("project_version")] = ESPHOME_PROJECT_VERSION; #endif // ESPHOME_PROJECT_NAME #ifdef USE_DASHBOARD_IMPORT - root["package_import_url"] = dashboard_import::get_package_import_url(); + root[ESPHOME_F("package_import_url")] = dashboard_import::get_package_import_url(); #endif #ifdef USE_API_NOISE - root[api::global_api_server->get_noise_ctx().has_psk() ? "api_encryption" : "api_encryption_supported"] = - "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; + root[api::global_api_server->get_noise_ctx().has_psk() ? ESPHOME_F("api_encryption") + : ESPHOME_F("api_encryption_supported")] = + ESPHOME_F("Noise_NNpsk0_25519_ChaChaPoly_SHA256"); #endif }, 2, this->discovery_info_.retain); @@ -153,15 +156,18 @@ void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *mes #endif void MQTTClientComponent::dump_config() { + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + // clang-format off ESP_LOGCONFIG(TAG, "MQTT:\n" " Server Address: %s:%u (%s)\n" " Username: " LOG_SECRET("'%s'") "\n" - " Client ID: " LOG_SECRET("'%s'") "\n" - " Clean Session: %s", - this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str().c_str(), + " Client ID: " LOG_SECRET("'%s'") "\n" + " Clean Session: %s", + this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str_to(ip_buf), this->credentials_.username.c_str(), this->credentials_.client_id.c_str(), YESNO(this->credentials_.clean_session)); + // clang-format on if (this->is_discovery_ip_enabled()) { ESP_LOGCONFIG(TAG, " Discovery IP enabled"); } @@ -246,7 +252,8 @@ void MQTTClientComponent::check_dnslookup_() { return; } - ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str().c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str_to(ip_buf)); this->start_connect_(); } #if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1 @@ -747,7 +754,6 @@ void MQTTMessageTrigger::dump_config() { } float MQTTMessageTrigger::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 8547fe337f..9e9db03b19 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -24,8 +24,7 @@ #include -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /** Callback for MQTT events. */ @@ -379,17 +378,17 @@ class MQTTJsonMessageTrigger : public Trigger { } }; -class MQTTConnectTrigger : public Trigger<> { +class MQTTConnectTrigger : public Trigger { public: explicit MQTTConnectTrigger(MQTTClientComponent *&client) { - client->set_on_connect([this](bool session_present) { this->trigger(); }); + client->set_on_connect([this](bool session_present) { this->trigger(session_present); }); } }; -class MQTTDisconnectTrigger : public Trigger<> { +class MQTTDisconnectTrigger : public Trigger { public: explicit MQTTDisconnectTrigger(MQTTClientComponent *&client) { - client->set_on_disconnect([this](MQTTClientDisconnectReason reason) { this->trigger(); }); + client->set_on_disconnect([this](MQTTClientDisconnectReason reason) { this->trigger(reason); }); } }; @@ -462,7 +461,6 @@ template class MQTTDisableAction : public Action { MQTTClientComponent *parent_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index aee2b38942..37d643f9e7 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_CLIMATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.climate"; @@ -32,18 +31,18 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo JsonArray modes = root[MQTT_MODES].to(); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) - modes.add("auto"); - modes.add("off"); + modes.add(ESPHOME_F("auto")); + modes.add(ESPHOME_F("off")); if (traits.supports_mode(CLIMATE_MODE_COOL)) - modes.add("cool"); + modes.add(ESPHOME_F("cool")); if (traits.supports_mode(CLIMATE_MODE_HEAT)) - modes.add("heat"); + modes.add(ESPHOME_F("heat")); if (traits.supports_mode(CLIMATE_MODE_FAN_ONLY)) - modes.add("fan_only"); + modes.add(ESPHOME_F("fan_only")); if (traits.supports_mode(CLIMATE_MODE_DRY)) - modes.add("dry"); + modes.add(ESPHOME_F("dry")); if (traits.supports_mode(CLIMATE_MODE_HEAT_COOL)) - modes.add("heat_cool"); + modes.add(ESPHOME_F("heat_cool")); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { @@ -91,21 +90,21 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // preset_mode_state_topic root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic(); // presets - JsonArray presets = root["preset_modes"].to(); + JsonArray presets = root[ESPHOME_F("preset_modes")].to(); if (traits.supports_preset(CLIMATE_PRESET_HOME)) - presets.add("home"); + presets.add(ESPHOME_F("home")); if (traits.supports_preset(CLIMATE_PRESET_AWAY)) - presets.add("away"); + presets.add(ESPHOME_F("away")); if (traits.supports_preset(CLIMATE_PRESET_BOOST)) - presets.add("boost"); + presets.add(ESPHOME_F("boost")); if (traits.supports_preset(CLIMATE_PRESET_COMFORT)) - presets.add("comfort"); + presets.add(ESPHOME_F("comfort")); if (traits.supports_preset(CLIMATE_PRESET_ECO)) - presets.add("eco"); + presets.add(ESPHOME_F("eco")); if (traits.supports_preset(CLIMATE_PRESET_SLEEP)) - presets.add("sleep"); + presets.add(ESPHOME_F("sleep")); if (traits.supports_preset(CLIMATE_PRESET_ACTIVITY)) - presets.add("activity"); + presets.add(ESPHOME_F("activity")); for (const auto &preset : traits.get_supported_custom_presets()) presets.add(preset); } @@ -121,27 +120,27 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // fan_mode_state_topic root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes - JsonArray fan_modes = root["fan_modes"].to(); + JsonArray fan_modes = root[ESPHOME_F("fan_modes")].to(); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) - fan_modes.add("on"); + fan_modes.add(ESPHOME_F("on")); if (traits.supports_fan_mode(CLIMATE_FAN_OFF)) - fan_modes.add("off"); + fan_modes.add(ESPHOME_F("off")); if (traits.supports_fan_mode(CLIMATE_FAN_AUTO)) - fan_modes.add("auto"); + fan_modes.add(ESPHOME_F("auto")); if (traits.supports_fan_mode(CLIMATE_FAN_LOW)) - fan_modes.add("low"); + fan_modes.add(ESPHOME_F("low")); if (traits.supports_fan_mode(CLIMATE_FAN_MEDIUM)) - fan_modes.add("medium"); + fan_modes.add(ESPHOME_F("medium")); if (traits.supports_fan_mode(CLIMATE_FAN_HIGH)) - fan_modes.add("high"); + fan_modes.add(ESPHOME_F("high")); if (traits.supports_fan_mode(CLIMATE_FAN_MIDDLE)) - fan_modes.add("middle"); + fan_modes.add(ESPHOME_F("middle")); if (traits.supports_fan_mode(CLIMATE_FAN_FOCUS)) - fan_modes.add("focus"); + fan_modes.add(ESPHOME_F("focus")); if (traits.supports_fan_mode(CLIMATE_FAN_DIFFUSE)) - fan_modes.add("diffuse"); + fan_modes.add(ESPHOME_F("diffuse")); if (traits.supports_fan_mode(CLIMATE_FAN_QUIET)) - fan_modes.add("quiet"); + fan_modes.add(ESPHOME_F("quiet")); for (const auto &fan_mode : traits.get_supported_custom_fan_modes()) fan_modes.add(fan_mode); } @@ -152,15 +151,15 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo // swing_mode_state_topic root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes - JsonArray swing_modes = root["swing_modes"].to(); + JsonArray swing_modes = root[ESPHOME_F("swing_modes")].to(); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) - swing_modes.add("off"); + swing_modes.add(ESPHOME_F("off")); if (traits.supports_swing_mode(CLIMATE_SWING_BOTH)) - swing_modes.add("both"); + swing_modes.add(ESPHOME_F("both")); if (traits.supports_swing_mode(CLIMATE_SWING_VERTICAL)) - swing_modes.add("vertical"); + swing_modes.add(ESPHOME_F("vertical")); if (traits.supports_swing_mode(CLIMATE_SWING_HORIZONTAL)) - swing_modes.add("horizontal"); + swing_modes.add(ESPHOME_F("horizontal")); } config.state_topic = false; @@ -255,7 +254,7 @@ void MQTTClimateComponent::setup() { } MQTTClimateComponent::MQTTClimateComponent(Climate *device) : device_(device) {} bool MQTTClimateComponent::send_initial_state() { return this->publish_state_(); } -std::string MQTTClimateComponent::component_type() const { return "climate"; } +MQTT_COMPONENT_TYPE(MQTTClimateComponent, "climate") const EntityBase *MQTTClimateComponent::get_entity() const { return this->device_; } bool MQTTClimateComponent::publish_state_() { @@ -292,36 +291,38 @@ bool MQTTClimateComponent::publish_state_() { success = false; int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); + char payload[VALUE_ACCURACY_MAX_LEN]; + size_t len; if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE) && !std::isnan(this->device_->current_temperature)) { - std::string payload = value_accuracy_to_string(this->device_->current_temperature, current_accuracy); - if (!this->publish(this->get_current_temperature_state_topic(), payload)) + len = value_accuracy_to_buf(payload, this->device_->current_temperature, current_accuracy); + if (!this->publish(this->get_current_temperature_state_topic(), payload, len)) success = false; } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { - std::string payload = value_accuracy_to_string(this->device_->target_temperature_low, target_accuracy); - if (!this->publish(this->get_target_temperature_low_state_topic(), payload)) + len = value_accuracy_to_buf(payload, this->device_->target_temperature_low, target_accuracy); + if (!this->publish(this->get_target_temperature_low_state_topic(), payload, len)) success = false; - payload = value_accuracy_to_string(this->device_->target_temperature_high, target_accuracy); - if (!this->publish(this->get_target_temperature_high_state_topic(), payload)) + len = value_accuracy_to_buf(payload, this->device_->target_temperature_high, target_accuracy); + if (!this->publish(this->get_target_temperature_high_state_topic(), payload, len)) success = false; } else { - std::string payload = value_accuracy_to_string(this->device_->target_temperature, target_accuracy); - if (!this->publish(this->get_target_temperature_state_topic(), payload)) + len = value_accuracy_to_buf(payload, this->device_->target_temperature, target_accuracy); + if (!this->publish(this->get_target_temperature_state_topic(), payload, len)) success = false; } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY) && !std::isnan(this->device_->current_humidity)) { - std::string payload = value_accuracy_to_string(this->device_->current_humidity, 0); - if (!this->publish(this->get_current_humidity_state_topic(), payload)) + len = value_accuracy_to_buf(payload, this->device_->current_humidity, 0); + if (!this->publish(this->get_current_humidity_state_topic(), payload, len)) success = false; } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY) && !std::isnan(this->device_->target_humidity)) { - std::string payload = value_accuracy_to_string(this->device_->target_humidity, 0); - if (!this->publish(this->get_target_humidity_state_topic(), payload)) + len = value_accuracy_to_buf(payload, this->device_->target_humidity, 0); + if (!this->publish(this->get_target_humidity_state_topic(), payload, len)) success = false; } @@ -358,7 +359,7 @@ bool MQTTClimateComponent::publish_state_() { } } if (this->device_->has_custom_preset()) - payload = this->device_->get_custom_preset(); + payload = this->device_->get_custom_preset().c_str(); if (!this->publish(this->get_preset_state_topic(), payload)) success = false; } @@ -430,7 +431,7 @@ bool MQTTClimateComponent::publish_state_() { } } if (this->device_->has_custom_fan_mode()) - payload = this->device_->get_custom_fan_mode(); + payload = this->device_->get_custom_fan_mode().c_str(); if (!this->publish(this->get_fan_mode_state_topic(), payload)) success = false; } @@ -460,8 +461,7 @@ bool MQTTClimateComponent::publish_state_() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_climate.h b/esphome/components/mqtt/mqtt_climate.h index 4e54230e68..f0715929d4 100644 --- a/esphome/components/mqtt/mqtt_climate.h +++ b/esphome/components/mqtt/mqtt_climate.h @@ -8,15 +8,14 @@ #include "esphome/components/climate/climate.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTClimateComponent : public mqtt::MQTTComponent { public: MQTTClimateComponent(climate::Climate *device); void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; bool send_initial_state() override; - std::string component_type() const override; + const char *component_type() const override; void setup() override; MQTT_COMPONENT_CUSTOM_TOPIC(current_temperature, state) @@ -49,8 +48,7 @@ class MQTTClimateComponent : public mqtt::MQTTComponent { climate::Climate *device_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 5d2bedae79..20c111de43 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -9,11 +9,38 @@ #include "mqtt_const.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.component"; +// Helper functions for building topic strings on stack +inline char *append_str(char *p, const char *s, size_t len) { + memcpy(p, s, len); + return p + len; +} + +inline char *append_char(char *p, char c) { + *p = c; + return p + 1; +} + +// Max lengths for stack-based topic building. +// These limits are enforced at Python config validation time in mqtt/__init__.py +// using cv.Length() validators for topic_prefix and discovery_prefix. +// MQTT_COMPONENT_TYPE_MAX_LEN and MQTT_SUFFIX_MAX_LEN are defined in mqtt_component.h. +// ESPHOME_DEVICE_NAME_MAX_LEN and OBJECT_ID_MAX_LEN are defined in entity_base.h. +// This ensures the stack buffers below are always large enough. +static constexpr size_t TOPIC_PREFIX_MAX_LEN = 64; // Validated in Python: cv.Length(max=64) +static constexpr size_t DISCOVERY_PREFIX_MAX_LEN = 64; // Validated in Python: cv.Length(max=64) + +// Stack buffer sizes - safe because all inputs are length-validated at config time +// Format: prefix + "/" + type + "/" + object_id + "/" + suffix + null +static constexpr size_t DEFAULT_TOPIC_MAX_LEN = + TOPIC_PREFIX_MAX_LEN + 1 + MQTT_COMPONENT_TYPE_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1 + MQTT_SUFFIX_MAX_LEN + 1; +// Format: prefix + "/" + type + "/" + name + "/" + object_id + "/config" + null +static constexpr size_t DISCOVERY_TOPIC_MAX_LEN = DISCOVERY_PREFIX_MAX_LEN + 1 + MQTT_COMPONENT_TYPE_MAX_LEN + 1 + + ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 7 + 1; + void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; } void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos; } @@ -22,8 +49,23 @@ void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { std::string sanitized_name = str_sanitize(App.get_name()); - return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" + - this->get_default_object_id_() + "/config"; + const char *comp_type = this->component_type(); + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = this->get_default_object_id_to_(object_id_buf); + + char buf[DISCOVERY_TOPIC_MAX_LEN]; + char *p = buf; + + p = append_str(p, discovery_info.prefix.data(), discovery_info.prefix.size()); + p = append_char(p, '/'); + p = append_str(p, comp_type, strlen(comp_type)); + p = append_char(p, '/'); + p = append_str(p, sanitized_name.data(), sanitized_name.size()); + p = append_char(p, '/'); + p = append_str(p, object_id.c_str(), object_id.size()); + p = append_str(p, "/config", 7); + + return std::string(buf, p - buf); } std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) const { @@ -33,25 +75,44 @@ std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) con return ""; } - return topic_prefix + "/" + this->component_type() + "/" + this->get_default_object_id_() + "/" + suffix; + const char *comp_type = this->component_type(); + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = this->get_default_object_id_to_(object_id_buf); + + char buf[DEFAULT_TOPIC_MAX_LEN]; + char *p = buf; + + p = append_str(p, topic_prefix.data(), topic_prefix.size()); + p = append_char(p, '/'); + p = append_str(p, comp_type, strlen(comp_type)); + p = append_char(p, '/'); + p = append_str(p, object_id.c_str(), object_id.size()); + p = append_char(p, '/'); + p = append_str(p, suffix.data(), suffix.size()); + + return std::string(buf, p - buf); } std::string MQTTComponent::get_state_topic_() const { - if (this->has_custom_state_topic_) - return this->custom_state_topic_.str(); + if (this->custom_state_topic_.has_value()) + return this->custom_state_topic_.value(); return this->get_default_topic_for_("state"); } std::string MQTTComponent::get_command_topic_() const { - if (this->has_custom_command_topic_) - return this->custom_command_topic_.str(); + if (this->custom_command_topic_.has_value()) + return this->custom_command_topic_.value(); return this->get_default_topic_for_("command"); } bool MQTTComponent::publish(const std::string &topic, const std::string &payload) { + return this->publish(topic, payload.data(), payload.size()); +} + +bool MQTTComponent::publish(const std::string &topic, const char *payload, size_t payload_length) { if (topic.empty()) return false; - return global_mqtt_client->publish(topic, payload, this->qos_, this->retain_); + return global_mqtt_client->publish(topic, payload, payload_length, this->qos_, this->retain_); } bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) { @@ -124,27 +185,36 @@ bool MQTTComponent::send_discovery_() { } const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = this->get_default_object_id_to_(object_id_buf); if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { char friendly_name_hash[9]; sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_())); friendly_name_hash[8] = 0; // ensure the hash-string ends with null - root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash; + // Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678") + // MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43 + char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11]; + char mac_buf[MAC_ADDRESS_BUFFER_SIZE]; + get_mac_address_into_buffer(mac_buf); + snprintf(unique_id, sizeof(unique_id), "%s-%s-%s", mac_buf, this->component_type(), friendly_name_hash); + root[MQTT_UNIQUE_ID] = unique_id; } else { // default to almost-unique ID. It's a hack but the only way to get that // gorgeous device registry view. - root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); + root[MQTT_UNIQUE_ID] = "ESP" + std::string(this->component_type()) + object_id.c_str(); } const std::string &node_name = App.get_name(); if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) - root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_(); + root[MQTT_OBJECT_ID] = node_name + "_" + object_id.c_str(); const std::string &friendly_name_ref = App.get_friendly_name(); const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref; std::string node_area = App.get_area(); JsonObject device_info = root[MQTT_DEVICE].to(); - const auto mac = get_mac_address(); + char mac[MAC_ADDRESS_BUFFER_SIZE]; + get_mac_address_into_buffer(mac); device_info[MQTT_DEVICE_IDENTIFIERS] = mac; device_info[MQTT_DEVICE_NAME] = node_friendly_name; #ifdef ESPHOME_PROJECT_NAME @@ -154,7 +224,15 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_MANUFACTURER] = model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME); #else - device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_VERSION " (" + App.get_compilation_time_ref() + ")"; + static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")"; +#ifdef USE_ESP8266 + char fmt_buf[sizeof(ver_fmt)]; + strcpy_P(fmt_buf, ver_fmt); + const char *fmt = fmt_buf; +#else + const char *fmt = ver_fmt; +#endif + device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(fmt, App.get_config_hash()); device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; #if defined(USE_ESP8266) || defined(USE_ESP32) device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif"; @@ -187,10 +265,6 @@ bool MQTTComponent::is_discovery_enabled() const { return this->discovery_enabled_ && global_mqtt_client->is_discovery_enabled(); } -std::string MQTTComponent::get_default_object_id_() const { - return str_sanitize(str_snake_case(this->friendly_name_())); -} - void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) { global_mqtt_client->subscribe(topic, std::move(callback), qos); } @@ -203,14 +277,6 @@ MQTTComponent::MQTTComponent() = default; float MQTTComponent::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } void MQTTComponent::disable_discovery() { this->discovery_enabled_ = false; } -void MQTTComponent::set_custom_state_topic(const char *custom_state_topic) { - this->custom_state_topic_ = StringRef(custom_state_topic); - this->has_custom_state_topic_ = true; -} -void MQTTComponent::set_custom_command_topic(const char *custom_command_topic) { - this->custom_command_topic_ = StringRef(custom_command_topic); - this->has_custom_command_topic_ = true; -} void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; } void MQTTComponent::set_availability(std::string topic, std::string payload_available, @@ -273,16 +339,19 @@ bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connec // Pull these properties from EntityBase if not overridden std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); } +StringRef MQTTComponent::get_default_object_id_to_(std::span buf) const { + return this->get_entity()->get_object_id_to(buf); +} StringRef MQTTComponent::get_icon_ref_() const { return this->get_entity()->get_icon_ref(); } bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); } bool MQTTComponent::is_internal() { - if (this->has_custom_state_topic_) { + if (this->custom_state_topic_.has_value()) { // If the custom state_topic is null, return true as it is internal and should not publish // else, return false, as it is explicitly set to a topic, so it is not internal and should publish return this->get_state_topic_().empty(); } - if (this->has_custom_command_topic_) { + if (this->custom_command_topic_.has_value()) { // If the custom command_topic is null, return true as it is internal and should not publish // else, return false, as it is explicitly set to a topic, so it is not internal and should publish return this->get_command_topic_().empty(); @@ -298,7 +367,6 @@ bool MQTTComponent::is_internal() { return this->get_entity()->is_internal(); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 2f8dfcf64e..676e3ad35d 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -6,13 +6,13 @@ #include +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/string_ref.h" #include "mqtt_client.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { /// Simple Helper struct used for Home Assistant MQTT send_discovery(). struct SendDiscoveryConfig { @@ -20,6 +20,10 @@ struct SendDiscoveryConfig { bool command_topic{true}; ///< If the command topic should be included. Default to true. }; +// Max lengths for stack-based topic building (must match mqtt_component.cpp) +static constexpr size_t MQTT_COMPONENT_TYPE_MAX_LEN = 20; +static constexpr size_t MQTT_SUFFIX_MAX_LEN = 32; + #define LOG_MQTT_COMPONENT(state_topic, command_topic) \ if (state_topic) { \ ESP_LOGCONFIG(TAG, " State Topic: '%s'", this->get_state_topic_().c_str()); \ @@ -28,7 +32,18 @@ struct SendDiscoveryConfig { ESP_LOGCONFIG(TAG, " Command Topic: '%s'", this->get_command_topic_().c_str()); \ } +// Macro to define component_type() with compile-time length verification +// Usage: MQTT_COMPONENT_TYPE(MQTTSensorComponent, "sensor") +#define MQTT_COMPONENT_TYPE(class_name, type_str) \ + const char *class_name::component_type() const { return type_str; } \ + static_assert(sizeof(type_str) - 1 <= MQTT_COMPONENT_TYPE_MAX_LEN, \ + #class_name "::component_type() exceeds MQTT_COMPONENT_TYPE_MAX_LEN"); + +// Macro to define custom topic getter/setter with compile-time suffix length verification #define MQTT_COMPONENT_CUSTOM_TOPIC_(name, type) \ + static_assert(sizeof(#name "/" #type) - 1 <= MQTT_SUFFIX_MAX_LEN, \ + "topic suffix " #name "/" #type " exceeds MQTT_SUFFIX_MAX_LEN"); \ +\ protected: \ std::string custom_##name##_##type##_topic_{}; \ \ @@ -93,12 +108,15 @@ class MQTTComponent : public Component { void set_subscribe_qos(uint8_t qos); /// Override this method to return the component type (e.g. "light", "sensor", ...) - virtual std::string component_type() const = 0; + virtual const char *component_type() const = 0; - /// Set a custom state topic. Set to "" for default behavior. - void set_custom_state_topic(const char *custom_state_topic); - /// Set a custom command topic. Set to "" for default behavior. - void set_custom_command_topic(const char *custom_command_topic); + /// Set a custom state topic. Do not set for default behavior. + template void set_custom_state_topic(T &&custom_state_topic) { + this->custom_state_topic_ = std::forward(custom_state_topic); + } + template void set_custom_command_topic(T &&custom_command_topic) { + this->custom_command_topic_ = std::forward(custom_command_topic); + } /// Set whether command message should be retained. void set_command_retain(bool command_retain); @@ -122,6 +140,14 @@ class MQTTComponent : public Component { */ bool publish(const std::string &topic, const std::string &payload); + /** Send a MQTT message. + * + * @param topic The topic. + * @param payload The payload buffer. + * @param payload_length The length of the payload. + */ + bool publish(const std::string &topic, const char *payload, size_t payload_length); + /** Construct and send a JSON MQTT message. * * @param topic The topic. @@ -186,17 +212,14 @@ class MQTTComponent : public Component { // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - /// Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name. - std::string get_default_object_id_() const; + /// Get the object ID for this MQTT component, writing to the provided buffer. + StringRef get_default_object_id_to_(std::span buf) const; - StringRef custom_state_topic_{}; - StringRef custom_command_topic_{}; + TemplatableValue custom_state_topic_{}; + TemplatableValue custom_command_topic_{}; std::unique_ptr availability_; - bool has_custom_state_topic_{false}; - bool has_custom_command_topic_{false}; - bool command_retain_{false}; bool retain_{true}; uint8_t qos_{0}; @@ -205,7 +228,6 @@ class MQTTComponent : public Component { bool resend_state_{false}; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_MQTt diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 3ddd8fc5cc..221af00371 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -1,547 +1,332 @@ #pragma once #include "esphome/core/defines.h" +#include "esphome/core/progmem.h" #ifdef USE_MQTT -namespace esphome { -namespace mqtt { +// MQTT JSON Key Constants for Home Assistant Discovery +// +// This file defines string constants used as JSON keys in MQTT discovery payloads +// for Home Assistant integration. These are used exclusively with ArduinoJson as: +// root[MQTT_DEVICE_CLASS] = "temperature"; +// +// Implementation: +// - ESP8266: Stores strings in PROGMEM (flash) using __FlashStringHelper* pointers. +// ArduinoJson recognizes this type and reads from flash memory. +// - Other platforms: Uses constexpr const char* for compile-time optimization. +// - USE_MQTT_ABBREVIATIONS: When defined, uses shortened key names to reduce message size. +// +// Adding new keys: +// Add a single line to MQTT_KEYS_LIST: X(MQTT_NEW_KEY, "abbr", "full_name") +// The X-macro will generate the appropriate constants for each platform. +// +// Note: Other MQTT_* constants (e.g., MQTT_CLIENT_CONNECTED, MQTT_LEGACY_UNIQUE_ID_GENERATOR) +// are C++ enums defined in mqtt_client.h and mqtt_backend*.h - unrelated to these JSON keys. +// X-macro list: MQTT_KEYS_LIST(X) calls X(name, abbr, full) for each key +// clang-format off +#define MQTT_KEYS_LIST(X) \ + X(MQTT_ACTION_TEMPLATE, "act_tpl", "action_template") \ + X(MQTT_ACTION_TOPIC, "act_t", "action_topic") \ + X(MQTT_AUTOMATION_TYPE, "atype", "automation_type") \ + X(MQTT_AUX_COMMAND_TOPIC, "aux_cmd_t", "aux_command_topic") \ + X(MQTT_AUX_STATE_TEMPLATE, "aux_stat_tpl", "aux_state_template") \ + X(MQTT_AUX_STATE_TOPIC, "aux_stat_t", "aux_state_topic") \ + X(MQTT_AVAILABILITY, "avty", "availability") \ + X(MQTT_AVAILABILITY_MODE, "avty_mode", "availability_mode") \ + X(MQTT_AVAILABILITY_TOPIC, "avty_t", "availability_topic") \ + X(MQTT_AWAY_MODE_COMMAND_TOPIC, "away_mode_cmd_t", "away_mode_command_topic") \ + X(MQTT_AWAY_MODE_STATE_TEMPLATE, "away_mode_stat_tpl", "away_mode_state_template") \ + X(MQTT_AWAY_MODE_STATE_TOPIC, "away_mode_stat_t", "away_mode_state_topic") \ + X(MQTT_BATTERY_LEVEL_TEMPLATE, "bat_lev_tpl", "battery_level_template") \ + X(MQTT_BATTERY_LEVEL_TOPIC, "bat_lev_t", "battery_level_topic") \ + X(MQTT_BLUE_TEMPLATE, "b_tpl", "blue_template") \ + X(MQTT_BRIGHTNESS_COMMAND_TOPIC, "bri_cmd_t", "brightness_command_topic") \ + X(MQTT_BRIGHTNESS_SCALE, "bri_scl", "brightness_scale") \ + X(MQTT_BRIGHTNESS_STATE_TOPIC, "bri_stat_t", "brightness_state_topic") \ + X(MQTT_BRIGHTNESS_TEMPLATE, "bri_tpl", "brightness_template") \ + X(MQTT_BRIGHTNESS_VALUE_TEMPLATE, "bri_val_tpl", "brightness_value_template") \ + X(MQTT_CHARGING_TEMPLATE, "chrg_tpl", "charging_template") \ + X(MQTT_CHARGING_TOPIC, "chrg_t", "charging_topic") \ + X(MQTT_CLEANING_TEMPLATE, "cln_tpl", "cleaning_template") \ + X(MQTT_CLEANING_TOPIC, "cln_t", "cleaning_topic") \ + X(MQTT_CODE_ARM_REQUIRED, "cod_arm_req", "code_arm_required") \ + X(MQTT_CODE_DISARM_REQUIRED, "cod_dis_req", "code_disarm_required") \ + X(MQTT_COLOR_MODE, "clrm", "color_mode") \ + X(MQTT_COLOR_MODE_STATE_TOPIC, "clrm_stat_t", "color_mode_state_topic") \ + X(MQTT_COLOR_MODE_VALUE_TEMPLATE, "clrm_val_tpl", "color_mode_value_template") \ + X(MQTT_COLOR_TEMP_COMMAND_TEMPLATE, "clr_temp_cmd_tpl", "color_temp_command_template") \ + X(MQTT_COLOR_TEMP_COMMAND_TOPIC, "clr_temp_cmd_t", "color_temp_command_topic") \ + X(MQTT_COLOR_TEMP_STATE_TOPIC, "clr_temp_stat_t", "color_temp_state_topic") \ + X(MQTT_COLOR_TEMP_TEMPLATE, "clr_temp_tpl", "color_temp_template") \ + X(MQTT_COLOR_TEMP_VALUE_TEMPLATE, "clr_temp_val_tpl", "color_temp_value_template") \ + X(MQTT_COMMAND_OFF_TEMPLATE, "cmd_off_tpl", "command_off_template") \ + X(MQTT_COMMAND_ON_TEMPLATE, "cmd_on_tpl", "command_on_template") \ + X(MQTT_COMMAND_RETAIN, "ret", "retain") \ + X(MQTT_COMMAND_TEMPLATE, "cmd_tpl", "command_template") \ + X(MQTT_COMMAND_TOPIC, "cmd_t", "command_topic") \ + X(MQTT_CONFIGURATION_URL, "cu", "configuration_url") \ + X(MQTT_CURRENT_HUMIDITY_TEMPLATE, "curr_hum_tpl", "current_humidity_template") \ + X(MQTT_CURRENT_HUMIDITY_TOPIC, "curr_hum_t", "current_humidity_topic") \ + X(MQTT_CURRENT_TEMPERATURE_STEP, "precision", "precision") \ + X(MQTT_CURRENT_TEMPERATURE_TEMPLATE, "curr_temp_tpl", "current_temperature_template") \ + X(MQTT_CURRENT_TEMPERATURE_TOPIC, "curr_temp_t", "current_temperature_topic") \ + X(MQTT_DEVICE, "dev", "device") \ + X(MQTT_DEVICE_CLASS, "dev_cla", "device_class") \ + X(MQTT_DEVICE_CONNECTIONS, "cns", "connections") \ + X(MQTT_DEVICE_IDENTIFIERS, "ids", "identifiers") \ + X(MQTT_DEVICE_MANUFACTURER, "mf", "manufacturer") \ + X(MQTT_DEVICE_MODEL, "mdl", "model") \ + X(MQTT_DEVICE_NAME, "name", "name") \ + X(MQTT_DEVICE_SUGGESTED_AREA, "sa", "suggested_area") \ + X(MQTT_DEVICE_SW_VERSION, "sw", "sw_version") \ + X(MQTT_DEVICE_HW_VERSION, "hw", "hw_version") \ + X(MQTT_DIRECTION_COMMAND_TOPIC, "dir_cmd_t", "direction_command_topic") \ + X(MQTT_DIRECTION_STATE_TOPIC, "dir_stat_t", "direction_state_topic") \ + X(MQTT_DOCKED_TEMPLATE, "dock_tpl", "docked_template") \ + X(MQTT_DOCKED_TOPIC, "dock_t", "docked_topic") \ + X(MQTT_EFFECT_COMMAND_TOPIC, "fx_cmd_t", "effect_command_topic") \ + X(MQTT_EFFECT_LIST, "fx_list", "effect_list") \ + X(MQTT_EFFECT_STATE_TOPIC, "fx_stat_t", "effect_state_topic") \ + X(MQTT_EFFECT_TEMPLATE, "fx_tpl", "effect_template") \ + X(MQTT_EFFECT_VALUE_TEMPLATE, "fx_val_tpl", "effect_value_template") \ + X(MQTT_ENABLED_BY_DEFAULT, "en", "enabled_by_default") \ + X(MQTT_ENTITY_CATEGORY, "ent_cat", "entity_category") \ + X(MQTT_ERROR_TEMPLATE, "err_tpl", "error_template") \ + X(MQTT_ERROR_TOPIC, "err_t", "error_topic") \ + X(MQTT_EVENT_TYPE, "event_type", "event_type") \ + X(MQTT_EVENT_TYPES, "evt_typ", "event_types") \ + X(MQTT_EXPIRE_AFTER, "exp_aft", "expire_after") \ + X(MQTT_FAN_MODE_COMMAND_TEMPLATE, "fan_mode_cmd_tpl", "fan_mode_command_template") \ + X(MQTT_FAN_MODE_COMMAND_TOPIC, "fan_mode_cmd_t", "fan_mode_command_topic") \ + X(MQTT_FAN_MODE_STATE_TEMPLATE, "fan_mode_stat_tpl", "fan_mode_state_template") \ + X(MQTT_FAN_MODE_STATE_TOPIC, "fan_mode_stat_t", "fan_mode_state_topic") \ + X(MQTT_FAN_SPEED_LIST, "fanspd_lst", "fan_speed_list") \ + X(MQTT_FAN_SPEED_TEMPLATE, "fanspd_tpl", "fan_speed_template") \ + X(MQTT_FAN_SPEED_TOPIC, "fanspd_t", "fan_speed_topic") \ + X(MQTT_FLASH_TIME_LONG, "flsh_tlng", "flash_time_long") \ + X(MQTT_FLASH_TIME_SHORT, "flsh_tsht", "flash_time_short") \ + X(MQTT_FORCE_UPDATE, "frc_upd", "force_update") \ + X(MQTT_GREEN_TEMPLATE, "g_tpl", "green_template") \ + X(MQTT_HOLD_COMMAND_TEMPLATE, "hold_cmd_tpl", "hold_command_template") \ + X(MQTT_HOLD_COMMAND_TOPIC, "hold_cmd_t", "hold_command_topic") \ + X(MQTT_HOLD_STATE_TEMPLATE, "hold_stat_tpl", "hold_state_template") \ + X(MQTT_HOLD_STATE_TOPIC, "hold_stat_t", "hold_state_topic") \ + X(MQTT_HS_COMMAND_TOPIC, "hs_cmd_t", "hs_command_topic") \ + X(MQTT_HS_STATE_TOPIC, "hs_stat_t", "hs_state_topic") \ + X(MQTT_HS_VALUE_TEMPLATE, "hs_val_tpl", "hs_value_template") \ + X(MQTT_ICON, "ic", "icon") \ + X(MQTT_INITIAL, "init", "initial") \ + X(MQTT_JSON_ATTRIBUTES, "json_attr", "json_attributes") \ + X(MQTT_JSON_ATTRIBUTES_TEMPLATE, "json_attr_tpl", "json_attributes_template") \ + X(MQTT_JSON_ATTRIBUTES_TOPIC, "json_attr_t", "json_attributes_topic") \ + X(MQTT_LAST_RESET_TOPIC, "lrst_t", "last_reset_topic") \ + X(MQTT_LAST_RESET_VALUE_TEMPLATE, "lrst_val_tpl", "last_reset_value_template") \ + X(MQTT_MAX, "max", "max") \ + X(MQTT_MAX_HUMIDITY, "max_hum", "max_humidity") \ + X(MQTT_MAX_MIREDS, "max_mirs", "max_mireds") \ + X(MQTT_MAX_TEMP, "max_temp", "max_temp") \ + X(MQTT_MIN, "min", "min") \ + X(MQTT_MIN_HUMIDITY, "min_hum", "min_humidity") \ + X(MQTT_MIN_MIREDS, "min_mirs", "min_mireds") \ + X(MQTT_MIN_TEMP, "min_temp", "min_temp") \ + X(MQTT_MODE, "mode", "mode") \ + X(MQTT_MODE_COMMAND_TEMPLATE, "mode_cmd_tpl", "mode_command_template") \ + X(MQTT_MODE_COMMAND_TOPIC, "mode_cmd_t", "mode_command_topic") \ + X(MQTT_MODE_STATE_TEMPLATE, "mode_stat_tpl", "mode_state_template") \ + X(MQTT_MODE_STATE_TOPIC, "mode_stat_t", "mode_state_topic") \ + X(MQTT_MODES, "modes", "modes") \ + X(MQTT_NAME, "name", "name") \ + X(MQTT_OBJECT_ID, "obj_id", "object_id") \ + X(MQTT_OFF_DELAY, "off_dly", "off_delay") \ + X(MQTT_ON_COMMAND_TYPE, "on_cmd_type", "on_command_type") \ + X(MQTT_OPTIMISTIC, "opt", "optimistic") \ + X(MQTT_OPTIONS, "ops", "options") \ + X(MQTT_OSCILLATION_COMMAND_TEMPLATE, "osc_cmd_tpl", "oscillation_command_template") \ + X(MQTT_OSCILLATION_COMMAND_TOPIC, "osc_cmd_t", "oscillation_command_topic") \ + X(MQTT_OSCILLATION_STATE_TOPIC, "osc_stat_t", "oscillation_state_topic") \ + X(MQTT_OSCILLATION_VALUE_TEMPLATE, "osc_val_tpl", "oscillation_value_template") \ + X(MQTT_PAYLOAD, "pl", "payload") \ + X(MQTT_PAYLOAD_ARM_AWAY, "pl_arm_away", "payload_arm_away") \ + X(MQTT_PAYLOAD_ARM_CUSTOM_BYPASS, "pl_arm_custom_b", "payload_arm_custom_bypass") \ + X(MQTT_PAYLOAD_ARM_HOME, "pl_arm_home", "payload_arm_home") \ + X(MQTT_PAYLOAD_ARM_NIGHT, "pl_arm_nite", "payload_arm_night") \ + X(MQTT_PAYLOAD_ARM_VACATION, "pl_arm_vacation", "payload_arm_vacation") \ + X(MQTT_PAYLOAD_AVAILABLE, "pl_avail", "payload_available") \ + X(MQTT_PAYLOAD_CLEAN_SPOT, "pl_cln_sp", "payload_clean_spot") \ + X(MQTT_PAYLOAD_CLOSE, "pl_cls", "payload_close") \ + X(MQTT_PAYLOAD_DISARM, "pl_disarm", "payload_disarm") \ + X(MQTT_PAYLOAD_HIGH_SPEED, "pl_hi_spd", "payload_high_speed") \ + X(MQTT_PAYLOAD_HOME, "pl_home", "payload_home") \ + X(MQTT_PAYLOAD_INSTALL, "pl_inst", "payload_install") \ + X(MQTT_PAYLOAD_LOCATE, "pl_loc", "payload_locate") \ + X(MQTT_PAYLOAD_LOCK, "pl_lock", "payload_lock") \ + X(MQTT_PAYLOAD_LOW_SPEED, "pl_lo_spd", "payload_low_speed") \ + X(MQTT_PAYLOAD_MEDIUM_SPEED, "pl_med_spd", "payload_medium_speed") \ + X(MQTT_PAYLOAD_NOT_AVAILABLE, "pl_not_avail", "payload_not_available") \ + X(MQTT_PAYLOAD_NOT_HOME, "pl_not_home", "payload_not_home") \ + X(MQTT_PAYLOAD_OFF, "pl_off", "payload_off") \ + X(MQTT_PAYLOAD_OFF_SPEED, "pl_off_spd", "payload_off_speed") \ + X(MQTT_PAYLOAD_ON, "pl_on", "payload_on") \ + X(MQTT_PAYLOAD_OPEN, "pl_open", "payload_open") \ + X(MQTT_PAYLOAD_OSCILLATION_OFF, "pl_osc_off", "payload_oscillation_off") \ + X(MQTT_PAYLOAD_OSCILLATION_ON, "pl_osc_on", "payload_oscillation_on") \ + X(MQTT_PAYLOAD_PAUSE, "pl_paus", "payload_pause") \ + X(MQTT_PAYLOAD_RESET, "pl_rst", "payload_reset") \ + X(MQTT_PAYLOAD_RESET_HUMIDITY, "pl_rst_hum", "payload_reset_humidity") \ + X(MQTT_PAYLOAD_RESET_MODE, "pl_rst_mode", "payload_reset_mode") \ + X(MQTT_PAYLOAD_RESET_PERCENTAGE, "pl_rst_pct", "payload_reset_percentage") \ + X(MQTT_PAYLOAD_RESET_PRESET_MODE, "pl_rst_pr_mode", "payload_reset_preset_mode") \ + X(MQTT_PAYLOAD_RETURN_TO_BASE, "pl_ret", "payload_return_to_base") \ + X(MQTT_PAYLOAD_START, "pl_strt", "payload_start") \ + X(MQTT_PAYLOAD_START_PAUSE, "pl_stpa", "payload_start_pause") \ + X(MQTT_PAYLOAD_STOP, "pl_stop", "payload_stop") \ + X(MQTT_PAYLOAD_TURN_OFF, "pl_toff", "payload_turn_off") \ + X(MQTT_PAYLOAD_TURN_ON, "pl_ton", "payload_turn_on") \ + X(MQTT_PAYLOAD_UNLOCK, "pl_unlk", "payload_unlock") \ + X(MQTT_PERCENTAGE_COMMAND_TEMPLATE, "pct_cmd_tpl", "percentage_command_template") \ + X(MQTT_PERCENTAGE_COMMAND_TOPIC, "pct_cmd_t", "percentage_command_topic") \ + X(MQTT_PERCENTAGE_STATE_TOPIC, "pct_stat_t", "percentage_state_topic") \ + X(MQTT_PERCENTAGE_VALUE_TEMPLATE, "pct_val_tpl", "percentage_value_template") \ + X(MQTT_POSITION_CLOSED, "pos_clsd", "position_closed") \ + X(MQTT_POSITION_OPEN, "pos_open", "position_open") \ + X(MQTT_POSITION_TEMPLATE, "pos_tpl", "position_template") \ + X(MQTT_POSITION_TOPIC, "pos_t", "position_topic") \ + X(MQTT_POWER_COMMAND_TOPIC, "pow_cmd_t", "power_command_topic") \ + X(MQTT_POWER_STATE_TEMPLATE, "pow_stat_tpl", "power_state_template") \ + X(MQTT_POWER_STATE_TOPIC, "pow_stat_t", "power_state_topic") \ + X(MQTT_PRESET_MODE_COMMAND_TEMPLATE, "pr_mode_cmd_tpl", "preset_mode_command_template") \ + X(MQTT_PRESET_MODE_COMMAND_TOPIC, "pr_mode_cmd_t", "preset_mode_command_topic") \ + X(MQTT_PRESET_MODE_STATE_TOPIC, "pr_mode_stat_t", "preset_mode_state_topic") \ + X(MQTT_PRESET_MODE_VALUE_TEMPLATE, "pr_mode_val_tpl", "preset_mode_value_template") \ + X(MQTT_PRESET_MODES, "pr_modes", "preset_modes") \ + X(MQTT_QOS, "qos", "qos") \ + X(MQTT_RED_TEMPLATE, "r_tpl", "red_template") \ + X(MQTT_RETAIN, "ret", "retain") \ + X(MQTT_RGB_COMMAND_TEMPLATE, "rgb_cmd_tpl", "rgb_command_template") \ + X(MQTT_RGB_COMMAND_TOPIC, "rgb_cmd_t", "rgb_command_topic") \ + X(MQTT_RGB_STATE_TOPIC, "rgb_stat_t", "rgb_state_topic") \ + X(MQTT_RGB_VALUE_TEMPLATE, "rgb_val_tpl", "rgb_value_template") \ + X(MQTT_RGBW_COMMAND_TEMPLATE, "rgbw_cmd_tpl", "rgbw_command_template") \ + X(MQTT_RGBW_COMMAND_TOPIC, "rgbw_cmd_t", "rgbw_command_topic") \ + X(MQTT_RGBW_STATE_TOPIC, "rgbw_stat_t", "rgbw_state_topic") \ + X(MQTT_RGBW_VALUE_TEMPLATE, "rgbw_val_tpl", "rgbw_value_template") \ + X(MQTT_RGBWW_COMMAND_TEMPLATE, "rgbww_cmd_tpl", "rgbww_command_template") \ + X(MQTT_RGBWW_COMMAND_TOPIC, "rgbww_cmd_t", "rgbww_command_topic") \ + X(MQTT_RGBWW_STATE_TOPIC, "rgbww_stat_t", "rgbww_state_topic") \ + X(MQTT_RGBWW_VALUE_TEMPLATE, "rgbww_val_tpl", "rgbww_value_template") \ + X(MQTT_SEND_COMMAND_TOPIC, "send_cmd_t", "send_command_topic") \ + X(MQTT_SEND_IF_OFF, "send_if_off", "send_if_off") \ + X(MQTT_SET_FAN_SPEED_TOPIC, "set_fan_spd_t", "set_fan_speed_topic") \ + X(MQTT_SET_POSITION_TEMPLATE, "set_pos_tpl", "set_position_template") \ + X(MQTT_SET_POSITION_TOPIC, "set_pos_t", "set_position_topic") \ + X(MQTT_SOURCE_TYPE, "src_type", "source_type") \ + X(MQTT_SPEED_COMMAND_TOPIC, "spd_cmd_t", "speed_command_topic") \ + X(MQTT_SPEED_RANGE_MAX, "spd_rng_max", "speed_range_max") \ + X(MQTT_SPEED_RANGE_MIN, "spd_rng_min", "speed_range_min") \ + X(MQTT_SPEED_STATE_TOPIC, "spd_stat_t", "speed_state_topic") \ + X(MQTT_SPEED_VALUE_TEMPLATE, "spd_val_tpl", "speed_value_template") \ + X(MQTT_SPEEDS, "spds", "speeds") \ + X(MQTT_STATE_CLASS, "stat_cla", "state_class") \ + X(MQTT_STATE_CLOSED, "stat_clsd", "state_closed") \ + X(MQTT_STATE_CLOSING, "stat_closing", "state_closing") \ + X(MQTT_STATE_LOCKED, "stat_locked", "state_locked") \ + X(MQTT_STATE_OFF, "stat_off", "state_off") \ + X(MQTT_STATE_ON, "stat_on", "state_on") \ + X(MQTT_STATE_OPEN, "stat_open", "state_open") \ + X(MQTT_STATE_OPENING, "stat_opening", "state_opening") \ + X(MQTT_STATE_STOPPED, "stat_stopped", "state_stopped") \ + X(MQTT_STATE_TEMPLATE, "stat_tpl", "state_template") \ + X(MQTT_STATE_TOPIC, "stat_t", "state_topic") \ + X(MQTT_STATE_UNLOCKED, "stat_unlocked", "state_unlocked") \ + X(MQTT_STATE_VALUE_TEMPLATE, "stat_val_tpl", "state_value_template") \ + X(MQTT_STEP, "step", "step") \ + X(MQTT_SUBTYPE, "stype", "subtype") \ + X(MQTT_SUPPORTED_COLOR_MODES, "sup_clrm", "supported_color_modes") \ + X(MQTT_SUPPORTED_FEATURES, "sup_feat", "supported_features") \ + X(MQTT_SWING_MODE_COMMAND_TEMPLATE, "swing_mode_cmd_tpl", "swing_mode_command_template") \ + X(MQTT_SWING_MODE_COMMAND_TOPIC, "swing_mode_cmd_t", "swing_mode_command_topic") \ + X(MQTT_SWING_MODE_STATE_TEMPLATE, "swing_mode_stat_tpl", "swing_mode_state_template") \ + X(MQTT_SWING_MODE_STATE_TOPIC, "swing_mode_stat_t", "swing_mode_state_topic") \ + X(MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE, "hum_cmd_tpl", "target_humidity_command_template") \ + X(MQTT_TARGET_HUMIDITY_COMMAND_TOPIC, "hum_cmd_t", "target_humidity_command_topic") \ + X(MQTT_TARGET_HUMIDITY_STATE_TEMPLATE, "hum_state_tpl", "target_humidity_state_template") \ + X(MQTT_TARGET_HUMIDITY_STATE_TOPIC, "hum_stat_t", "target_humidity_state_topic") \ + X(MQTT_TARGET_TEMPERATURE_STEP, "temp_step", "temp_step") \ + X(MQTT_TEMPERATURE_COMMAND_TEMPLATE, "temp_cmd_tpl", "temperature_command_template") \ + X(MQTT_TEMPERATURE_COMMAND_TOPIC, "temp_cmd_t", "temperature_command_topic") \ + X(MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE, "temp_hi_cmd_tpl", "temperature_high_command_template") \ + X(MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC, "temp_hi_cmd_t", "temperature_high_command_topic") \ + X(MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE, "temp_hi_stat_tpl", "temperature_high_state_template") \ + X(MQTT_TEMPERATURE_HIGH_STATE_TOPIC, "temp_hi_stat_t", "temperature_high_state_topic") \ + X(MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE, "temp_lo_cmd_tpl", "temperature_low_command_template") \ + X(MQTT_TEMPERATURE_LOW_COMMAND_TOPIC, "temp_lo_cmd_t", "temperature_low_command_topic") \ + X(MQTT_TEMPERATURE_LOW_STATE_TEMPLATE, "temp_lo_stat_tpl", "temperature_low_state_template") \ + X(MQTT_TEMPERATURE_LOW_STATE_TOPIC, "temp_lo_stat_t", "temperature_low_state_topic") \ + X(MQTT_TEMPERATURE_STATE_TEMPLATE, "temp_stat_tpl", "temperature_state_template") \ + X(MQTT_TEMPERATURE_STATE_TOPIC, "temp_stat_t", "temperature_state_topic") \ + X(MQTT_TEMPERATURE_UNIT, "temp_unit", "temperature_unit") \ + X(MQTT_TILT_CLOSED_VALUE, "tilt_clsd_val", "tilt_closed_value") \ + X(MQTT_TILT_COMMAND_TEMPLATE, "tilt_cmd_tpl", "tilt_command_template") \ + X(MQTT_TILT_COMMAND_TOPIC, "tilt_cmd_t", "tilt_command_topic") \ + X(MQTT_TILT_INVERT_STATE, "tilt_inv_stat", "tilt_invert_state") \ + X(MQTT_TILT_MAX, "tilt_max", "tilt_max") \ + X(MQTT_TILT_MIN, "tilt_min", "tilt_min") \ + X(MQTT_TILT_OPENED_VALUE, "tilt_opnd_val", "tilt_opened_value") \ + X(MQTT_TILT_OPTIMISTIC, "tilt_opt", "tilt_optimistic") \ + X(MQTT_TILT_STATUS_TEMPLATE, "tilt_status_tpl", "tilt_status_template") \ + X(MQTT_TILT_STATUS_TOPIC, "tilt_status_t", "tilt_status_topic") \ + X(MQTT_TOPIC, "t", "topic") \ + X(MQTT_UNIQUE_ID, "uniq_id", "unique_id") \ + X(MQTT_UNIT_OF_MEASUREMENT, "unit_of_meas", "unit_of_measurement") \ + X(MQTT_VALUE_TEMPLATE, "val_tpl", "value_template") \ + X(MQTT_WHITE_COMMAND_TOPIC, "whit_cmd_t", "white_command_topic") \ + X(MQTT_WHITE_SCALE, "whit_scl", "white_scale") \ + X(MQTT_WHITE_VALUE_COMMAND_TOPIC, "whit_val_cmd_t", "white_value_command_topic") \ + X(MQTT_WHITE_VALUE_SCALE, "whit_val_scl", "white_value_scale") \ + X(MQTT_WHITE_VALUE_STATE_TOPIC, "whit_val_stat_t", "white_value_state_topic") \ + X(MQTT_WHITE_VALUE_TEMPLATE, "whit_val_tpl", "white_value_template") \ + X(MQTT_XY_COMMAND_TOPIC, "xy_cmd_t", "xy_command_topic") \ + X(MQTT_XY_STATE_TOPIC, "xy_stat_t", "xy_state_topic") \ + X(MQTT_XY_VALUE_TEMPLATE, "xy_val_tpl", "xy_value_template") +// clang-format on + +#ifdef USE_ESP8266 +// ESP8266: Store strings in PROGMEM (flash) and expose as __FlashStringHelper* pointers. +// ArduinoJson recognizes this type and reads from flash memory. +namespace esphome::mqtt { + +// Generate PROGMEM data arrays #ifdef USE_MQTT_ABBREVIATIONS - -constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl"; -constexpr const char *const MQTT_ACTION_TOPIC = "act_t"; -constexpr const char *const MQTT_AUTOMATION_TYPE = "atype"; -constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t"; -constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl"; -constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t"; -constexpr const char *const MQTT_AVAILABILITY = "avty"; -constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode"; -constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t"; -constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t"; -constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl"; -constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t"; -constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl"; -constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t"; -constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl"; -constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl"; -constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl"; -constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl"; -constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t"; -constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req"; -constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req"; -constexpr const char *const MQTT_COLOR_MODE = "clrm"; -constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t"; -constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t"; -constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t"; -constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl"; -constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl"; -constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl"; -constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl"; -constexpr const char *const MQTT_COMMAND_RETAIN = "ret"; -constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; -constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; -constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "curr_hum_tpl"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "curr_hum_t"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; -constexpr const char *const MQTT_DEVICE = "dev"; -constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf"; -constexpr const char *const MQTT_DEVICE_MODEL = "mdl"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw"; -constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw"; -constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "dir_cmd_t"; -constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "dir_stat_t"; -constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl"; -constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t"; -constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t"; -constexpr const char *const MQTT_EFFECT_LIST = "fx_list"; -constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t"; -constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl"; -constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en"; -constexpr const char *const MQTT_ENTITY_CATEGORY = "ent_cat"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; -constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; -constexpr const char *const MQTT_EVENT_TYPE = "event_type"; -constexpr const char *const MQTT_EVENT_TYPES = "evt_typ"; -constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t"; -constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl"; -constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t"; -constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng"; -constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht"; -constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd"; -constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl"; -constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl"; -constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t"; -constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl"; -constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t"; -constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t"; -constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t"; -constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl"; -constexpr const char *const MQTT_ICON = "ic"; -constexpr const char *const MQTT_INITIAL = "init"; -constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t"; -constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t"; -constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl"; -constexpr const char *const MQTT_MAX = "max"; -constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum"; -constexpr const char *const MQTT_MAX_MIREDS = "max_mirs"; -constexpr const char *const MQTT_MAX_TEMP = "max_temp"; -constexpr const char *const MQTT_MIN = "min"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum"; -constexpr const char *const MQTT_MIN_MIREDS = "min_mirs"; -constexpr const char *const MQTT_MIN_TEMP = "min_temp"; -constexpr const char *const MQTT_MODE = "mode"; -constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl"; -constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t"; -constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl"; -constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t"; -constexpr const char *const MQTT_MODES = "modes"; -constexpr const char *const MQTT_NAME = "name"; -constexpr const char *const MQTT_OBJECT_ID = "obj_id"; -constexpr const char *const MQTT_OFF_DELAY = "off_dly"; -constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type"; -constexpr const char *const MQTT_OPTIMISTIC = "opt"; -constexpr const char *const MQTT_OPTIONS = "ops"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t"; -constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t"; -constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl"; -constexpr const char *const MQTT_PAYLOAD = "pl"; -constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away"; -constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b"; -constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home"; -constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite"; -constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation"; -constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail"; -constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp"; -constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls"; -constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm"; -constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd"; -constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home"; -constexpr const char *const MQTT_PAYLOAD_INSTALL = "pl_inst"; -constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc"; -constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock"; -constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd"; -constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd"; -constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail"; -constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home"; -constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off"; -constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd"; -constexpr const char *const MQTT_PAYLOAD_ON = "pl_on"; -constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on"; -constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus"; -constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst"; -constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum"; -constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode"; -constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct"; -constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode"; -constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret"; -constexpr const char *const MQTT_PAYLOAD_START = "pl_strt"; -constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa"; -constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop"; -constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff"; -constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton"; -constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t"; -constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t"; -constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl"; -constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd"; -constexpr const char *const MQTT_POSITION_OPEN = "pos_open"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl"; -constexpr const char *const MQTT_POSITION_TOPIC = "pos_t"; -constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t"; -constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl"; -constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; -constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t"; -constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl"; -constexpr const char *const MQTT_PRESET_MODES = "pr_modes"; -constexpr const char *const MQTT_QOS = "qos"; -constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl"; -constexpr const char *const MQTT_RETAIN = "ret"; -constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl"; -constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t"; -constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t"; -constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl"; -constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl"; -constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t"; -constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t"; -constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl"; -constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl"; -constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t"; -constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t"; -constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl"; -constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t"; -constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; -constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t"; -constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl"; -constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t"; -constexpr const char *const MQTT_SOURCE_TYPE = "src_type"; -constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t"; -constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min"; -constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t"; -constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl"; -constexpr const char *const MQTT_SPEEDS = "spds"; -constexpr const char *const MQTT_STATE_CLASS = "stat_cla"; -constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd"; -constexpr const char *const MQTT_STATE_CLOSING = "stat_closing"; -constexpr const char *const MQTT_STATE_LOCKED = "stat_locked"; -constexpr const char *const MQTT_STATE_OFF = "stat_off"; -constexpr const char *const MQTT_STATE_ON = "stat_on"; -constexpr const char *const MQTT_STATE_OPEN = "stat_open"; -constexpr const char *const MQTT_STATE_OPENING = "stat_opening"; -constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped"; -constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl"; -constexpr const char *const MQTT_STATE_TOPIC = "stat_t"; -constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked"; -constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl"; -constexpr const char *const MQTT_STEP = "step"; -constexpr const char *const MQTT_SUBTYPE = "stype"; -constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm"; -constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t"; -constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl"; -constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; -constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t"; -constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit"; -constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val"; -constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl"; -constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t"; -constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat"; -constexpr const char *const MQTT_TILT_MAX = "tilt_max"; -constexpr const char *const MQTT_TILT_MIN = "tilt_min"; -constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val"; -constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt"; -constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl"; -constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t"; -constexpr const char *const MQTT_TOPIC = "t"; -constexpr const char *const MQTT_UNIQUE_ID = "uniq_id"; -constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas"; -constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl"; -constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t"; -constexpr const char *const MQTT_WHITE_SCALE = "whit_scl"; -constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t"; -constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl"; -constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t"; -constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl"; -constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t"; -constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t"; -constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl"; - +#define MQTT_DATA(name, abbr, full) static const char name##_data[] PROGMEM = abbr; #else +#define MQTT_DATA(name, abbr, full) static const char name##_data[] PROGMEM = full; +#endif +MQTT_KEYS_LIST(MQTT_DATA) +#undef MQTT_DATA -constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template"; -constexpr const char *const MQTT_ACTION_TOPIC = "action_topic"; -constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type"; -constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic"; -constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template"; -constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic"; -constexpr const char *const MQTT_AVAILABILITY = "availability"; -constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode"; -constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic"; -constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template"; -constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic"; -constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template"; -constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic"; -constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template"; -constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic"; -constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale"; -constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic"; -constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template"; -constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template"; -constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template"; -constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic"; -constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template"; -constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic"; -constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required"; -constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required"; -constexpr const char *const MQTT_COLOR_MODE = "color_mode"; -constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic"; -constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template"; -constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic"; -constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic"; -constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template"; -constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template"; -constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template"; -constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template"; -constexpr const char *const MQTT_COMMAND_RETAIN = "retain"; -constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; -constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; -constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "current_humidity_template"; -constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "current_humidity_topic"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_STEP = "precision"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template"; -constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; -constexpr const char *const MQTT_DEVICE = "device"; -constexpr const char *const MQTT_DEVICE_CLASS = "device_class"; -constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections"; -constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers"; -constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer"; -constexpr const char *const MQTT_DEVICE_MODEL = "model"; -constexpr const char *const MQTT_DEVICE_NAME = "name"; -constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; -constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; -constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw_version"; -constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "direction_command_topic"; -constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "direction_state_topic"; -constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template"; -constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic"; -constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic"; -constexpr const char *const MQTT_EFFECT_LIST = "effect_list"; -constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic"; -constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template"; -constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template"; -constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default"; -constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; -constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; -constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; -constexpr const char *const MQTT_EVENT_TYPE = "event_type"; -constexpr const char *const MQTT_EVENT_TYPES = "event_types"; -constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template"; -constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic"; -constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template"; -constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic"; -constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list"; -constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template"; -constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic"; -constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long"; -constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short"; -constexpr const char *const MQTT_FORCE_UPDATE = "force_update"; -constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template"; -constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template"; -constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic"; -constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template"; -constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic"; -constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic"; -constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic"; -constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template"; -constexpr const char *const MQTT_ICON = "icon"; -constexpr const char *const MQTT_INITIAL = "initial"; -constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template"; -constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic"; -constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic"; -constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"; -constexpr const char *const MQTT_MAX = "max"; -constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity"; -constexpr const char *const MQTT_MAX_MIREDS = "max_mireds"; -constexpr const char *const MQTT_MAX_TEMP = "max_temp"; -constexpr const char *const MQTT_MIN = "min"; -constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity"; -constexpr const char *const MQTT_MIN_MIREDS = "min_mireds"; -constexpr const char *const MQTT_MIN_TEMP = "min_temp"; -constexpr const char *const MQTT_MODE = "mode"; -constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template"; -constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic"; -constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template"; -constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic"; -constexpr const char *const MQTT_MODES = "modes"; -constexpr const char *const MQTT_NAME = "name"; -constexpr const char *const MQTT_OBJECT_ID = "object_id"; -constexpr const char *const MQTT_OFF_DELAY = "off_delay"; -constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type"; -constexpr const char *const MQTT_OPTIMISTIC = "optimistic"; -constexpr const char *const MQTT_OPTIONS = "options"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template"; -constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"; -constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"; -constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"; -constexpr const char *const MQTT_PAYLOAD = "payload"; -constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away"; -constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass"; -constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home"; -constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night"; -constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation"; -constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available"; -constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot"; -constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close"; -constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm"; -constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed"; -constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home"; -constexpr const char *const MQTT_PAYLOAD_INSTALL = "payload_install"; -constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate"; -constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock"; -constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed"; -constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"; -constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available"; -constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home"; -constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off"; -constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed"; -constexpr const char *const MQTT_PAYLOAD_ON = "payload_on"; -constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"; -constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"; -constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause"; -constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset"; -constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity"; -constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode"; -constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage"; -constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode"; -constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base"; -constexpr const char *const MQTT_PAYLOAD_START = "payload_start"; -constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause"; -constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop"; -constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off"; -constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on"; -constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"; -constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"; -constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"; -constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"; -constexpr const char *const MQTT_POSITION_CLOSED = "position_closed"; -constexpr const char *const MQTT_POSITION_OPEN = "position_open"; -constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template"; -constexpr const char *const MQTT_POSITION_TOPIC = "position_topic"; -constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic"; -constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template"; -constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template"; -constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"; -constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"; -constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"; -constexpr const char *const MQTT_PRESET_MODES = "preset_modes"; -constexpr const char *const MQTT_QOS = "qos"; -constexpr const char *const MQTT_RED_TEMPLATE = "red_template"; -constexpr const char *const MQTT_RETAIN = "retain"; -constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template"; -constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic"; -constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic"; -constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template"; -constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template"; -constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic"; -constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic"; -constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template"; -constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template"; -constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic"; -constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic"; -constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template"; -constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic"; -constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; -constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic"; -constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template"; -constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic"; -constexpr const char *const MQTT_SOURCE_TYPE = "source_type"; -constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic"; -constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max"; -constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min"; -constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic"; -constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template"; -constexpr const char *const MQTT_SPEEDS = "speeds"; -constexpr const char *const MQTT_STATE_CLASS = "state_class"; -constexpr const char *const MQTT_STATE_CLOSED = "state_closed"; -constexpr const char *const MQTT_STATE_CLOSING = "state_closing"; -constexpr const char *const MQTT_STATE_LOCKED = "state_locked"; -constexpr const char *const MQTT_STATE_OFF = "state_off"; -constexpr const char *const MQTT_STATE_ON = "state_on"; -constexpr const char *const MQTT_STATE_OPEN = "state_open"; -constexpr const char *const MQTT_STATE_OPENING = "state_opening"; -constexpr const char *const MQTT_STATE_STOPPED = "state_stopped"; -constexpr const char *const MQTT_STATE_TEMPLATE = "state_template"; -constexpr const char *const MQTT_STATE_TOPIC = "state_topic"; -constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked"; -constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template"; -constexpr const char *const MQTT_STEP = "step"; -constexpr const char *const MQTT_SUBTYPE = "subtype"; -constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes"; -constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template"; -constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic"; -constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template"; -constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"; -constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; -constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; -constexpr const char *const MQTT_TARGET_TEMPERATURE_STEP = "temp_step"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template"; -constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template"; -constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template"; -constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template"; -constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template"; -constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic"; -constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit"; -constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value"; -constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template"; -constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic"; -constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state"; -constexpr const char *const MQTT_TILT_MAX = "tilt_max"; -constexpr const char *const MQTT_TILT_MIN = "tilt_min"; -constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value"; -constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic"; -constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template"; -constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic"; -constexpr const char *const MQTT_TOPIC = "topic"; -constexpr const char *const MQTT_UNIQUE_ID = "unique_id"; -constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement"; -constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template"; -constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic"; -constexpr const char *const MQTT_WHITE_SCALE = "white_scale"; -constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic"; -constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale"; -constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic"; -constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template"; -constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic"; -constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic"; -constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template"; +// Generate flash string pointers from the PROGMEM data +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_PTR(name, abbr, full) \ + static const __FlashStringHelper *const name = reinterpret_cast(name##_data); +MQTT_KEYS_LIST(MQTT_PTR) +#undef MQTT_PTR +} // namespace esphome::mqtt +#else +// Other platforms: constexpr in namespace +namespace esphome::mqtt { +#ifdef USE_MQTT_ABBREVIATIONS +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_CONST(name, abbr, full) constexpr const char *name = abbr; +#else +// NOLINTNEXTLINE(bugprone-macro-parentheses) +#define MQTT_CONST(name, abbr, full) constexpr const char *name = full; +#endif +MQTT_KEYS_LIST(MQTT_CONST) +#undef MQTT_CONST +} // namespace esphome::mqtt #endif -} // namespace mqtt -} // namespace esphome - #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index b63aa66d29..f2df6af236 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_COVER -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.cover"; @@ -91,7 +90,7 @@ void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf } } -std::string MQTTCoverComponent::component_type() const { return "cover"; } +MQTT_COMPONENT_TYPE(MQTTCoverComponent, "cover") const EntityBase *MQTTCoverComponent::get_entity() const { return this->cover_; } bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); } @@ -99,13 +98,15 @@ bool MQTTCoverComponent::publish_state() { auto traits = this->cover_->get_traits(); bool success = true; if (traits.get_supports_position()) { - std::string pos = value_accuracy_to_string(roundf(this->cover_->position * 100), 0); - if (!this->publish(this->get_position_state_topic(), pos)) + char pos[VALUE_ACCURACY_MAX_LEN]; + size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0); + if (!this->publish(this->get_position_state_topic(), pos, len)) success = false; } if (traits.get_supports_tilt()) { - std::string pos = value_accuracy_to_string(roundf(this->cover_->tilt * 100), 0); - if (!this->publish(this->get_tilt_state_topic(), pos)) + char pos[VALUE_ACCURACY_MAX_LEN]; + size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0); + if (!this->publish(this->get_tilt_state_topic(), pos, len)) success = false; } const char *state_s = this->cover_->current_operation == COVER_OPERATION_OPENING ? "opening" @@ -119,8 +120,7 @@ bool MQTTCoverComponent::publish_state() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.h b/esphome/components/mqtt/mqtt_cover.h index f3e6053d0b..13582d14d1 100644 --- a/esphome/components/mqtt/mqtt_cover.h +++ b/esphome/components/mqtt/mqtt_cover.h @@ -8,8 +8,7 @@ #include "esphome/components/cover/cover.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTCoverComponent : public mqtt::MQTTComponent { public: @@ -30,14 +29,13 @@ class MQTTCoverComponent : public mqtt::MQTTComponent { void dump_config() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; cover::Cover *cover_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index 0f0a334ae7..dba7c1a671 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_DATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime"; @@ -20,14 +19,14 @@ MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {} void MQTTDateComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->date_->make_call(); - if (root["year"].is()) { - call.set_year(root["year"]); + if (root[ESPHOME_F("year")].is()) { + call.set_year(root[ESPHOME_F("year")]); } - if (root["month"].is()) { - call.set_month(root["month"]); + if (root[ESPHOME_F("month")].is()) { + call.set_month(root[ESPHOME_F("month")]); } - if (root["day"].is()) { - call.set_day(root["day"]); + if (root[ESPHOME_F("day")].is()) { + call.set_day(root[ESPHOME_F("day")]); } call.perform(); }); @@ -40,7 +39,7 @@ void MQTTDateComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTDateComponent::component_type() const { return "date"; } +MQTT_COMPONENT_TYPE(MQTTDateComponent, "date") const EntityBase *MQTTDateComponent::get_entity() const { return this->date_; } void MQTTDateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { @@ -56,14 +55,13 @@ bool MQTTDateComponent::send_initial_state() { bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) { return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["year"] = year; - root["month"] = month; - root["day"] = day; + root[ESPHOME_F("year")] = year; + root[ESPHOME_F("month")] = month; + root[ESPHOME_F("day")] = day; }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_date.h b/esphome/components/mqtt/mqtt_date.h index 5147afe7e7..4a626becb2 100644 --- a/esphome/components/mqtt/mqtt_date.h +++ b/esphome/components/mqtt/mqtt_date.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/date_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTDateComponent : public mqtt::MQTTComponent { public: @@ -32,14 +31,13 @@ class MQTTDateComponent : public mqtt::MQTTComponent { bool publish_state(uint16_t year, uint8_t month, uint8_t day); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; datetime::DateEntity *date_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 5c56baabe0..5f1cf19b97 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_DATETIME -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime.datetime"; @@ -20,23 +19,23 @@ MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetim void MQTTDateTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->datetime_->make_call(); - if (root["year"].is()) { - call.set_year(root["year"]); + if (root[ESPHOME_F("year")].is()) { + call.set_year(root[ESPHOME_F("year")]); } - if (root["month"].is()) { - call.set_month(root["month"]); + if (root[ESPHOME_F("month")].is()) { + call.set_month(root[ESPHOME_F("month")]); } - if (root["day"].is()) { - call.set_day(root["day"]); + if (root[ESPHOME_F("day")].is()) { + call.set_day(root[ESPHOME_F("day")]); } - if (root["hour"].is()) { - call.set_hour(root["hour"]); + if (root[ESPHOME_F("hour")].is()) { + call.set_hour(root[ESPHOME_F("hour")]); } - if (root["minute"].is()) { - call.set_minute(root["minute"]); + if (root[ESPHOME_F("minute")].is()) { + call.set_minute(root[ESPHOME_F("minute")]); } - if (root["second"].is()) { - call.set_second(root["second"]); + if (root[ESPHOME_F("second")].is()) { + call.set_second(root[ESPHOME_F("second")]); } call.perform(); }); @@ -51,7 +50,7 @@ void MQTTDateTimeComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTDateTimeComponent::component_type() const { return "datetime"; } +MQTT_COMPONENT_TYPE(MQTTDateTimeComponent, "datetime") const EntityBase *MQTTDateTimeComponent::get_entity() const { return this->datetime_; } void MQTTDateTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { @@ -69,17 +68,16 @@ bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t uint8_t second) { return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["year"] = year; - root["month"] = month; - root["day"] = day; - root["hour"] = hour; - root["minute"] = minute; - root["second"] = second; + root[ESPHOME_F("year")] = year; + root[ESPHOME_F("month")] = month; + root[ESPHOME_F("day")] = day; + root[ESPHOME_F("hour")] = hour; + root[ESPHOME_F("minute")] = minute; + root[ESPHOME_F("second")] = second; }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATETIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_datetime.h b/esphome/components/mqtt/mqtt_datetime.h index ba81c06cb3..d02d6f579c 100644 --- a/esphome/components/mqtt/mqtt_datetime.h +++ b/esphome/components/mqtt/mqtt_datetime.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/datetime_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTDateTimeComponent : public mqtt::MQTTComponent { public: @@ -32,14 +31,13 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent { bool publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; datetime::DateTimeEntity *datetime_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATETIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index fd095ea041..42fbc1eabd 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_EVENT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.event"; @@ -51,11 +50,10 @@ bool MQTTEventComponent::publish_event_(const std::string &event_type) { }); } -std::string MQTTEventComponent::component_type() const { return "event"; } +MQTT_COMPONENT_TYPE(MQTTEventComponent, "event") const EntityBase *MQTTEventComponent::get_entity() const { return this->event_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_event.h b/esphome/components/mqtt/mqtt_event.h index 4335820e53..e6d5b6f278 100644 --- a/esphome/components/mqtt/mqtt_event.h +++ b/esphome/components/mqtt/mqtt_event.h @@ -8,8 +8,7 @@ #include "esphome/components/event/event.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTEventComponent : public mqtt::MQTTComponent { public: @@ -26,14 +25,13 @@ class MQTTEventComponent : public mqtt::MQTTComponent { protected: bool publish_event_(const std::string &event_type); - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; event::Event *event_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 2aefc3a4db..a6f0503588 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_FAN -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.fan"; @@ -16,7 +15,7 @@ using namespace esphome::fan; MQTTFanComponent::MQTTFanComponent(Fan *state) : state_(state) {} Fan *MQTTFanComponent::get_state() const { return this->state_; } -std::string MQTTFanComponent::component_type() const { return "fan"; } +MQTT_COMPONENT_TYPE(MQTTFanComponent, "fan") const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } void MQTTFanComponent::setup() { @@ -175,15 +174,15 @@ bool MQTTFanComponent::publish_state() { } auto traits = this->state_->get_traits(); if (traits.supports_speed()) { - std::string payload = to_string(this->state_->speed); - bool success = this->publish(this->get_speed_level_state_topic(), payload); + char buf[12]; + int len = snprintf(buf, sizeof(buf), "%d", this->state_->speed); + bool success = this->publish(this->get_speed_level_state_topic(), buf, len); failed = failed || !success; } return !failed; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 78641d224f..43ef67e733 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -8,8 +8,7 @@ #include "esphome/components/fan/fan.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTFanComponent : public mqtt::MQTTComponent { public: @@ -37,7 +36,7 @@ class MQTTFanComponent : public mqtt::MQTTComponent { bool send_initial_state() override; bool publish_state(); /// 'fan' component type for discovery. - std::string component_type() const override; + const char *component_type() const override; fan::Fan *get_state() const; @@ -47,8 +46,7 @@ class MQTTFanComponent : public mqtt::MQTTComponent { fan::Fan *state_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index fe911bfba2..fac19f3210 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -8,14 +8,13 @@ #ifdef USE_LIGHT #include "esphome/components/light/light_json_schema.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.light"; using namespace esphome::light; -std::string MQTTJSONLightComponent::component_type() const { return "light"; } +MQTT_COMPONENT_TYPE(MQTTJSONLightComponent, "light") const EntityBase *MQTTJSONLightComponent::get_entity() const { return this->state_; } void MQTTJSONLightComponent::setup() { @@ -44,33 +43,33 @@ LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["schema"] = "json"; + root[ESPHOME_F("schema")] = ESPHOME_F("json"); auto traits = this->state_->get_traits(); root[MQTT_COLOR_MODE] = true; // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - JsonArray color_modes = root["supported_color_modes"].to(); + JsonArray color_modes = root[ESPHOME_F("supported_color_modes")].to(); if (traits.supports_color_mode(ColorMode::ON_OFF)) - color_modes.add("onoff"); + color_modes.add(ESPHOME_F("onoff")); if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) - color_modes.add("brightness"); + color_modes.add(ESPHOME_F("brightness")); if (traits.supports_color_mode(ColorMode::WHITE)) - color_modes.add("white"); + color_modes.add(ESPHOME_F("white")); if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE) || traits.supports_color_mode(ColorMode::COLD_WARM_WHITE)) - color_modes.add("color_temp"); + color_modes.add(ESPHOME_F("color_temp")); if (traits.supports_color_mode(ColorMode::RGB)) - color_modes.add("rgb"); + color_modes.add(ESPHOME_F("rgb")); if (traits.supports_color_mode(ColorMode::RGB_WHITE) || // HA doesn't support RGBCT, and there's no CWWW->CT emulation in ESPHome yet, so ignore CT control for now traits.supports_color_mode(ColorMode::RGB_COLOR_TEMPERATURE)) - color_modes.add("rgbw"); + color_modes.add(ESPHOME_F("rgbw")); if (traits.supports_color_mode(ColorMode::RGB_COLD_WARM_WHITE)) - color_modes.add("rgbww"); + color_modes.add(ESPHOME_F("rgbww")); // legacy API if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) - root["brightness"] = true; + root[ESPHOME_F("brightness")] = true; if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE) || traits.supports_color_mode(ColorMode::COLD_WARM_WHITE)) { @@ -79,11 +78,13 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery } if (this->state_->supports_effects()) { - root["effect"] = true; + root[ESPHOME_F("effect")] = true; JsonArray effect_list = root[MQTT_EFFECT_LIST].to(); - for (auto *effect : this->state_->get_effects()) - effect_list.add(effect->get_name()); - effect_list.add("None"); + for (auto *effect : this->state_->get_effects()) { + // c_str() is safe as effect names are null-terminated strings from codegen + effect_list.add(effect->get_name().c_str()); + } + effect_list.add(ESPHOME_F("None")); } } bool MQTTJSONLightComponent::send_initial_state() { return this->publish_state_(); } @@ -92,8 +93,7 @@ void MQTTJSONLightComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_light.h b/esphome/components/mqtt/mqtt_light.h index a105f3d7b8..41981655ef 100644 --- a/esphome/components/mqtt/mqtt_light.h +++ b/esphome/components/mqtt/mqtt_light.h @@ -8,8 +8,7 @@ #include "mqtt_component.h" #include "esphome/components/light/light_state.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRemoteValuesListener { public: @@ -29,7 +28,7 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRe void on_light_remote_values_update() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; bool publish_state_(); @@ -37,8 +36,7 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRe light::LightState *state_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 95efbf60e1..43ef60bdf4 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_LOCK -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.lock"; @@ -35,7 +34,7 @@ void MQTTLockComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } -std::string MQTTLockComponent::component_type() const { return "lock"; } +MQTT_COMPONENT_TYPE(MQTTLockComponent, "lock") const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson @@ -58,8 +57,7 @@ bool MQTTLockComponent::publish_state() { #endif } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.h b/esphome/components/mqtt/mqtt_lock.h index 789f74c795..666882c73d 100644 --- a/esphome/components/mqtt/mqtt_lock.h +++ b/esphome/components/mqtt/mqtt_lock.h @@ -8,8 +8,7 @@ #include "esphome/components/lock/lock.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTLockComponent : public mqtt::MQTTComponent { public: @@ -28,14 +27,13 @@ class MQTTLockComponent : public mqtt::MQTTComponent { protected: /// "lock" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; lock::Lock *lock_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index f419eac130..8342210ee4 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_NUMBER -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.number"; @@ -34,7 +33,7 @@ void MQTTNumberComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } -std::string MQTTNumberComponent::component_type() const { return "number"; } +MQTT_COMPONENT_TYPE(MQTTNumberComponent, "number") const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_; } void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { @@ -80,8 +79,7 @@ bool MQTTNumberComponent::publish_state(float value) { return this->publish(this->get_state_topic_(), buffer); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.h b/esphome/components/mqtt/mqtt_number.h index 10500c8333..021a539988 100644 --- a/esphome/components/mqtt/mqtt_number.h +++ b/esphome/components/mqtt/mqtt_number.h @@ -8,8 +8,7 @@ #include "esphome/components/number/number.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTNumberComponent : public mqtt::MQTTComponent { public: @@ -33,14 +32,13 @@ class MQTTNumberComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "number". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; number::Number *number_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index e1660b07ea..03ab82312b 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_SELECT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.select"; @@ -21,8 +20,7 @@ void MQTTSelectComponent::setup() { call.set_option(state); call.perform(); }); - this->select_->add_on_state_callback( - [this](const std::string &state, size_t index) { this->publish_state(this->select_->option_at(index)); }); + this->select_->add_on_state_callback([this](size_t index) { this->publish_state(this->select_->option_at(index)); }); } void MQTTSelectComponent::dump_config() { @@ -30,7 +28,7 @@ void MQTTSelectComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } -std::string MQTTSelectComponent::component_type() const { return "select"; } +MQTT_COMPONENT_TYPE(MQTTSelectComponent, "select") const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_; } void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { @@ -45,7 +43,8 @@ void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon } bool MQTTSelectComponent::send_initial_state() { if (this->select_->has_state()) { - return this->publish_state(this->select_->current_option()); + auto option = this->select_->current_option(); + return this->publish_state(std::string(option.c_str(), option.size())); } else { return true; } @@ -54,8 +53,7 @@ bool MQTTSelectComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_select.h b/esphome/components/mqtt/mqtt_select.h index e0d8ac2417..aaf174ff72 100644 --- a/esphome/components/mqtt/mqtt_select.h +++ b/esphome/components/mqtt/mqtt_select.h @@ -8,8 +8,7 @@ #include "esphome/components/select/select.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSelectComponent : public mqtt::MQTTComponent { public: @@ -33,14 +32,13 @@ class MQTTSelectComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "select". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; select::Select *select_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 010ac3013e..c14c889d47 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -11,8 +11,7 @@ #include "esphome/components/deep_sleep/deep_sleep_component.h" #endif -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.sensor"; @@ -32,7 +31,7 @@ void MQTTSensorComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } -std::string MQTTSensorComponent::component_type() const { return "sensor"; } +MQTT_COMPONENT_TYPE(MQTTSensorComponent, "sensor") const EntityBase *MQTTSensorComponent::get_entity() const { return this->sensor_; } uint32_t MQTTSensorComponent::get_expire_after() const { @@ -81,13 +80,14 @@ bool MQTTSensorComponent::send_initial_state() { } bool MQTTSensorComponent::publish_state(float value) { if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value)) - return this->publish(this->get_state_topic_(), "None"); + return this->publish(this->get_state_topic_(), "None", 4); int8_t accuracy = this->sensor_->get_accuracy_decimals(); - return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); + char buf[VALUE_ACCURACY_MAX_LEN]; + size_t len = value_accuracy_to_buf(buf, value, accuracy); + return this->publish(this->get_state_topic_(), buf, len); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_sensor.h b/esphome/components/mqtt/mqtt_sensor.h index 15ea703ad4..e8202aa8e2 100644 --- a/esphome/components/mqtt/mqtt_sensor.h +++ b/esphome/components/mqtt/mqtt_sensor.h @@ -8,8 +8,7 @@ #include "esphome/components/sensor/sensor.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSensorComponent : public mqtt::MQTTComponent { public: @@ -44,15 +43,14 @@ class MQTTSensorComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "sensor". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; sensor::Sensor *sensor_; optional expire_after_; // Override the expire after advertised to Home Assistant }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index b3a35420b9..a985ec66be 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_SWITCH -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.switch"; @@ -42,7 +41,7 @@ void MQTTSwitchComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } -std::string MQTTSwitchComponent::component_type() const { return "switch"; } +MQTT_COMPONENT_TYPE(MQTTSwitchComponent, "switch") const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson @@ -57,8 +56,7 @@ bool MQTTSwitchComponent::publish_state(bool state) { return this->publish(this->get_state_topic_(), state_s); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_switch.h b/esphome/components/mqtt/mqtt_switch.h index c4d3f7164c..5f6cb841fd 100644 --- a/esphome/components/mqtt/mqtt_switch.h +++ b/esphome/components/mqtt/mqtt_switch.h @@ -8,8 +8,7 @@ #include "esphome/components/switch/switch.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTSwitchComponent : public mqtt::MQTTComponent { public: @@ -28,14 +27,13 @@ class MQTTSwitchComponent : public mqtt::MQTTComponent { protected: /// "switch" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; switch_::Switch *switch_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index 5ab0ca9688..cee94965c6 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_TEXT -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.text"; @@ -30,7 +29,7 @@ void MQTTTextComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTTextComponent::component_type() const { return "text"; } +MQTT_COMPONENT_TYPE(MQTTTextComponent, "text") const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; } void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { @@ -57,8 +56,7 @@ bool MQTTTextComponent::publish_state(const std::string &value) { return this->publish(this->get_state_topic_(), value); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text.h b/esphome/components/mqtt/mqtt_text.h index d9486fcbf8..8ae0b9e29a 100644 --- a/esphome/components/mqtt/mqtt_text.h +++ b/esphome/components/mqtt/mqtt_text.h @@ -8,8 +8,7 @@ #include "esphome/components/text/text.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTextComponent : public mqtt::MQTTComponent { public: @@ -33,14 +32,13 @@ class MQTTTextComponent : public mqtt::MQTTComponent { protected: /// Override for MQTTComponent, returns "text". - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; text::Text *text_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index e6e7cf04e8..5346923b41 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_TEXT_SENSOR -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.text_sensor"; @@ -40,11 +39,10 @@ bool MQTTTextSensor::send_initial_state() { return true; } } -std::string MQTTTextSensor::component_type() const { return "sensor"; } +MQTT_COMPONENT_TYPE(MQTTTextSensor, "sensor") const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_text_sensor.h b/esphome/components/mqtt/mqtt_text_sensor.h index 9a14efdd16..d8f9315c1e 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.h +++ b/esphome/components/mqtt/mqtt_text_sensor.h @@ -8,8 +8,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTextSensor : public mqtt::MQTTComponent { public: @@ -26,14 +25,13 @@ class MQTTTextSensor : public mqtt::MQTTComponent { bool send_initial_state() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; text_sensor::TextSensor *sensor_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index 0c95bd8147..b75325022a 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -8,8 +8,7 @@ #ifdef USE_MQTT #ifdef USE_DATETIME_TIME -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.datetime.time"; @@ -20,14 +19,14 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {} void MQTTTimeComponent::setup() { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { auto call = this->time_->make_call(); - if (root["hour"].is()) { - call.set_hour(root["hour"]); + if (root[ESPHOME_F("hour")].is()) { + call.set_hour(root[ESPHOME_F("hour")]); } - if (root["minute"].is()) { - call.set_minute(root["minute"]); + if (root[ESPHOME_F("minute")].is()) { + call.set_minute(root[ESPHOME_F("minute")]); } - if (root["second"].is()) { - call.set_second(root["second"]); + if (root[ESPHOME_F("second")].is()) { + call.set_second(root[ESPHOME_F("second")]); } call.perform(); }); @@ -40,7 +39,7 @@ void MQTTTimeComponent::dump_config() { LOG_MQTT_COMPONENT(true, true) } -std::string MQTTTimeComponent::component_type() const { return "time"; } +MQTT_COMPONENT_TYPE(MQTTTimeComponent, "time") const EntityBase *MQTTTimeComponent::get_entity() const { return this->time_; } void MQTTTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { @@ -56,14 +55,13 @@ bool MQTTTimeComponent::send_initial_state() { bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) { return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["hour"] = hour; - root["minute"] = minute; - root["second"] = second; + root[ESPHOME_F("hour")] = hour; + root[ESPHOME_F("minute")] = minute; + root[ESPHOME_F("second")] = second; }); } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_TIME #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_time.h b/esphome/components/mqtt/mqtt_time.h index b9dd822a73..cf5780da2d 100644 --- a/esphome/components/mqtt/mqtt_time.h +++ b/esphome/components/mqtt/mqtt_time.h @@ -8,8 +8,7 @@ #include "esphome/components/datetime/time_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTTimeComponent : public mqtt::MQTTComponent { public: @@ -32,14 +31,13 @@ class MQTTTimeComponent : public mqtt::MQTTComponent { bool publish_state(uint8_t hour, uint8_t minute, uint8_t second); protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; datetime::TimeEntity *time_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_DATETIME_DATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index 20f3a69a9e..99e0c85509 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_UPDATE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.update"; @@ -30,20 +29,20 @@ void MQTTUpdateComponent::setup() { bool MQTTUpdateComponent::publish_state() { return this->publish_json(this->get_state_topic_(), [this](JsonObject root) { - root["installed_version"] = this->update_->update_info.current_version; - root["latest_version"] = this->update_->update_info.latest_version; - root["title"] = this->update_->update_info.title; + root[ESPHOME_F("installed_version")] = this->update_->update_info.current_version; + root[ESPHOME_F("latest_version")] = this->update_->update_info.latest_version; + root[ESPHOME_F("title")] = this->update_->update_info.title; if (!this->update_->update_info.summary.empty()) - root["release_summary"] = this->update_->update_info.summary; + root[ESPHOME_F("release_summary")] = this->update_->update_info.summary; if (!this->update_->update_info.release_url.empty()) - root["release_url"] = this->update_->update_info.release_url; + root[ESPHOME_F("release_url")] = this->update_->update_info.release_url; }); } void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - root["schema"] = "json"; - root[MQTT_PAYLOAD_INSTALL] = "INSTALL"; + root[ESPHOME_F("schema")] = ESPHOME_F("json"); + root[MQTT_PAYLOAD_INSTALL] = ESPHOME_F("INSTALL"); } bool MQTTUpdateComponent::send_initial_state() { return this->publish_state(); } @@ -53,11 +52,10 @@ void MQTTUpdateComponent::dump_config() { LOG_MQTT_COMPONENT(true, true); } -std::string MQTTUpdateComponent::component_type() const { return "update"; } +MQTT_COMPONENT_TYPE(MQTTUpdateComponent, "update") const EntityBase *MQTTUpdateComponent::get_entity() const { return this->update_; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_UPDATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_update.h b/esphome/components/mqtt/mqtt_update.h index 6fe04c4ea7..ec1adb1fcd 100644 --- a/esphome/components/mqtt/mqtt_update.h +++ b/esphome/components/mqtt/mqtt_update.h @@ -8,8 +8,7 @@ #include "esphome/components/update/update_entity.h" #include "mqtt_component.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTUpdateComponent : public mqtt::MQTTComponent { public: @@ -28,14 +27,13 @@ class MQTTUpdateComponent : public mqtt::MQTTComponent { protected: /// "update" component type. - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; update::UpdateEntity *update_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif // USE_UPDATE #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index ae60670748..2faaace46b 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -6,8 +6,7 @@ #ifdef USE_MQTT #ifdef USE_VALVE -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { static const char *const TAG = "mqtt.valve"; @@ -66,7 +65,7 @@ void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf } } -std::string MQTTValveComponent::component_type() const { return "valve"; } +MQTT_COMPONENT_TYPE(MQTTValveComponent, "valve") const EntityBase *MQTTValveComponent::get_entity() const { return this->valve_; } bool MQTTValveComponent::send_initial_state() { return this->publish_state(); } @@ -74,8 +73,9 @@ bool MQTTValveComponent::publish_state() { auto traits = this->valve_->get_traits(); bool success = true; if (traits.get_supports_position()) { - std::string pos = value_accuracy_to_string(roundf(this->valve_->position * 100), 0); - if (!this->publish(this->get_position_state_topic(), pos)) + char pos[VALUE_ACCURACY_MAX_LEN]; + size_t len = value_accuracy_to_buf(pos, roundf(this->valve_->position * 100), 0); + if (!this->publish(this->get_position_state_topic(), pos, len)) success = false; } const char *state_s = this->valve_->current_operation == VALVE_OPERATION_OPENING ? "opening" @@ -89,8 +89,7 @@ bool MQTTValveComponent::publish_state() { return success; } -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_valve.h b/esphome/components/mqtt/mqtt_valve.h index 63a0462193..d3b724a8ba 100644 --- a/esphome/components/mqtt/mqtt_valve.h +++ b/esphome/components/mqtt/mqtt_valve.h @@ -8,8 +8,7 @@ #include "esphome/components/valve/valve.h" -namespace esphome { -namespace mqtt { +namespace esphome::mqtt { class MQTTValveComponent : public mqtt::MQTTComponent { public: @@ -28,14 +27,13 @@ class MQTTValveComponent : public mqtt::MQTTComponent { void dump_config() override; protected: - std::string component_type() const override; + const char *component_type() const override; const EntityBase *get_entity() const override; valve::Valve *valve_; }; -} // namespace mqtt -} // namespace esphome +} // namespace esphome::mqtt #endif #endif // USE_MQTT diff --git a/esphome/components/my9231/my9231.cpp b/esphome/components/my9231/my9231.cpp index fba7ac2bf3..5b77a49e72 100644 --- a/esphome/components/my9231/my9231.cpp +++ b/esphome/components/my9231/my9231.cpp @@ -58,14 +58,14 @@ void MY9231OutputComponent::setup() { } } void MY9231OutputComponent::dump_config() { - ESP_LOGCONFIG(TAG, "MY9231:"); - LOG_PIN(" DI Pin: ", this->pin_di_); - LOG_PIN(" DCKI Pin: ", this->pin_dcki_); ESP_LOGCONFIG(TAG, + "MY9231:\n" " Total number of channels: %u\n" " Number of chips: %u\n" " Bit depth: %u", this->num_channels_, this->num_chips_, this->bit_depth_); + LOG_PIN(" DI Pin: ", this->pin_di_); + LOG_PIN(" DCKI Pin: ", this->pin_dcki_); } void MY9231OutputComponent::loop() { if (!this->update_) diff --git a/esphome/components/nau7802/nau7802.cpp b/esphome/components/nau7802/nau7802.cpp index 11f63a9a33..5edbc79862 100644 --- a/esphome/components/nau7802/nau7802.cpp +++ b/esphome/components/nau7802/nau7802.cpp @@ -131,9 +131,9 @@ void NAU7802Sensor::dump_config() { } // Note these may differ from the values on the device if calbration has been run ESP_LOGCONFIG(TAG, - " Offset Calibration: %s\n" + " Offset Calibration: %" PRId32 "\n" " Gain Calibration: %f", - to_string(this->offset_calibration_).c_str(), this->gain_calibration_); + this->offset_calibration_, this->gain_calibration_); std::string voltage = "unknown"; switch (this->ldo_) { @@ -289,7 +289,7 @@ void NAU7802Sensor::loop() { this->status_clear_error(); int32_t ocal = this->read_value_(OCAL1_B2_REG, 3); - ESP_LOGI(TAG, "New Offset: %s", to_string(ocal).c_str()); + ESP_LOGI(TAG, "New Offset: %" PRId32, ocal); uint32_t gcal = this->read_value_(GCAL1_B3_REG, 4); float gcal_f = ((float) gcal / (float) (1 << GCAL1_FRACTIONAL)); ESP_LOGI(TAG, "New Gain: %f", gcal_f); diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index d071059185..c77217243c 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -194,6 +194,14 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): + if CORE.is_esp8266: + # NeoPixelBus library unconditionally includes NeoEsp8266UartMethod.h + # which references Serial and Serial1, so we must enable both + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + enable_serial() + enable_serial1() + has_white = "W" in config[CONF_TYPE] method = config[CONF_METHOD] diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index d7a51fb0c6..5b63bbfce9 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -156,7 +156,7 @@ async def to_code(config): "High performance networking disabled by user configuration (overriding component request)" ) - if CORE.is_esp32 and CORE.using_esp_idf and should_enable: + if CORE.is_esp32 and should_enable: # Check if PSRAM is guaranteed (set by psram component during final validation) psram_guaranteed = psram_is_guaranteed() @@ -210,12 +210,12 @@ async def to_code(config): "USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT] ) if CORE.is_esp32: - if CORE.using_esp_idf: - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6) - add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6) - else: + if CORE.using_arduino: add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) + else: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6) + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6) elif enable_ipv6: cg.add_build_flag("-DCONFIG_LWIP_IPV6") cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG") diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 3d8b062d0b..b719d1a70e 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -8,7 +8,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/macros.h" -#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) #include #endif @@ -40,6 +40,9 @@ using ip4_addr_t = in_addr; namespace esphome { namespace network { +/// Buffer size for IP address string (IPv6 max: 39 chars + null) +static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40; + struct IPAddress { public: #ifdef USE_HOST @@ -50,6 +53,10 @@ struct IPAddress { IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); } IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; } std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); } + /// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes. + char *str_to(char *buf) const { + return const_cast(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE)); + } #else IPAddress() { ip_addr_set_zero(&ip_addr_); } IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { @@ -128,6 +135,8 @@ struct IPAddress { bool is_ip6() const { return IP_IS_V6(&ip_addr_); } bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); } std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); } + /// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes. + char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); } bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); } bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); } IPAddress &operator+=(uint8_t increase) { diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 8adc49d68c..38f449dc03 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -13,14 +13,16 @@ CONF_SEND_TO_NEXTION = "send_to_nextion" FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "nextion_upload_arduino.cpp": { + "nextion_upload_esp32.cpp": { PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, + "nextion_upload_arduino.cpp": { PlatformFramework.ESP8266_ARDUINO, PlatformFramework.RP2040_ARDUINO, PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, }, - "nextion_upload_idf.cpp": {PlatformFramework.ESP32_IDF}, } ) diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index ed6cd93027..b95df55a61 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -154,14 +154,11 @@ async def to_code(config): cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) if CORE.is_esp32: - if CORE.using_arduino: - cg.add_library("NetworkClientSecure", None) - cg.add_library("HTTPClient", None) esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True) esp32.add_idf_sdkconfig_option( "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True ) - elif CORE.is_esp8266 and CORE.using_arduino: + elif CORE.is_esp8266: cg.add_library("ESP8266HTTPClient", None) if CONF_TOUCH_SLEEP_TIMEOUT in config: diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 7e8f563a96..331e901578 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -13,17 +13,12 @@ #include "esphome/components/display/display_color_utils.h" #ifdef USE_NEXTION_TFT_UPLOAD -#ifdef USE_ARDUINO #ifdef USE_ESP32 -#include -#endif // USE_ESP32 -#ifdef USE_ESP8266 +#include +#elif defined(USE_ESP8266) #include #include -#endif // USE_ESP8266 -#elif defined(USE_ESP_IDF) -#include -#endif // ARDUINO vs USE_ESP_IDF +#endif // USE_ESP32 vs USE_ESP8266 #endif // USE_NEXTION_TFT_UPLOAD namespace esphome { @@ -1078,7 +1073,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe #ifdef USE_NEXTION_TFT_UPLOAD /** - * Set the tft file URL. https seems problematic with Arduino.. + * Set the tft file URL. */ void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } @@ -1422,16 +1417,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe uint32_t original_baud_rate_ = 0; bool upload_first_chunk_sent_ = false; -#ifdef USE_ARDUINO - /** - * will request chunk_size chunks from the web server - * and send each to the nextion - * @param HTTPClient http_client HTTP client handler. - * @param int range_start Position of next byte to transfer. - * @return position of last byte transferred, -1 for failure. - */ - int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start); -#elif defined(USE_ESP_IDF) +#ifdef USE_ESP32 /** * will request 4096 bytes chunks from the web server * and send each to Nextion @@ -1440,7 +1426,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * @return position of last byte transferred, -1 for failure. */ int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start); -#endif // USE_ARDUINO vs USE_ESP_IDF +#elif defined(USE_ARDUINO) + /** + * will request chunk_size chunks from the web server + * and send each to the nextion + * @param HTTPClient http_client HTTP client handler. + * @param int range_start Position of next byte to transfer. + * @return position of last byte transferred, -1 for failure. + */ + int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start); +#endif // USE_ESP32 vs USE_ARDUINO /** * Ends the upload process, restart Nextion and, if successful, @@ -1450,12 +1445,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ bool upload_end_(bool successful); - /** - * Returns the ESP Free Heap memory. This is framework independent. - * @return Free Heap in bytes. - */ - uint32_t get_free_heap_(); - #endif // USE_NEXTION_TFT_UPLOAD bool check_connect_(); diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index baea938729..d210bad004 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -1,43 +1,35 @@ #include "nextion.h" #ifdef USE_NEXTION_TFT_UPLOAD -#ifdef USE_ARDUINO +#ifndef USE_ESP32 #include #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" -#ifdef USE_ESP32 -#include -#endif - namespace esphome { namespace nextion { static const char *const TAG = "nextion.upload.arduino"; +static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16; // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 -inline uint32_t Nextion::get_free_heap_() { -#if defined(USE_ESP32) - return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); -#elif defined(USE_ESP8266) - return EspClass::getFreeHeap(); -#endif // USE_ESP32 vs USE_ESP8266 -} - int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { uint32_t range_size = this->tft_size_ - range_start; - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap()); uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1; ESP_LOGD(TAG, "Range start: %" PRIu32, range_start); if (range_size <= 0 or range_end <= range_start) { - ESP_LOGD(TAG, "Range end: %" PRIu32, range_end); - ESP_LOGD(TAG, "Range size: %" PRIu32, range_size); ESP_LOGE(TAG, "Invalid range"); + ESP_LOGD(TAG, + "Range end: %" PRIu32 "\n" + "Range size: %" PRIu32, + range_end, range_size); return -1; } @@ -95,18 +87,14 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { this->recv_ret_string_(recv_string, upload_first_chunk_sent_ ? 500 : 5000, true); this->content_length_ -= read_len; const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_; -#if defined(USE_ESP32) && defined(USE_PSRAM) - ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 "+%" PRIu32 ")", upload_percentage, - this->content_length_, static_cast(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)), - static_cast(heap_caps_get_free_size(MALLOC_CAP_SPIRAM))); -#else ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_, - this->get_free_heap_()); -#endif + EspClass::getFreeHeap()); upload_first_chunk_sent_ = true; if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request - ESP_LOGD(TAG, "Recv: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGD( + TAG, "Recv: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); uint32_t result = 0; for (int j = 0; j < 4; ++j) { result += static_cast(recv_string[j + 1]) << (8 * j); @@ -123,8 +111,10 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { buffer = nullptr; return range_end + 1; } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok" - ESP_LOGE(TAG, "Invalid response: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGE( + TAG, "Invalid response: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); // Deallocate buffer allocator.deallocate(buffer, 4096); buffer = nullptr; @@ -148,9 +138,11 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { } bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { - ESP_LOGD(TAG, "TFT upload requested"); - ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); - ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); + ESP_LOGD(TAG, + "TFT upload requested\n" + "Exit reparse: %s\n" + "URL: %s", + YESNO(exit_reparse), this->tft_url_.c_str()); if (this->connection_state_.is_updating_) { ESP_LOGW(TAG, "Upload in progress"); @@ -180,15 +172,14 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client - ESP_LOGV(TAG, "Init HTTP client"); - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, + "Init HTTP client\n" + "Heap: %" PRIu32, + EspClass::getFreeHeap()); HTTPClient http_client; http_client.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along bool begin_status = false; -#ifdef USE_ESP32 - begin_status = http_client.begin(this->tft_url_.c_str()); -#endif #ifdef USE_ESP8266 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http_client.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); @@ -256,22 +247,24 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { this->send_command_("sleep=0"); this->send_command_("dim=100"); delay(250); // NOLINT - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap()); App.feed_wdt(); char command[128]; // Tells the Nextion the content length of the tft file and baud rate it will be sent at // Once the Nextion accepts the command it will wait until the file is successfully uploaded // If it fails for any reason a power cycle of the display will be needed - sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate); + snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate); // Clear serial receive buffer ESP_LOGV(TAG, "Clear RX buffer"); this->reset_(false); delay(250); // NOLINT - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); - ESP_LOGV(TAG, "Upload cmd: %s", command); + ESP_LOGV(TAG, + "Heap: %" PRIu32 "\n" + "Upload cmd: %s", + EspClass::getFreeHeap(), command); this->send_command_(command); if (baud_rate != this->original_baud_rate_) { @@ -287,10 +280,11 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { this->recv_ret_string_(response, 5000, true); // This can take some time to return // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; ESP_LOGD(TAG, "Upload resp: [%s] %zu B", - format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), + format_hex_pretty_to(hex_buf, reinterpret_cast(response.data()), response.size()), response.length()); - ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap()); if (response.find(0x05) != std::string::npos) { ESP_LOGV(TAG, "Upload prep done"); @@ -302,10 +296,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { return this->upload_end_(false); } - ESP_LOGD(TAG, "Upload TFT:"); - ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str()); - ESP_LOGD(TAG, " Size: %d bytes", this->content_length_); - ESP_LOGD(TAG, " Heap: %" PRIu32, this->get_free_heap_()); + ESP_LOGD(TAG, + "Upload TFT:\n" + " URL: %s\n" + " Size: %d bytes\n" + " Heap: %" PRIu32, + this->tft_url_.c_str(), this->content_length_, EspClass::getFreeHeap()); // Proceed with the content download as before @@ -322,7 +318,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { return this->upload_end_(false); } App.feed_wdt(); - ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, this->get_free_heap_(), this->content_length_); + ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, EspClass::getFreeHeap(), this->content_length_); } ESP_LOGD(TAG, "Upload complete"); @@ -356,5 +352,5 @@ WiFiClient *Nextion::get_wifi_client_() { } // namespace nextion } // namespace esphome -#endif // USE_ARDUINO +#endif // NOT USE_ESP32 #endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_esp32.cpp similarity index 84% rename from esphome/components/nextion/nextion_upload_idf.cpp rename to esphome/components/nextion/nextion_upload_esp32.cpp index 942e3dd6c3..712fa8e78e 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_esp32.cpp @@ -1,7 +1,7 @@ #include "nextion.h" #ifdef USE_NEXTION_TFT_UPLOAD -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include @@ -9,12 +9,14 @@ #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" namespace esphome { namespace nextion { -static const char *const TAG = "nextion.upload.idf"; +static const char *const TAG = "nextion.upload.esp32"; +static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16; // Followed guide // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 @@ -25,8 +27,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1; ESP_LOGD(TAG, "Range start: %" PRIu32, range_start); if (range_size <= 0 or range_end <= range_start) { - ESP_LOGD(TAG, "Range end: %" PRIu32, range_end); - ESP_LOGD(TAG, "Range size: %" PRIu32, range_size); + ESP_LOGD(TAG, + "Range end: %" PRIu32 "\n" + "Range size: %" PRIu32, + range_end, range_size); ESP_LOGE(TAG, "Invalid range"); return -1; } @@ -108,8 +112,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r #endif upload_first_chunk_sent_ = true; if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request - ESP_LOGD(TAG, "Recv: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGD( + TAG, "Recv: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); uint32_t result = 0; for (int j = 0; j < 4; ++j) { result += static_cast(recv_string[j + 1]) << (8 * j); @@ -126,8 +132,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r buffer = nullptr; return range_end + 1; } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok" - ESP_LOGE(TAG, "Invalid response: [%s]", - format_hex_pretty(reinterpret_cast(recv_string.data()), recv_string.size()).c_str()); + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; + ESP_LOGE( + TAG, "Invalid response: [%s]", + format_hex_pretty_to(hex_buf, reinterpret_cast(recv_string.data()), recv_string.size())); // Deallocate buffer allocator.deallocate(buffer, 4096); buffer = nullptr; @@ -151,9 +159,11 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r } bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { - ESP_LOGD(TAG, "TFT upload requested"); - ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse)); - ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str()); + ESP_LOGD(TAG, + "TFT upload requested\n" + "Exit reparse: %s\n" + "URL: %s", + YESNO(exit_reparse), this->tft_url_.c_str()); if (this->connection_state_.is_updating_) { ESP_LOGW(TAG, "Upload in progress"); @@ -183,8 +193,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); // Define the configuration for the HTTP client - ESP_LOGV(TAG, "Init HTTP client"); - ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, + "Init HTTP client\n" + "Heap: %" PRIu32, + esp_get_free_heap_size()); esp_http_client_config_t config = { .url = this->tft_url_.c_str(), .cert_pem = nullptr, @@ -208,8 +220,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { } // Perform the HTTP request - ESP_LOGV(TAG, "Check connection"); - ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, + "Check connection\n" + "Heap: %" PRIu32, + esp_get_free_heap_size()); err = esp_http_client_perform(http_client); if (err != ESP_OK) { ESP_LOGE(TAG, "HTTP failed: %s", esp_err_to_name(err)); @@ -218,8 +232,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { } // Check the HTTP Status Code - ESP_LOGV(TAG, "Check status"); - ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGV(TAG, + "Check status\n" + "Heap: %" PRIu32, + esp_get_free_heap_size()); int status_code = esp_http_client_get_status_code(http_client); if (status_code != 200 && status_code != 206) { return this->upload_end_(false); @@ -255,7 +271,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { // Tells the Nextion the content length of the tft file and baud rate it will be sent at // Once the Nextion accepts the command it will wait until the file is successfully uploaded // If it fails for any reason a power cycle of the display will be needed - sprintf(command, "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate); + snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate); // Clear serial receive buffer ESP_LOGV(TAG, "Clear RX buffer"); @@ -277,8 +293,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { this->recv_ret_string_(response, 5000, true); // This can take some time to return // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)]; ESP_LOGD(TAG, "Upload resp: [%s] %zu B", - format_hex_pretty(reinterpret_cast(response.data()), response.size()).c_str(), + format_hex_pretty_to(hex_buf, reinterpret_cast(response.data()), response.size()), response.length()); ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size()); @@ -300,10 +317,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { return this->upload_end_(false); } - ESP_LOGD(TAG, "Uploading TFT:"); - ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str()); - ESP_LOGD(TAG, " Size: %" PRIu32 " bytes", this->content_length_); - ESP_LOGD(TAG, " Heap: %" PRIu32, esp_get_free_heap_size()); + ESP_LOGD(TAG, + "Uploading TFT:\n" + " URL: %s\n" + " Size: %" PRIu32 " bytes\n" + " Heap: %" PRIu32, + this->tft_url_.c_str(), this->content_length_, esp_get_free_heap_size()); // Proceed with the content download as before @@ -324,9 +343,8 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, esp_get_free_heap_size(), this->content_length_); } - ESP_LOGD(TAG, "TFT upload complete"); - - ESP_LOGD(TAG, "Close HTTP"); + ESP_LOGD(TAG, "TFT upload complete\n" + "Close HTTP"); esp_http_client_close(http_client); esp_http_client_cleanup(http_client); ESP_LOGV(TAG, "Connection closed"); @@ -336,5 +354,5 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { } // namespace nextion } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 #endif // USE_NEXTION_TFT_UPLOAD diff --git a/esphome/components/nfc/automation.cpp b/esphome/components/nfc/automation.cpp index ff00340df0..e2956e4c12 100644 --- a/esphome/components/nfc/automation.cpp +++ b/esphome/components/nfc/automation.cpp @@ -1,9 +1,13 @@ #include "automation.h" +#include "nfc.h" namespace esphome { namespace nfc { -void NfcOnTagTrigger::process(const std::unique_ptr &tag) { this->trigger(format_uid(tag->get_uid()), *tag); } +void NfcOnTagTrigger::process(const std::unique_ptr &tag) { + char uid_buf[FORMAT_UID_BUFFER_SIZE]; + this->trigger(std::string(format_uid_to(uid_buf, tag->get_uid())), *tag); +} } // namespace nfc } // namespace esphome diff --git a/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp b/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp index bc19fa7213..b62b243cc6 100644 --- a/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp +++ b/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp @@ -1,4 +1,5 @@ #include "nfc_binary_sensor.h" +#include "../nfc.h" #include "../nfc_helpers.h" #include "esphome/core/log.h" @@ -24,7 +25,8 @@ void NfcTagBinarySensor::dump_config() { return; } if (!this->uid_.empty()) { - ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str()); + char uid_buf[FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes_to(uid_buf, this->uid_)); } } diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index d3a2481693..f60d2671cd 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -8,9 +8,20 @@ namespace nfc { static const char *const TAG = "nfc"; -std::string format_uid(const std::vector &uid) { return format_hex_pretty(uid, '-', false); } +char *format_uid_to(char *buffer, const std::vector &uid) { + return format_hex_pretty_to(buffer, FORMAT_UID_BUFFER_SIZE, uid.data(), uid.size(), '-'); +} -std::string format_bytes(const std::vector &bytes) { return format_hex_pretty(bytes, ' ', false); } +char *format_bytes_to(char *buffer, const std::vector &bytes) { + return format_hex_pretty_to(buffer, FORMAT_BYTES_BUFFER_SIZE, bytes.data(), bytes.size(), ' '); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +// Deprecated wrappers intentionally use heap-allocating version for backward compatibility +std::string format_uid(const std::vector &uid) { return format_hex_pretty(uid, '-', false); } // NOLINT +std::string format_bytes(const std::vector &bytes) { return format_hex_pretty(bytes, ' ', false); } // NOLINT +#pragma GCC diagnostic pop uint8_t guess_tag_type(uint8_t uid_length) { if (uid_length == 4) { diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h index 9879cfdb03..6568c60a85 100644 --- a/esphome/components/nfc/nfc.h +++ b/esphome/components/nfc/nfc.h @@ -53,7 +53,21 @@ static const uint8_t DEFAULT_KEY[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}; static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; +/// Max UID size is 10 bytes, formatted as "XX-XX-XX-XX-XX-XX-XX-XX-XX-XX\0" = 30 chars +static constexpr size_t FORMAT_UID_BUFFER_SIZE = 30; +/// Format UID to buffer with '-' separator (e.g., "04-11-22-33"). Returns buffer for inline use. +char *format_uid_to(char *buffer, const std::vector &uid); + +/// Buffer size for format_bytes_to (64 bytes max = 192 chars with space separator) +static constexpr size_t FORMAT_BYTES_BUFFER_SIZE = 192; +/// Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use. +char *format_bytes_to(char *buffer, const std::vector &bytes); + +// Remove before 2026.6.0 +ESPDEPRECATED("Use format_uid_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0") std::string format_uid(const std::vector &uid); +// Remove before 2026.6.0 +ESPDEPRECATED("Use format_bytes_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0") std::string format_bytes(const std::vector &bytes); uint8_t guess_tag_type(uint8_t uid_length); diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 03927e8ea2..5fb8abddfc 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -8,10 +8,12 @@ from esphome import pins import esphome.codegen as cg from esphome.components.zephyr import ( copy_files as zephyr_copy_files, + zephyr_add_overlay, zephyr_add_pm_static, zephyr_add_prj_conf, zephyr_data, zephyr_set_core_data, + zephyr_setup_preferences, zephyr_to_code, ) from esphome.components.zephyr.const import ( @@ -25,6 +27,7 @@ from esphome.const import ( CONF_FRAMEWORK, CONF_ID, CONF_RESET_PIN, + CONF_VERSION, CONF_VOLTAGE, KEY_CORE, KEY_FRAMEWORK_VERSION, @@ -49,7 +52,7 @@ from .const import ( from .gpio import nrf52_pin_to_code # noqa CODEOWNERS = ["@tomaszduda23"] -AUTO_LOAD = ["zephyr"] +AUTO_LOAD = ["zephyr", "preferences"] IS_TARGET_PLATFORM = True _LOGGER = logging.getLogger(__name__) @@ -58,7 +61,6 @@ def set_core_data(config: ConfigType) -> ConfigType: zephyr_set_core_data(config) CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_NRF52 CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = KEY_ZEPHYR - CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(2, 6, 1) if config[KEY_BOOTLOADER] in BOOTLOADER_CONFIG: zephyr_add_pm_static(BOOTLOADER_CONFIG[config[KEY_BOOTLOADER]]) @@ -66,6 +68,12 @@ def set_core_data(config: ConfigType) -> ConfigType: return config +def set_framework(config: ConfigType) -> ConfigType: + version = cv.Version.parse(cv.version_number(config[CONF_FRAMEWORK][CONF_VERSION])) + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = version + return config + + BOOTLOADERS = [ BOOTLOADER_ADAFRUIT, BOOTLOADER_ADAFRUIT_NRF52_SD132, @@ -132,8 +140,14 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean, } ), + cv.Optional(CONF_FRAMEWORK, default={CONF_VERSION: "2.6.1-7"}): cv.Schema( + { + cv.Required(CONF_VERSION): cv.string_strict, + } + ), } ), + set_framework, ) @@ -172,7 +186,7 @@ async def to_code(config: ConfigType) -> None: cg.add_platformio_option( "platform_packages", [ - "platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v2.6.1-7.zip", + f"platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v{CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}.zip", "platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.17.4-0.zip", ], ) @@ -194,11 +208,22 @@ async def to_code(config: ConfigType) -> None: cg.add_platformio_option("board_upload.require_upload_port", "true") cg.add_platformio_option("board_upload.wait_for_upload_port", "true") + zephyr_setup_preferences() zephyr_to_code(config) if dfu_config := config.get(CONF_DFU): CORE.add_job(_dfu_to_code, dfu_config) - zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC]) + framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + if framework_ver < cv.Version(2, 9, 2): + zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC]) + else: + zephyr_add_overlay( + f""" + ®1 {{ + regulator-initial-mode = <{"NRF5X_REG_MODE_DCDC" if config[CONF_DCDC] else "NRF5X_REG_MODE_LDO"}>; + }}; + """ + ) if reg0_config := config.get(CONF_REG0): value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE]) @@ -206,6 +231,31 @@ async def to_code(config: ConfigType) -> None: if reg0_config[CONF_UICR_ERASE]: cg.add_define("USE_NRF52_UICR_ERASE") + # c++ support + if framework_ver < cv.Version(2, 9, 2): + zephyr_add_prj_conf("CPLUSPLUS", True) + zephyr_add_prj_conf("LIB_CPLUSPLUS", True) + else: + zephyr_add_prj_conf("CPP", True) + zephyr_add_prj_conf("REQUIRES_FULL_LIBCPP", True) + # watchdog + zephyr_add_prj_conf("WATCHDOG", True) + zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False) + # disable console + zephyr_add_prj_conf("UART_CONSOLE", False) + zephyr_add_prj_conf("CONSOLE", False) + # use NFC pins as GPIO + if framework_ver < cv.Version(2, 9, 2): + zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True) + else: + zephyr_add_overlay( + """ + &uicr { + nfct-pins-as-gpios; + }; + """ + ) + @coroutine_with_priority(CoroPriority.DIAGNOSTICS) async def _dfu_to_code(dfu_config): diff --git a/esphome/components/nrf52/gpio.py b/esphome/components/nrf52/gpio.py index 17329042b2..498e8cc330 100644 --- a/esphome/components/nrf52/gpio.py +++ b/esphome/components/nrf52/gpio.py @@ -71,8 +71,15 @@ NRF52_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(PLATFORM_NRF52, NRF52_PIN_SCHEMA) async def nrf52_pin_to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) num = config[CONF_NUMBER] + port = num // 32 + pin_name_prefix = f"P{port}." + var = cg.new_Pvariable( + config[CONF_ID], + cg.RawExpression(f"DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gpio{port}))"), + 32, + pin_name_prefix, + ) cg.add(var.set_pin(num)) # Only set if true to avoid bloating setup() function # (inverted bit in pin_flags_ bitfield is zero-initialized to false) diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 472e06ad61..0425714702 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -49,7 +49,7 @@ class Number : public EntityBase { */ virtual void control(float value) = 0; - CallbackManager state_callback_; + LazyCallbackManager state_callback_; }; } // namespace esphome::number diff --git a/esphome/components/one_wire/__init__.py b/esphome/components/one_wire/__init__.py index e12cca3e27..9173b7014b 100644 --- a/esphome/components/one_wire/__init__.py +++ b/esphome/components/one_wire/__init__.py @@ -32,7 +32,7 @@ async def register_one_wire_device(var, config): Sets the 1-wire bus to use and the 1-wire address. - This is a coroutine, you need to await it with a 'yield' expression! + This is a coroutine, you need to await it with an 'await' expression! """ parent = await cg.get_variable(config[CONF_ONE_WIRE_ID]) cg.add(var.set_one_wire_bus(parent)) diff --git a/esphome/components/one_wire/one_wire.cpp b/esphome/components/one_wire/one_wire.cpp index fd139d0ddc..187f559ca6 100644 --- a/esphome/components/one_wire/one_wire.cpp +++ b/esphome/components/one_wire/one_wire.cpp @@ -6,8 +6,10 @@ namespace one_wire { static const char *const TAG = "one_wire"; const std::string &OneWireDevice::get_address_name() { - if (this->address_name_.empty()) - this->address_name_ = std::string("0x") + format_hex(this->address_); + if (this->address_name_.empty()) { + char hex_buf[19]; // "0x" + 16 hex chars + null + this->address_name_ = format_hex_prefixed_to(hex_buf, this->address_); + } return this->address_name_; } diff --git a/esphome/components/one_wire/one_wire_bus.cpp b/esphome/components/one_wire/one_wire_bus.cpp index c2542177cf..27b7d58a0f 100644 --- a/esphome/components/one_wire/one_wire_bus.cpp +++ b/esphome/components/one_wire/one_wire_bus.cpp @@ -49,7 +49,8 @@ void OneWireBus::search() { break; auto *address8 = reinterpret_cast(&address); if (crc8(address8, 7) != address8[7]) { - ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str()); + char hex_buf[17]; + ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex_to(hex_buf, address)); } else { this->devices_.push_back(address); } @@ -82,8 +83,9 @@ void OneWireBus::dump_devices_(const char *tag) { ESP_LOGW(tag, " Found no devices!"); } else { ESP_LOGCONFIG(tag, " Found devices:"); + char hex_buf[17]; // uint64_t = 16 hex chars + null for (auto &address : this->devices_) { - ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff))); + ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex_to(hex_buf, address), LOG_STR_ARG(get_model_str(address & 0xff))); } } } diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index b23792fc7a..7a0cdc7f80 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -395,10 +395,8 @@ void OpenthermHub::dump_config() { this->write_initial_messages_(initial_messages); this->write_repeating_messages_(repeating_messages); - ESP_LOGCONFIG(TAG, "OpenTherm:"); - LOG_PIN(" In: ", this->in_pin_); - LOG_PIN(" Out: ", this->out_pin_); ESP_LOGCONFIG(TAG, + "OpenTherm:\n" " Sync mode: %s\n" " Sensors: %s\n" " Binary sensors: %s\n" @@ -409,6 +407,8 @@ void OpenthermHub::dump_config() { YESNO(this->sync_mode_), SHOW(OPENTHERM_SENSOR_LIST(ID, )), SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )), SHOW(OPENTHERM_SWITCH_LIST(ID, )), SHOW(OPENTHERM_INPUT_SENSOR_LIST(ID, )), SHOW(OPENTHERM_OUTPUT_LIST(ID, )), SHOW(OPENTHERM_NUMBER_LIST(ID, ))); + LOG_PIN(" In: ", this->in_pin_); + LOG_PIN(" Out: ", this->out_pin_); ESP_LOGCONFIG(TAG, " Initial requests:"); for (auto type : initial_messages) { ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type)); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index d59b9584d1..c6443f1282 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -7,6 +7,7 @@ #include "opentherm.h" #include "esphome/core/helpers.h" +#include #ifdef USE_ESP32 #include "driver/timer.h" #include "esp_err.h" @@ -20,7 +21,6 @@ namespace esphome { namespace opentherm { using std::string; -using std::to_string; static const char *const TAG = "opentherm"; @@ -563,14 +563,13 @@ const char *OpenTherm::message_id_to_str(MessageId id) { void OpenTherm::debug_data(OpenthermData &data) { ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(), format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str()); - ESP_LOGD(TAG, "type: %s; id: %s; HB: %s; LB: %s; uint_16: %s; float: %s", - this->message_type_to_str((MessageType) data.type), to_string(data.id).c_str(), - to_string(data.valueHB).c_str(), to_string(data.valueLB).c_str(), to_string(data.u16()).c_str(), - to_string(data.f88()).c_str()); + ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f", + this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(), + data.f88()); } void OpenTherm::debug_error(OpenThermError &error) const { - ESP_LOGD(TAG, "data: %s; clock: %s; capture: %s; bit_pos: %s", format_hex(error.data).c_str(), - to_string(clock_).c_str(), format_bin(error.capture).c_str(), to_string(error.bit_pos).c_str()); + ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_, + error.capture, error.bit_pos); } float OpenthermData::f88() { return ((float) this->s16()) / 256.0; } diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 5b1abe4fb5..26c05a0a86 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -91,7 +91,7 @@ def set_sdkconfig_options(config): add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True) add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5) - # TODO: Add suport for synchronized sleepy end devices (SSED) + # TODO: Add support for synchronized sleepy end devices (SSED) add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True) @@ -102,7 +102,7 @@ OpenThreadSrpComponent = openthread_ns.class_("OpenThreadSrpComponent", cg.Compo _CONNECTION_SCHEMA = cv.Schema( { cv.Optional(CONF_PAN_ID): cv.hex_int, - cv.Optional(CONF_CHANNEL): cv.int_, + cv.Optional(CONF_CHANNEL): cv.int_range(min=11, max=26), cv.Optional(CONF_NETWORK_KEY): cv.hex_int, cv.Optional(CONF_EXT_PAN_ID): cv.hex_int, cv.Optional(CONF_NETWORK_NAME): cv.string_strict, @@ -152,7 +152,6 @@ CONFIG_SCHEMA = cv.All( } ).extend(_CONNECTION_SCHEMA), cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV), - cv.only_with_esp_idf, only_on_variant(supported=[VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2]), _validate, _require_vfs_select, diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index 721ab89326..90da17e2d3 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -21,8 +21,7 @@ static const char *const TAG = "openthread"; -namespace esphome { -namespace openthread { +namespace esphome::openthread { OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -275,7 +274,5 @@ const char *OpenThreadComponent::get_use_address() const { return this->use_addr void OpenThreadComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; } -} // namespace openthread -} // namespace esphome - +} // namespace esphome::openthread #endif diff --git a/esphome/components/openthread/openthread.h b/esphome/components/openthread/openthread.h index 546128b366..3c60acaadd 100644 --- a/esphome/components/openthread/openthread.h +++ b/esphome/components/openthread/openthread.h @@ -13,8 +13,7 @@ #include #include -namespace esphome { -namespace openthread { +namespace esphome::openthread { class InstanceLock; @@ -91,6 +90,5 @@ class InstanceLock { InstanceLock() {} }; -} // namespace openthread -} // namespace esphome +} // namespace esphome::openthread #endif diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index 72dc521091..a9aff3cce4 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -1,5 +1,5 @@ #include "esphome/core/defines.h" -#if defined(USE_OPENTHREAD) && defined(USE_ESP_IDF) +#if defined(USE_OPENTHREAD) && defined(USE_ESP32) #include #include "openthread.h" @@ -24,8 +24,7 @@ static const char *const TAG = "openthread"; -namespace esphome { -namespace openthread { +namespace esphome::openthread { void OpenThreadComponent::setup() { // Used eventfds: @@ -127,9 +126,12 @@ void OpenThreadComponent::ot_main() { ESP_LOGE(TAG, "Failed to set OpenThread linkmode."); } link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance()); - ESP_LOGD(TAG, "Link Mode Device Type: %s", link_mode_config.mDeviceType ? "true" : "false"); - ESP_LOGD(TAG, "Link Mode Network Data: %s", link_mode_config.mNetworkData ? "true" : "false"); - ESP_LOGD(TAG, "Link Mode RX On When Idle: %s", link_mode_config.mRxOnWhenIdle ? "true" : "false"); + ESP_LOGD(TAG, + "Link Mode Device Type: %s\n" + "Link Mode Network Data: %s\n" + "Link Mode RX On When Idle: %s", + link_mode_config.mDeviceType ? "true" : "false", link_mode_config.mNetworkData ? "true" : "false", + link_mode_config.mRxOnWhenIdle ? "true" : "false"); // Run the main loop #if CONFIG_OPENTHREAD_CLI @@ -145,8 +147,8 @@ void OpenThreadComponent::ot_main() { // Make sure the length is 0 so we fallback to the configuration dataset.mLength = 0; } else { - ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration"); - ESP_LOGI(TAG, "(set force_dataset: true to override)"); + ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration\n" + "(set force_dataset: true to override)"); } #endif @@ -209,6 +211,5 @@ otInstance *InstanceLock::get_instance() { return esp_openthread_get_instance(); InstanceLock::~InstanceLock() { esp_openthread_lock_release(); } -} // namespace openthread -} // namespace esphome +} // namespace esphome::openthread #endif diff --git a/esphome/components/openthread_info/openthread_info_text_sensor.cpp b/esphome/components/openthread_info/openthread_info_text_sensor.cpp index 10724f3e2f..fc61ad81b2 100644 --- a/esphome/components/openthread_info/openthread_info_text_sensor.cpp +++ b/esphome/components/openthread_info/openthread_info_text_sensor.cpp @@ -3,8 +3,7 @@ #ifdef USE_OPENTHREAD #include "esphome/core/log.h" -namespace esphome { -namespace openthread_info { +namespace esphome::openthread_info { static const char *const TAG = "openthread_info"; @@ -19,6 +18,5 @@ void NetworkKeyOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Network Key" void PanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "PAN ID", this); } void ExtPanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Extended PAN ID", this); } -} // namespace openthread_info -} // namespace esphome +} // namespace esphome::openthread_info #endif diff --git a/esphome/components/openthread_info/openthread_info_text_sensor.h b/esphome/components/openthread_info/openthread_info_text_sensor.h index bbcd2d4655..ac5623e0c1 100644 --- a/esphome/components/openthread_info/openthread_info_text_sensor.h +++ b/esphome/components/openthread_info/openthread_info_text_sensor.h @@ -5,8 +5,7 @@ #include "esphome/core/component.h" #ifdef USE_OPENTHREAD -namespace esphome { -namespace openthread_info { +namespace esphome::openthread_info { using esphome::openthread::InstanceLock; @@ -34,13 +33,12 @@ class IPAddressOpenThreadInfo : public PollingComponent, public text_sensor::Tex return; } - char address_as_string[40]; - otIp6AddressToString(&*address, address_as_string, 40); - std::string ip = address_as_string; + char buf[OT_IP6_ADDRESS_STRING_SIZE]; + otIp6AddressToString(&*address, buf, sizeof(buf)); - if (this->last_ip_ != ip) { - this->last_ip_ = ip; - this->publish_state(this->last_ip_); + if (this->last_ip_ != buf) { + this->last_ip_ = buf; + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -90,7 +88,9 @@ class ExtAddrOpenThreadInfo : public OpenThreadInstancePollingComponent, public const auto *extaddr = otLinkGetExtendedAddress(instance); if (!std::equal(this->last_extaddr_.begin(), this->last_extaddr_.end(), extaddr->m8)) { std::copy(extaddr->m8, extaddr->m8 + 8, this->last_extaddr_.begin()); - this->publish_state(format_hex(extaddr->m8, 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, extaddr->m8, 8); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -108,7 +108,9 @@ class Eui64OpenThreadInfo : public OpenThreadInstancePollingComponent, public te if (!std::equal(this->last_eui64_.begin(), this->last_eui64_.end(), addr.m8)) { std::copy(addr.m8, addr.m8 + 8, this->last_eui64_.begin()); - this->publish_state(format_hex(this->last_eui64_.begin(), 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, this->last_eui64_.data(), 8); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -124,7 +126,9 @@ class ChannelOpenThreadInfo : public OpenThreadInstancePollingComponent, public uint8_t channel = otLinkGetChannel(instance); if (this->last_channel_ != channel) { this->last_channel_ = channel; - this->publish_state(std::to_string(this->last_channel_)); + char buf[4]; // max "255" + null + snprintf(buf, sizeof(buf), "%u", channel); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -169,7 +173,9 @@ class NetworkKeyOpenThreadInfo : public DatasetOpenThreadInfo, public text_senso void update_dataset(otOperationalDataset *dataset) override { if (!std::equal(this->last_key_.begin(), this->last_key_.end(), dataset->mNetworkKey.m8)) { std::copy(dataset->mNetworkKey.m8, dataset->mNetworkKey.m8 + 16, this->last_key_.begin()); - this->publish_state(format_hex(dataset->mNetworkKey.m8, 16)); + char buf[format_hex_size(16)]; + format_hex_to(buf, dataset->mNetworkKey.m8, 16); + this->publish_state(buf); } } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } @@ -202,7 +208,9 @@ class ExtPanIdOpenThreadInfo : public DatasetOpenThreadInfo, public text_sensor: void update_dataset(otOperationalDataset *dataset) override { if (!std::equal(this->last_extpanid_.begin(), this->last_extpanid_.end(), dataset->mExtendedPanId.m8)) { std::copy(dataset->mExtendedPanId.m8, dataset->mExtendedPanId.m8 + 8, this->last_extpanid_.begin()); - this->publish_state(format_hex(this->last_extpanid_.begin(), 8)); + char buf[format_hex_size(8)]; + format_hex_to(buf, this->last_extpanid_.data(), 8); + this->publish_state(buf); } } @@ -213,6 +221,5 @@ class ExtPanIdOpenThreadInfo : public DatasetOpenThreadInfo, public text_sensor: std::array last_extpanid_{}; }; -} // namespace openthread_info -} // namespace esphome +} // namespace esphome::openthread_info #endif diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index be1b6da241..ee54d5f8d3 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation import esphome.codegen as cg from esphome.config_helpers import filter_source_files_from_platform @@ -13,6 +15,8 @@ from esphome.const import ( from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority +OTA_STATE_LISTENER_KEY = "ota_state_listener" + CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["md5", "safe_mode"] @@ -25,6 +29,8 @@ CONF_ON_PROGRESS = "on_progress" CONF_ON_STATE_CHANGE = "on_state_change" +_LOGGER = logging.getLogger(__name__) + ota_ns = cg.esphome_ns.namespace("ota") OTAComponent = ota_ns.class_("OTAComponent", cg.Component) OTAState = ota_ns.enum("OTAState") @@ -43,6 +49,10 @@ def _ota_final_validate(config): raise cv.Invalid( f"At least one platform must be specified for '{CONF_OTA}'; add '{CONF_PLATFORM}: {CONF_ESPHOME}' for original OTA functionality" ) + if CORE.is_host: + _LOGGER.warning( + "OTA not available for platform 'host'. OTA functionality disabled." + ) FINAL_VALIDATE_SCHEMA = _ota_final_validate @@ -86,6 +96,7 @@ BASE_OTA_SCHEMA = cv.Schema( @coroutine_with_priority(CoroPriority.OTA_UPDATES) async def to_code(config): cg.add_define("USE_OTA") + CORE.add_job(final_step) if CORE.is_rp2040 and CORE.using_arduino: cg.add_library("Updater", None) @@ -119,7 +130,24 @@ async def ota_to_code(var, config): await automation.build_automation(trigger, [(cg.uint8, "x")], conf) use_state_callback = True if use_state_callback: - cg.add_define("USE_OTA_STATE_CALLBACK") + request_ota_state_listeners() + + +def request_ota_state_listeners() -> None: + """Request that OTA state listeners be compiled in. + + Components that need to be notified about OTA state changes (start, progress, + complete, error) should call this function during their code generation. + This enables the add_state_listener() API on OTAComponent. + """ + CORE.data[OTA_STATE_LISTENER_KEY] = True + + +@coroutine_with_priority(CoroPriority.FINAL) +async def final_step(): + """Final code generation step to configure optional OTA features.""" + if CORE.data.get(OTA_STATE_LISTENER_KEY, False): + cg.add_define("USE_OTA_STATE_LISTENER") FILTER_SOURCE_FILES = filter_source_files_from_platform( @@ -128,7 +156,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, }, - "ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "ota_backend_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, "ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, "ota_backend_arduino_libretiny.cpp": { PlatformFramework.BK72XX_ARDUINO, diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h index 7e1a60f3ce..92c0050ba0 100644 --- a/esphome/components/ota/automation.h +++ b/esphome/components/ota/automation.h @@ -1,5 +1,5 @@ #pragma once -#ifdef USE_OTA_STATE_CALLBACK +#ifdef USE_OTA_STATE_LISTENER #include "ota_backend.h" #include "esphome/core/automation.h" @@ -7,70 +7,64 @@ namespace esphome { namespace ota { -class OTAStateChangeTrigger : public Trigger { +class OTAStateChangeTrigger final : public Trigger, public OTAStateListener { public: - explicit OTAStateChangeTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (!parent->is_failed()) { - trigger(state); - } - }); + explicit OTAStateChangeTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } + + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (!this->parent_->is_failed()) { + this->trigger(state); + } } + + protected: + OTAComponent *parent_; }; -class OTAStartTrigger : public Trigger<> { +template class OTAStateTrigger final : public Trigger<>, public OTAStateListener { public: - explicit OTAStartTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_STARTED && !parent->is_failed()) { - trigger(); - } - }); + explicit OTAStateTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } + + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (state == State && !this->parent_->is_failed()) { + this->trigger(); + } } + + protected: + OTAComponent *parent_; }; -class OTAProgressTrigger : public Trigger { +using OTAStartTrigger = OTAStateTrigger; +using OTAEndTrigger = OTAStateTrigger; +using OTAAbortTrigger = OTAStateTrigger; + +class OTAProgressTrigger final : public Trigger, public OTAStateListener { public: - explicit OTAProgressTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_IN_PROGRESS && !parent->is_failed()) { - trigger(progress); - } - }); + explicit OTAProgressTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } + + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (state == OTA_IN_PROGRESS && !this->parent_->is_failed()) { + this->trigger(progress); + } } + + protected: + OTAComponent *parent_; }; -class OTAEndTrigger : public Trigger<> { +class OTAErrorTrigger final : public Trigger, public OTAStateListener { public: - explicit OTAEndTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_COMPLETED && !parent->is_failed()) { - trigger(); - } - }); - } -}; + explicit OTAErrorTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); } -class OTAAbortTrigger : public Trigger<> { - public: - explicit OTAAbortTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_ABORT && !parent->is_failed()) { - trigger(); - } - }); + void on_ota_state(OTAState state, float progress, uint8_t error) override { + if (state == OTA_ERROR && !this->parent_->is_failed()) { + this->trigger(error); + } } -}; -class OTAErrorTrigger : public Trigger { - public: - explicit OTAErrorTrigger(OTAComponent *parent) { - parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == OTA_ERROR && !parent->is_failed()) { - trigger(error); - } - }); - } + protected: + OTAComponent *parent_; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend.cpp b/esphome/components/ota/ota_backend.cpp index 30de4ec4b3..8fb9f67214 100644 --- a/esphome/components/ota/ota_backend.cpp +++ b/esphome/components/ota/ota_backend.cpp @@ -3,7 +3,7 @@ namespace esphome { namespace ota { -#ifdef USE_OTA_STATE_CALLBACK +#ifdef USE_OTA_STATE_LISTENER OTAGlobalCallback *global_ota_callback{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) OTAGlobalCallback *get_global_ota_callback() { @@ -13,7 +13,12 @@ OTAGlobalCallback *get_global_ota_callback() { return global_ota_callback; } -void register_ota_platform(OTAComponent *ota_caller) { get_global_ota_callback()->register_ota(ota_caller); } +void OTAComponent::notify_state_(OTAState state, float progress, uint8_t error) { + for (auto *listener : this->state_listeners_) { + listener->on_ota_state(state, progress, error); + } + get_global_ota_callback()->notify_ota_state(state, progress, error, this); +} #endif } // namespace ota diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h index 64ee0b9f7c..e03afd4fc6 100644 --- a/esphome/components/ota/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -4,8 +4,8 @@ #include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#ifdef USE_OTA_STATE_CALLBACK -#include "esphome/core/automation.h" +#ifdef USE_OTA_STATE_LISTENER +#include #endif namespace esphome { @@ -60,62 +60,75 @@ class OTABackend { virtual bool supports_compression() = 0; }; -class OTAComponent : public Component { -#ifdef USE_OTA_STATE_CALLBACK +/** Listener interface for OTA state changes. + * + * Components can implement this interface to receive OTA state updates + * without the overhead of std::function callbacks. + */ +class OTAStateListener { public: - void add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); - } + virtual ~OTAStateListener() = default; + virtual void on_ota_state(OTAState state, float progress, uint8_t error) = 0; +}; + +class OTAComponent : public Component { +#ifdef USE_OTA_STATE_LISTENER + public: + void add_state_listener(OTAStateListener *listener) { this->state_listeners_.push_back(listener); } protected: - /** Extended callback manager with deferred call support. + void notify_state_(OTAState state, float progress, uint8_t error); + + /** Notify state with deferral to main loop (for thread safety). * - * This adds a call_deferred() method for thread-safe execution from other tasks. + * This should be used by OTA implementations that run in separate tasks + * (like web_server OTA) to ensure listeners execute in the main loop. */ - class StateCallbackManager : public CallbackManager { - public: - StateCallbackManager(OTAComponent *component) : component_(component) {} + void notify_state_deferred_(OTAState state, float progress, uint8_t error) { + this->defer([this, state, progress, error]() { this->notify_state_(state, progress, error); }); + } - /** Call callbacks with deferral to main loop (for thread safety). - * - * This should be used by OTA implementations that run in separate tasks - * (like web_server OTA) to ensure callbacks execute in the main loop. - */ - void call_deferred(ota::OTAState state, float progress, uint8_t error) { - component_->defer([this, state, progress, error]() { this->call(state, progress, error); }); - } - - private: - OTAComponent *component_; - }; - - StateCallbackManager state_callback_{this}; + std::vector state_listeners_; #endif }; -#ifdef USE_OTA_STATE_CALLBACK +#ifdef USE_OTA_STATE_LISTENER + +/** Listener interface for global OTA state changes (includes OTA component pointer). + * + * Used by OTAGlobalCallback to aggregate state from multiple OTA components. + */ +class OTAGlobalStateListener { + public: + virtual ~OTAGlobalStateListener() = default; + virtual void on_ota_global_state(OTAState state, float progress, uint8_t error, OTAComponent *component) = 0; +}; + +/** Global callback that aggregates OTA state from all OTA components. + * + * OTA components call notify_ota_state() directly with their pointer, + * which forwards the event to all registered global listeners. + */ class OTAGlobalCallback { public: - void register_ota(OTAComponent *ota_caller) { - ota_caller->add_on_state_callback([this, ota_caller](OTAState state, float progress, uint8_t error) { - this->state_callback_.call(state, progress, error, ota_caller); - }); - } - void add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); + void add_global_state_listener(OTAGlobalStateListener *listener) { this->global_listeners_.push_back(listener); } + + void notify_ota_state(OTAState state, float progress, uint8_t error, OTAComponent *component) { + for (auto *listener : this->global_listeners_) { + listener->on_ota_global_state(state, progress, error, component); + } } protected: - CallbackManager state_callback_{}; + std::vector global_listeners_; }; OTAGlobalCallback *get_global_ota_callback(); -void register_ota_platform(OTAComponent *ota_caller); // OTA implementations should use: -// - state_callback_.call() when already in main loop (e.g., esphome OTA) -// - state_callback_.call_deferred() when in separate task (e.g., web_server OTA) -// This ensures proper callback execution in all contexts. +// - notify_state_() when already in main loop (e.g., esphome OTA) +// - notify_state_deferred_() when in separate task (e.g., web_server OTA) +// This ensures proper listener execution in all contexts. #endif std::unique_ptr make_ota_backend(); diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp deleted file mode 100644 index 375c4e7200..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp8266.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include "ota_backend_arduino_esp8266.h" -#include "ota_backend.h" - -#include "esphome/components/esp8266/preferences.h" -#include "esphome/core/defines.h" -#include "esphome/core/log.h" - -#include - -namespace esphome { -namespace ota { - -static const char *const TAG = "ota.arduino_esp8266"; - -std::unique_ptr make_ota_backend() { return make_unique(); } - -OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { - // Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space - if (image_size == 0) { - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; - } - bool ret = Update.begin(image_size, U_FLASH); - if (ret) { - esp8266::preferences_prevent_write(true); - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - if (error == UPDATE_ERROR_BOOTSTRAP) - return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; - if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; - if (error == UPDATE_ERROR_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; - if (error == UPDATE_ERROR_SPACE) - return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; - - ESP_LOGE(TAG, "Begin error: %d", error); - - return OTA_RESPONSE_ERROR_UNKNOWN; -} - -void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { - Update.setMD5(md5); - this->md5_set_ = true; -} - -OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { - size_t written = Update.write(data, len); - if (written == len) { - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - ESP_LOGE(TAG, "Write error: %d", error); - - return OTA_RESPONSE_ERROR_WRITING_FLASH; -} - -OTAResponseTypes ArduinoESP8266OTABackend::end() { - // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 - // This matches the behavior of the old web_server OTA implementation - bool success = Update.end(!this->md5_set_); - - // On ESP8266, Update.end() might return false even with error code 0 - // Check the actual error code to determine success - uint8_t error = Update.getError(); - - if (success || error == UPDATE_ERROR_OK) { - return OTA_RESPONSE_OK; - } - - ESP_LOGE(TAG, "End error: %d", error); - return OTA_RESPONSE_ERROR_UPDATE_END; -} - -void ArduinoESP8266OTABackend::abort() { - Update.end(); - esp8266::preferences_prevent_write(false); -} - -} // namespace ota -} // namespace esphome - -#endif -#endif diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h deleted file mode 100644 index e1b9015cc7..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include "ota_backend.h" - -#include "esphome/core/defines.h" -#include "esphome/core/macros.h" - -namespace esphome { -namespace ota { - -class ArduinoESP8266OTABackend : public OTABackend { - public: - OTAResponseTypes begin(size_t image_size) override; - void set_update_md5(const char *md5) override; - OTAResponseTypes write(uint8_t *data, size_t len) override; - OTAResponseTypes end() override; - void abort() override; -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - bool supports_compression() override { return true; } -#else - bool supports_compression() override { return false; } -#endif - - private: - bool md5_set_{false}; -}; - -} // namespace ota -} // namespace esphome - -#endif -#endif diff --git a/esphome/components/ota/ota_backend_esp8266.cpp b/esphome/components/ota/ota_backend_esp8266.cpp new file mode 100644 index 0000000000..4b84708cd9 --- /dev/null +++ b/esphome/components/ota/ota_backend_esp8266.cpp @@ -0,0 +1,356 @@ +#ifdef USE_ESP8266 +#include "ota_backend_esp8266.h" +#include "ota_backend.h" + +#include "esphome/components/esp8266/preferences.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include + +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +// Note: FLASH_SECTOR_SIZE (0x1000) is already defined in spi_flash_geometry.h + +// Flash header offsets +static constexpr uint8_t FLASH_MODE_OFFSET = 2; + +// Firmware magic bytes +static constexpr uint8_t FIRMWARE_MAGIC = 0xE9; +static constexpr uint8_t GZIP_MAGIC_1 = 0x1F; +static constexpr uint8_t GZIP_MAGIC_2 = 0x8B; + +// ESP8266 flash memory base address (memory-mapped flash starts here) +static constexpr uint32_t FLASH_BASE_ADDRESS = 0x40200000; + +// Boot mode extraction from GPI register (bits 16-19 contain boot mode) +static constexpr int BOOT_MODE_SHIFT = 16; +static constexpr int BOOT_MODE_MASK = 0xf; + +// Boot mode indicating UART download mode (OTA not possible) +static constexpr int BOOT_MODE_UART_DOWNLOAD = 1; + +// Minimum buffer size when memory is constrained +static constexpr size_t MIN_BUFFER_SIZE = 256; + +namespace esphome::ota { + +static const char *const TAG = "ota.esp8266"; + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes ESP8266OTABackend::begin(size_t image_size) { + // Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space + if (image_size == 0) { + // Round down to sector boundary: subtract one sector, then mask to sector alignment + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + image_size = (ESP.getFreeSketchSpace() - FLASH_SECTOR_SIZE) & ~(FLASH_SECTOR_SIZE - 1); + } + + // Check boot mode - if boot mode is UART download mode, + // we will not be able to reset into normal mode once update is done + int boot_mode = (GPI >> BOOT_MODE_SHIFT) & BOOT_MODE_MASK; + if (boot_mode == BOOT_MODE_UART_DOWNLOAD) { + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + } + + // Check flash configuration - real size must be >= configured size + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + if (!ESP.checkFlashConfig(false)) { + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + } + + // Get current sketch size + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + uint32_t sketch_size = ESP.getSketchSize(); + + // Size of current sketch rounded to sector boundary + uint32_t current_sketch_size = (sketch_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + + // Size of update rounded to sector boundary + uint32_t rounded_size = (image_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + + // End of available space for sketch and update (start of filesystem) + uint32_t update_end_address = FS_start - FLASH_BASE_ADDRESS; + + // Calculate start address for the update (write from end backwards) + this->start_address_ = (update_end_address > rounded_size) ? (update_end_address - rounded_size) : 0; + + // Check if there's enough space for both current sketch and update + if (this->start_address_ < current_sketch_size) { + return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; + } + + // Allocate buffer for sector writes (use smaller buffer if memory constrained) + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->buffer_size_ = (ESP.getFreeHeap() > 2 * FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : MIN_BUFFER_SIZE; + + // ESP8266's umm_malloc guarantees 4-byte aligned allocations, which is required + // for spi_flash_write(). This is the same pattern used by Arduino's Updater class. + this->buffer_ = make_unique(this->buffer_size_); + if (!this->buffer_) { + return OTA_RESPONSE_ERROR_UNKNOWN; + } + + this->current_address_ = this->start_address_; + this->image_size_ = image_size; + this->buffer_len_ = 0; + this->md5_set_ = false; + + // Disable WiFi sleep during update + wifi_set_sleep_type(NONE_SLEEP_T); + + // Prevent preference writes during update + esp8266::preferences_prevent_write(true); + + // Initialize MD5 computation + this->md5_.init(); + + ESP_LOGD(TAG, "OTA begin: start=0x%08" PRIX32 ", size=%zu", this->start_address_, image_size); + + return OTA_RESPONSE_OK; +} + +void ESP8266OTABackend::set_update_md5(const char *md5) { + // Parse hex string to bytes + if (parse_hex(md5, this->expected_md5_, 16)) { + this->md5_set_ = true; + } +} + +OTAResponseTypes ESP8266OTABackend::write(uint8_t *data, size_t len) { + if (!this->buffer_) { + return OTA_RESPONSE_ERROR_UNKNOWN; + } + + size_t written = 0; + while (written < len) { + // Calculate how much we can buffer + size_t to_buffer = std::min(len - written, this->buffer_size_ - this->buffer_len_); + memcpy(this->buffer_.get() + this->buffer_len_, data + written, to_buffer); + this->buffer_len_ += to_buffer; + written += to_buffer; + + // If buffer is full, write to flash + if (this->buffer_len_ == this->buffer_size_ && !this->write_buffer_()) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + } + + return OTA_RESPONSE_OK; +} + +bool ESP8266OTABackend::erase_sector_if_needed_() { + if ((this->current_address_ % FLASH_SECTOR_SIZE) != 0) { + return true; // Not at sector boundary + } + + App.feed_wdt(); + if (spi_flash_erase_sector(this->current_address_ / FLASH_SECTOR_SIZE) != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Flash erase failed at 0x%08" PRIX32, this->current_address_); + return false; + } + return true; +} + +bool ESP8266OTABackend::flash_write_() { + App.feed_wdt(); + if (spi_flash_write(this->current_address_, reinterpret_cast(this->buffer_.get()), this->buffer_len_) != + SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Flash write failed at 0x%08" PRIX32, this->current_address_); + return false; + } + return true; +} + +bool ESP8266OTABackend::write_buffer_() { + if (this->buffer_len_ == 0) { + return true; + } + + if (!this->erase_sector_if_needed_()) { + return false; + } + + // Patch flash mode in first sector if needed + // This is analogous to what esptool.py does when it receives a --flash_mode argument + bool is_first_sector = (this->current_address_ == this->start_address_); + uint8_t original_flash_mode = 0; + bool patched_flash_mode = false; + + // Only patch if we have enough bytes to access flash mode offset and it's not GZIP + if (is_first_sector && this->buffer_len_ > FLASH_MODE_OFFSET && this->buffer_[0] != GZIP_MAGIC_1) { + // Not GZIP compressed - check and patch flash mode + uint8_t current_flash_mode = this->get_flash_chip_mode_(); + uint8_t buffer_flash_mode = this->buffer_[FLASH_MODE_OFFSET]; + + if (buffer_flash_mode != current_flash_mode) { + original_flash_mode = buffer_flash_mode; + this->buffer_[FLASH_MODE_OFFSET] = current_flash_mode; + patched_flash_mode = true; + } + } + + if (!this->flash_write_()) { + return false; + } + + // Restore original flash mode for MD5 calculation + if (patched_flash_mode) { + this->buffer_[FLASH_MODE_OFFSET] = original_flash_mode; + } + + // Update MD5 with original (unpatched) data + this->md5_.add(this->buffer_.get(), this->buffer_len_); + + this->current_address_ += this->buffer_len_; + this->buffer_len_ = 0; + + return true; +} + +bool ESP8266OTABackend::write_buffer_final_() { + // Similar to write_buffer_(), but without flash mode patching or MD5 update (for final padded write) + if (this->buffer_len_ == 0) { + return true; + } + + if (!this->erase_sector_if_needed_() || !this->flash_write_()) { + return false; + } + + this->current_address_ += this->buffer_len_; + this->buffer_len_ = 0; + + return true; +} + +OTAResponseTypes ESP8266OTABackend::end() { + // Write any remaining buffered data + if (this->buffer_len_ > 0) { + // Add actual data to MD5 before padding + this->md5_.add(this->buffer_.get(), this->buffer_len_); + + // Pad to 4-byte alignment for flash write + while (this->buffer_len_ % 4 != 0) { + this->buffer_[this->buffer_len_++] = 0xFF; + } + if (!this->write_buffer_final_()) { + this->abort(); + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + } + + // Calculate actual bytes written + size_t actual_size = this->current_address_ - this->start_address_; + + // Check if any data was written + if (actual_size == 0) { + ESP_LOGE(TAG, "No data written"); + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } + + // Verify MD5 if set (strict mode), otherwise use lenient mode + // In lenient mode (no MD5), we accept whatever was written + if (this->md5_set_) { + this->md5_.calculate(); + if (!this->md5_.equals_bytes(this->expected_md5_)) { + ESP_LOGE(TAG, "MD5 mismatch"); + this->abort(); + return OTA_RESPONSE_ERROR_MD5_MISMATCH; + } + } else { + // Lenient mode: adjust size to what was actually written + // This matches Arduino's Update.end(true) behavior + this->image_size_ = actual_size; + } + + // Verify firmware header + if (!this->verify_end_()) { + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } + + // Write eboot command to copy firmware on next boot + eboot_command ebcmd; + ebcmd.action = ACTION_COPY_RAW; + ebcmd.args[0] = this->start_address_; + ebcmd.args[1] = 0x00000; // Destination: start of flash + ebcmd.args[2] = this->image_size_; + eboot_command_write(&ebcmd); + + ESP_LOGI(TAG, "OTA update staged: 0x%08" PRIX32 " -> 0x00000, size=%zu", this->start_address_, this->image_size_); + + // Clean up + this->buffer_.reset(); + esp8266::preferences_prevent_write(false); + + return OTA_RESPONSE_OK; +} + +void ESP8266OTABackend::abort() { + this->buffer_.reset(); + this->buffer_len_ = 0; + this->image_size_ = 0; + esp8266::preferences_prevent_write(false); +} + +bool ESP8266OTABackend::verify_end_() { + uint32_t buf; + if (spi_flash_read(this->start_address_, &buf, 4) != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Failed to read firmware header"); + return false; + } + + uint8_t *bytes = reinterpret_cast(&buf); + + // Check for GZIP (compressed firmware) + if (bytes[0] == GZIP_MAGIC_1 && bytes[1] == GZIP_MAGIC_2) { + // GZIP compressed - can't verify further + return true; + } + + // Check firmware magic byte + if (bytes[0] != FIRMWARE_MAGIC) { + ESP_LOGE(TAG, "Invalid firmware magic: 0x%02X (expected 0x%02X)", bytes[0], FIRMWARE_MAGIC); + return false; + } + +#if !FLASH_MAP_SUPPORT + // Check if new firmware's flash size fits (only when auto-detection is disabled) + // With FLASH_MAP_SUPPORT (modern cores), flash size is auto-detected from chip + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + uint32_t bin_flash_size = ESP.magicFlashChipSize((bytes[3] & 0xf0) >> 4); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + if (bin_flash_size > ESP.getFlashChipRealSize()) { + ESP_LOGE(TAG, "Firmware flash size (%" PRIu32 ") exceeds chip size (%" PRIu32 ")", bin_flash_size, + ESP.getFlashChipRealSize()); + return false; + } +#endif + + return true; +} + +uint8_t ESP8266OTABackend::get_flash_chip_mode_() { + uint32_t data; + if (spi_flash_read(0x0000, &data, 4) != SPI_FLASH_RESULT_OK) { + return 0; // Default to QIO + } + return (reinterpret_cast(&data))[FLASH_MODE_OFFSET]; +} + +} // namespace esphome::ota +#endif // USE_ESP8266 diff --git a/esphome/components/ota/ota_backend_esp8266.h b/esphome/components/ota/ota_backend_esp8266.h new file mode 100644 index 0000000000..a9d6dd2ccc --- /dev/null +++ b/esphome/components/ota/ota_backend_esp8266.h @@ -0,0 +1,58 @@ +#pragma once +#ifdef USE_ESP8266 +#include "ota_backend.h" + +#include "esphome/components/md5/md5.h" +#include "esphome/core/defines.h" + +#include + +namespace esphome::ota { + +/// OTA backend for ESP8266 using native SDK functions. +/// This implementation bypasses the Arduino Updater library to save ~228 bytes of RAM +/// by not having a global Update object in .bss. +class ESP8266OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + // Compression supported in all ESP8266 Arduino versions ESPHome supports (>= 2.7.0) + bool supports_compression() override { return true; } + + protected: + /// Erase flash sector if current address is at sector boundary + bool erase_sector_if_needed_(); + + /// Write buffer to flash (does not update address or clear buffer) + bool flash_write_(); + + /// Write buffered data to flash and update MD5 + bool write_buffer_(); + + /// Write buffered data to flash without MD5 update (for final padded write) + bool write_buffer_final_(); + + /// Verify the firmware header is valid + bool verify_end_(); + + /// Get current flash chip mode from flash header + uint8_t get_flash_chip_mode_(); + + std::unique_ptr buffer_; + size_t buffer_size_{0}; + size_t buffer_len_{0}; + + uint32_t start_address_{0}; + uint32_t current_address_{0}; + size_t image_size_{0}; + + md5::MD5Digest md5_{}; + uint8_t expected_md5_[16]; // Fixed-size buffer for 128-bit (16-byte) MD5 digest + bool md5_set_{false}; +}; + +} // namespace esphome::ota +#endif // USE_ESP8266 diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index f278c3741f..93c65a9624 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -14,6 +14,13 @@ namespace ota { std::unique_ptr make_ota_backend() { return make_unique(); } OTAResponseTypes IDFOTABackend::begin(size_t image_size) { +#ifdef USE_OTA_ROLLBACK + // If we're starting an OTA, the current boot is good enough - mark it valid + // to prevent rollback and allow the OTA to proceed even if the safe mode + // timer hasn't expired yet. + esp_ota_mark_app_valid_cancel_rollback(); +#endif + this->partition_ = esp_ota_get_next_update_partition(nullptr); if (this->partition_ == nullptr) { return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; diff --git a/esphome/components/ota/ota_backend_host.cpp b/esphome/components/ota/ota_backend_host.cpp new file mode 100644 index 0000000000..ddab174bed --- /dev/null +++ b/esphome/components/ota/ota_backend_host.cpp @@ -0,0 +1,24 @@ +#ifdef USE_HOST +#include "ota_backend_host.h" + +#include "esphome/core/defines.h" + +namespace esphome::ota { + +// Stub implementation - OTA is not supported on host platform. +// All methods return error codes to allow compilation of configs with OTA triggers. + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes HostOTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UPDATE_PREPARE; } + +void HostOTABackend::set_update_md5(const char *expected_md5) {} + +OTAResponseTypes HostOTABackend::write(uint8_t *data, size_t len) { return OTA_RESPONSE_ERROR_WRITING_FLASH; } + +OTAResponseTypes HostOTABackend::end() { return OTA_RESPONSE_ERROR_UPDATE_END; } + +void HostOTABackend::abort() {} + +} // namespace esphome::ota +#endif diff --git a/esphome/components/ota/ota_backend_host.h b/esphome/components/ota/ota_backend_host.h new file mode 100644 index 0000000000..ae7d0cb0b3 --- /dev/null +++ b/esphome/components/ota/ota_backend_host.h @@ -0,0 +1,21 @@ +#pragma once +#ifdef USE_HOST +#include "ota_backend.h" + +namespace esphome::ota { + +/// Stub OTA backend for host platform - allows compilation but does not implement OTA. +/// All operations return error codes immediately. This enables configurations with +/// OTA triggers to compile for host platform during development. +class HostOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace esphome::ota +#endif diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 8ae2f759d0..cefe9a604e 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -1,11 +1,15 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "packet_transport.h" #include "esphome/components/xxtea/xxtea.h" namespace esphome { namespace packet_transport { + +// Maximum bytes to log in hex output (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t PACKET_MAX_LOG_BYTES = 168; /** * Structure of a data packet; everything is little-endian * @@ -263,7 +267,8 @@ void PacketTransport::flush_() { xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4, (uint32_t *) this->encryption_key_.data()); } - ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty(encode_buffer.data(), encode_buffer.size()).c_str()); + char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)]; + ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty_to(hex_buf, encode_buffer.data(), encode_buffer.size())); this->send_packet(encode_buffer); } @@ -505,8 +510,9 @@ void PacketTransport::process_(const std::vector &data) { } if (decoder.get(byte) == DECODE_OK) { ESP_LOGW(TAG, "Unknown key %X", byte); + char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)]; ESP_LOGD(TAG, "Buffer pos: %zu contents: %s", data.size() - decoder.get_remaining_size(), - format_hex_pretty(data).c_str()); + format_hex_pretty_to(hex_buf, data.data(), data.size())); } break; } diff --git a/esphome/components/pca6416a/pca6416a.cpp b/esphome/components/pca6416a/pca6416a.cpp index c0056e780b..909bac5f05 100644 --- a/esphome/components/pca6416a/pca6416a.cpp +++ b/esphome/components/pca6416a/pca6416a.cpp @@ -180,10 +180,8 @@ void PCA6416AGPIOPin::setup() { pin_mode(flags_); } void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PCA6416AGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_); - return buffer; +size_t PCA6416AGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PCA6416A", this->pin_); } } // namespace pca6416a diff --git a/esphome/components/pca6416a/pca6416a.h b/esphome/components/pca6416a/pca6416a.h index 10a4a64e9b..138a51cc20 100644 --- a/esphome/components/pca6416a/pca6416a.h +++ b/esphome/components/pca6416a/pca6416a.h @@ -52,7 +52,7 @@ class PCA6416AGPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(PCA6416AComponent *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index e8d49f66e2..a6f9c2396c 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -129,10 +129,8 @@ void PCA9554GPIOPin::setup() { pin_mode(flags_); } void PCA9554GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCA9554GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PCA9554GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via PCA9554", pin_); - return buffer; +size_t PCA9554GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PCA9554", this->pin_); } } // namespace pca9554 diff --git a/esphome/components/pca9554/pca9554.h b/esphome/components/pca9554/pca9554.h index 7b356b4068..bf752e50c9 100644 --- a/esphome/components/pca9554/pca9554.h +++ b/esphome/components/pca9554/pca9554.h @@ -59,7 +59,7 @@ class PCA9554GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(PCA9554Component *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/pcd8544/pcd_8544.cpp b/esphome/components/pcd8544/pcd_8544.cpp index f5b018b127..95d91ff18a 100644 --- a/esphome/components/pcd8544/pcd_8544.cpp +++ b/esphome/components/pcd8544/pcd_8544.cpp @@ -117,6 +117,12 @@ void PCD8544::update() { } void PCD8544::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + uint8_t fill = color.is_on() ? 0xFF : 0x00; for (uint32_t i = 0; i < this->get_buffer_length_(); i++) this->buffer_[i] = fill; diff --git a/esphome/components/pcf8574/pcf8574.cpp b/esphome/components/pcf8574/pcf8574.cpp index 72d8865d7f..8bdd312ab9 100644 --- a/esphome/components/pcf8574/pcf8574.cpp +++ b/esphome/components/pcf8574/pcf8574.cpp @@ -21,9 +21,11 @@ void PCF8574Component::loop() { this->reset_pin_cache_(); } void PCF8574Component::dump_config() { - ESP_LOGCONFIG(TAG, "PCF8574:"); + ESP_LOGCONFIG(TAG, + "PCF8574:\n" + " Is PCF8575: %s", + YESNO(this->pcf8575_)); LOG_I2C_DEVICE(this) - ESP_LOGCONFIG(TAG, " Is PCF8575: %s", YESNO(this->pcf8575_)); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } @@ -104,10 +106,8 @@ void PCF8574GPIOPin::setup() { pin_mode(flags_); } void PCF8574GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool PCF8574GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void PCF8574GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PCF8574GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via PCF8574", pin_); - return buffer; +size_t PCF8574GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PCF8574", this->pin_); } } // namespace pcf8574 diff --git a/esphome/components/pcf8574/pcf8574.h b/esphome/components/pcf8574/pcf8574.h index fd1ea8af63..5203030142 100644 --- a/esphome/components/pcf8574/pcf8574.h +++ b/esphome/components/pcf8574/pcf8574.h @@ -54,7 +54,7 @@ class PCF8574GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(PCF8574Component *parent) { parent_ = parent; } void set_pin(uint8_t pin) { pin_ = pin; } diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp index 517ca833e6..f3a1f013d9 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp @@ -164,7 +164,9 @@ bool PI4IOE5V6408GPIOPin::digital_read() { return this->parent_->digital_read(th void PI4IOE5V6408GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string PI4IOE5V6408GPIOPin::dump_summary() const { return str_sprintf("%u via PI4IOE5V6408", this->pin_); } +size_t PI4IOE5V6408GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via PI4IOE5V6408", this->pin_); +} } // namespace pi4ioe5v6408 } // namespace esphome diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h index 82b3076fab..4dc31201ce 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h @@ -52,7 +52,7 @@ class PI4IOE5V6408GPIOPin : public GPIOPin, public Parentedpin_ = pin; } void set_inverted(bool inverted) { this->inverted_ = inverted; } diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index 28d16e17ab..d1d9c200cf 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -138,20 +138,21 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce } void PIDAutotuner::dump_config() { if (this->state_ == AUTOTUNE_SUCCEEDED) { - ESP_LOGI(TAG, "%s: PID Autotune:", this->id_.c_str()); - ESP_LOGI(TAG, " State: Succeeded!"); + ESP_LOGI(TAG, + "%s: PID Autotune:\n" + " State: Succeeded!", + this->id_.c_str()); bool has_issue = false; if (!this->amplitude_detector_.is_amplitude_convergent()) { - ESP_LOGW(TAG, " Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!"); - ESP_LOGW(TAG, " Please make sure you eliminate all outside influences on the measured temperature."); + ESP_LOGW(TAG, " Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!\n" + " Please make sure you eliminate all outside influences on the measured temperature."); has_issue = true; } if (!this->frequency_detector_.is_increase_decrease_symmetrical()) { - ESP_LOGW(TAG, " Oscillation Frequency is not symmetrical. PID parameters may be inaccurate!"); - ESP_LOGW( - TAG, - " This is usually because the heat and cool processes do not change the temperature at the same rate."); ESP_LOGW(TAG, + " Oscillation Frequency is not symmetrical. PID parameters may be inaccurate!\n" + " This is usually because the heat and cool processes do not change the temperature at the same " + "rate.\n" " Please try reducing the positive_output value (or increase negative_output in case of a cooler)"); has_issue = true; } @@ -160,18 +161,23 @@ void PIDAutotuner::dump_config() { } auto fac = get_ziegler_nichols_pid_(); - ESP_LOGI(TAG, " Calculated PID parameters (\"Ziegler-Nichols PID\" rule):"); - ESP_LOGI(TAG, " "); - ESP_LOGI(TAG, " control_parameters:"); - ESP_LOGI(TAG, " kp: %.5f", fac.kp); - ESP_LOGI(TAG, " ki: %.5f", fac.ki); - ESP_LOGI(TAG, " kd: %.5f", fac.kd); - ESP_LOGI(TAG, " "); - ESP_LOGI(TAG, " Please copy these values into your YAML configuration! They will reset on the next reboot."); + ESP_LOGI(TAG, + " Calculated PID parameters (\"Ziegler-Nichols PID\" rule):\n" + "\n" + " control_parameters:\n" + " kp: %.5f\n" + " ki: %.5f\n" + " kd: %.5f\n" + "\n" + " Please copy these values into your YAML configuration! They will reset on the next reboot.", + fac.kp, fac.ki, fac.kd); - ESP_LOGV(TAG, " Oscillation Period: %f", this->frequency_detector_.get_mean_oscillation_period()); - ESP_LOGV(TAG, " Oscillation Amplitude: %f", this->amplitude_detector_.get_mean_oscillation_amplitude()); - ESP_LOGV(TAG, " Ku: %f, Pu: %f", this->ku_, this->pu_); + ESP_LOGV(TAG, + " Oscillation Period: %f\n" + " Oscillation Amplitude: %f\n" + " Ku: %f, Pu: %f", + this->frequency_detector_.get_mean_oscillation_period(), + this->amplitude_detector_.get_mean_oscillation_amplitude(), this->ku_, this->pu_); ESP_LOGD(TAG, " Alternative Rules:"); // http://www.mstarlabs.com/control/znrule.html @@ -183,13 +189,16 @@ void PIDAutotuner::dump_config() { } if (this->state_ == AUTOTUNE_RUNNING) { - ESP_LOGD(TAG, "%s: PID Autotune:", this->id_.c_str()); - ESP_LOGD(TAG, " Autotune is still running!"); - ESP_LOGD(TAG, " Status: Trying to reach %.2f °C", setpoint_ - relay_function_.current_target_error()); - ESP_LOGD(TAG, " Stats so far:"); - ESP_LOGD(TAG, " Phases: %" PRIu32, relay_function_.phase_count); - ESP_LOGD(TAG, " Detected %zu zero-crossings", frequency_detector_.zerocrossing_intervals.size()); - ESP_LOGD(TAG, " Current Phase Min: %.2f, Max: %.2f", amplitude_detector_.phase_min, + ESP_LOGD(TAG, + "%s: PID Autotune:\n" + " Autotune is still running!\n" + " Status: Trying to reach %.2f °C\n" + " Stats so far:\n" + " Phases: %" PRIu32 "\n" + " Detected %zu zero-crossings\n" + " Current Phase Min: %.2f, Max: %.2f", + this->id_.c_str(), setpoint_ - relay_function_.current_target_error(), relay_function_.phase_count, + frequency_detector_.zerocrossing_intervals.size(), amplitude_detector_.phase_min, amplitude_detector_.phase_max); } } @@ -205,8 +214,10 @@ PIDAutotuner::PIDResult PIDAutotuner::calculate_pid_(float kp_factor, float ki_f } void PIDAutotuner::print_rule_(const char *name, float kp_factor, float ki_factor, float kd_factor) { auto fac = calculate_pid_(kp_factor, ki_factor, kd_factor); - ESP_LOGD(TAG, " Rule '%s':", name); - ESP_LOGD(TAG, " kp: %.5f, ki: %.5f, kd: %.5f", fac.kp, fac.ki, fac.kd); + ESP_LOGD(TAG, + " Rule '%s':\n" + " kp: %.5f, ki: %.5f, kd: %.5f", + name, fac.kp, fac.ki, fac.kd); } // ================== RelayFunction ================== diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index fd74eabd87..2094c0e942 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -162,14 +162,14 @@ void PIDClimate::start_autotune(std::unique_ptr &&autotune) { float min_value = this->supports_cool_() ? -1.0f : 0.0f; float max_value = this->supports_heat_() ? 1.0f : 0.0f; this->autotuner_->config(min_value, max_value); - this->autotuner_->set_autotuner_id(this->get_object_id()); + this->autotuner_->set_autotuner_id(this->get_name()); ESP_LOGI(TAG, "%s: Autotune has started. This can take a long time depending on the " "responsiveness of your system. Your system " "output will be altered to deliberately oscillate above and below the setpoint multiple times. " "Until your sensor provides a reading, the autotuner may display \'nan\'", - this->get_object_id().c_str()); + this->get_name().c_str()); this->set_interval("autotune-progress", 10000, [this]() { if (this->autotuner_ != nullptr && !this->autotuner_->is_finished()) @@ -178,7 +178,7 @@ void PIDClimate::start_autotune(std::unique_ptr &&autotune) { if (mode != climate::CLIMATE_MODE_HEAT_COOL) { ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!", - this->get_object_id().c_str()); + this->get_name().c_str()); } } diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index eb10d19c91..bb167033d1 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -18,6 +18,8 @@ static const uint16_t PMS_CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automaticall static const uint16_t PMS_CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode static const uint16_t PMS_CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode +void PMSX003Component::setup() {} + void PMSX003Component::dump_config() { ESP_LOGCONFIG(TAG, "PMSX003:"); LOG_SENSOR(" ", "PM1.0STD", this->pm_1_0_std_sensor_); @@ -39,21 +41,36 @@ void PMSX003Component::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + + if (this->update_interval_ <= PMS_STABILISING_MS) { + ESP_LOGCONFIG(TAG, " Mode: active continuous (sensor default)"); + } else { + ESP_LOGCONFIG(TAG, " Mode: passive with sleep/wake cycles"); + } + this->check_uart_settings(9600); } void PMSX003Component::loop() { const uint32_t now = App.get_loop_component_start_time(); + // Initialize sensor mode on first loop + if (this->initialised_ == 0) { + if (this->update_interval_ > PMS_STABILISING_MS) { + // Long update interval: use passive mode with sleep/wake cycles + this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE); + this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); + } else { + // Short/zero update interval: use active continuous mode + this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_ACTIVE); + } + this->initialised_ = 1; + } + // If we update less often than it takes the device to stabilise, spin the fan down // rather than running it constantly. It does take some time to stabilise, so we // need to keep track of what state we're in. if (this->update_interval_ > PMS_STABILISING_MS) { - if (this->initialised_ == 0) { - this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE); - this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); - this->initialised_ = 1; - } switch (this->state_) { case PMSX003_STATE_IDLE: // Power on the sensor now so it'll be ready when we hit the update time diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index ba607b4487..f48121800e 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -31,6 +31,7 @@ enum PMSX003State { class PMSX003Component : public uart::UARTDevice, public Component { public: PMSX003Component() = default; + void setup() override; void dump_config() override; void loop() override; diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index ef4022db4b..8f0c5581d4 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -31,8 +31,10 @@ void PN532::setup() { this->mark_failed(); return; } - ESP_LOGD(TAG, "Found chip PN5%02X", version_data[0]); - ESP_LOGD(TAG, "Firmware ver. %d.%d", version_data[1], version_data[2]); + ESP_LOGD(TAG, + "Found chip PN5%02X\n" + "Firmware ver. %d.%d", + version_data[0], version_data[1], version_data[2]); if (!this->write_command_({ PN532_COMMAND_SAMCONFIGURATION, @@ -195,7 +197,8 @@ void PN532::loop() { trigger->process(tag); if (report) { - ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid(nfcid).c_str()); + char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid_to(uid_buf, nfcid)); if (tag->has_ndef_message()) { const auto &message = tag->get_ndef_message(); const auto &records = message->get_records(); diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index 943f8c5519..28ab22e160 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -77,7 +77,8 @@ bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector & } data.erase(data.begin()); - ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes(data).c_str()); + char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes_to(data_buf, data)); return true; } diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index f823829a6c..0221ba31c5 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -71,7 +71,8 @@ bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes } } - ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str()); + char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(data_buf, data)); return true; } diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index 0871f7acab..118421c47f 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -1,4 +1,5 @@ #include "pn532_spi.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" // Based on: @@ -11,6 +12,9 @@ namespace pn532_spi { static const char *const TAG = "pn532_spi"; +// Maximum bytes to log in verbose hex output +static constexpr size_t PN532_MAX_LOG_BYTES = 64; + void PN532Spi::setup() { this->spi_setup(); @@ -32,7 +36,10 @@ bool PN532Spi::write_data(const std::vector &data) { delay(2); // First byte, communication mode: Write data this->write_byte(0x01); - ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); this->write_array(data.data(), data.size()); this->disable(); @@ -55,7 +62,10 @@ bool PN532Spi::read_data(std::vector &data, uint8_t len) { this->read_array(data.data(), len); this->disable(); data.insert(data.begin(), 0x01); - ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Read data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); return true; } @@ -73,7 +83,10 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { std::vector header(7); this->read_array(header.data(), 7); - ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Header data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), header.data(), header.size())); if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) { // invalid packet @@ -103,7 +116,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { this->read_array(data.data(), len + 1); this->disable(); - ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str()); + ESP_LOGV(TAG, "Response data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); uint8_t checksum = header[5] + header[6]; // TFI + Command response code for (int i = 0; i < len - 1; i++) { diff --git a/esphome/components/pn7150/pn7150.cpp b/esphome/components/pn7150/pn7150.cpp index f827bd151a..e1ba3761d4 100644 --- a/esphome/components/pn7150/pn7150.cpp +++ b/esphome/components/pn7150/pn7150.cpp @@ -203,7 +203,8 @@ uint8_t PN7150::set_test_mode(const TestMode test_mode, const std::vectortag_listeners_) { listener->tag_off(*this->discovered_endpoint_[tag_index].tag); } - ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); + char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; + ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid())); this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); } } @@ -767,26 +777,33 @@ void PN7150::process_message_() { ESP_LOGV(TAG, "Unimplemented NCI Core OID received: 0x%02X", rx.get_oid()); } } else { - ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes(rx.get_message()).c_str()); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes_to(buf, rx.get_message())); } break; - case nfc::NCI_PKT_MT_CTRL_RESPONSE: + case nfc::NCI_PKT_MT_CTRL_RESPONSE: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(), - nfc::format_bytes(rx.get_message()).c_str()); + nfc::format_bytes_to(buf, rx.get_message())); break; + } - case nfc::NCI_PKT_MT_CTRL_COMMAND: - ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes(rx.get_message()).c_str()); + case nfc::NCI_PKT_MT_CTRL_COMMAND: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes_to(buf, rx.get_message())); break; + } case nfc::NCI_PKT_MT_DATA: this->process_data_message_(rx); break; - default: - ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes(rx.get_message()).c_str()); + default: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes_to(buf, rx.get_message())); break; + } } } @@ -847,8 +864,8 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi case EP_WRITE: if (this->next_task_message_to_write_ != nullptr) { - ESP_LOGD(TAG, " Tag writing"); - ESP_LOGD(TAG, " Tag formatting"); + ESP_LOGD(TAG, " Tag writing\n" + " Tag formatting"); if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { ESP_LOGE(TAG, " Tag could not be formatted for writing"); } else { @@ -867,8 +884,9 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi case EP_READ: default: if (!working_endpoint.trig_called) { + char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(), - nfc::format_uid(working_endpoint.tag->get_uid()).c_str()); + nfc::format_uid_to(uid_buf, working_endpoint.tag->get_uid())); if (this->read_endpoint_data_(*working_endpoint.tag) != nfc::STATUS_OK) { ESP_LOGW(TAG, " Unable to read NDEF record(s)"); } else if (working_endpoint.tag->has_ndef_message()) { @@ -959,7 +977,8 @@ void PN7150::process_rf_deactivate_oid_(nfc::NciMessage &rx) { } void PN7150::process_data_message_(nfc::NciMessage &rx) { - ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message())); std::vector ndef_response; this->card_emu_t4t_get_response_(rx.get_message(), ndef_response); @@ -973,7 +992,7 @@ void PN7150::process_data_message_(nfc::NciMessage &rx) { uint8_t(ndef_response_size & 0x00FF)}; tx_msg.insert(tx_msg.end(), ndef_response.begin(), ndef_response.end()); nfc::NciMessage tx(tx_msg); - ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes(tx.get_message()).c_str()); + ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes_to(buf, tx.get_message())); if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending reply for card emulation failed"); } @@ -1026,7 +1045,8 @@ void PN7150::card_emu_t4t_get_response_(std::vector &response, std::vec uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3]; uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; - ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes(ndef_message).c_str()); + char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes_to(ndef_buf, ndef_message)); if (length <= (ndef_msg_size + offset + 2)) { if (offset == 0) { @@ -1065,7 +1085,8 @@ void PN7150::card_emu_t4t_get_response_(std::vector &response, std::vec ndef_msg_written.insert(ndef_msg_written.end(), response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5, response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5 + length); - ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes(ndef_msg_written).c_str()); + char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(ndef_buf, ndef_msg_written)); ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); } } @@ -1074,6 +1095,7 @@ void PN7150::card_emu_t4t_get_response_(std::vector &response, std::vec uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout, const bool expect_notification) { uint8_t retries = NFCC_MAX_COMM_FAILS; + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; while (retries) { // first, send the message we need to send @@ -1081,7 +1103,7 @@ uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint ESP_LOGE(TAG, "Error sending message"); return nfc::STATUS_FAILED; } - ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes(tx.get_message()).c_str()); + ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes_to(buf, tx.get_message())); // next, the NFCC should send back a response if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { ESP_LOGW(TAG, "Error receiving message"); @@ -1093,24 +1115,24 @@ uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint break; } } - ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message())); // validate the response based on the message type that was sent (command vs. data) if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) { // for commands, the GID and OID should match and the status should be OK if ((rx.get_gid() != tx.get_gid()) || (rx.get_oid()) != tx.get_oid()) { - ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes_to(buf, rx.get_message())); return nfc::STATUS_FAILED; } if (!rx.simple_status_response_is(nfc::STATUS_OK)) { - ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes_to(buf, rx.get_message())); } return rx.get_simple_status_response(); } else { // when requesting data from the endpoint, the first response is from the NFCC; we must validate this, first if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.gid_is(nfc::NCI_CORE_GID)) || (!rx.oid_is(nfc::NCI_CORE_CONN_CREDITS_OID)) || (!rx.message_length_is(3))) { - ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes_to(buf, rx.get_message())); return nfc::STATUS_FAILED; } @@ -1120,7 +1142,7 @@ uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint ESP_LOGE(TAG, "Error receiving data from endpoint"); return nfc::STATUS_FAILED; } - ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message())); } return nfc::STATUS_OK; diff --git a/esphome/components/pn7150/pn7150_mifare_classic.cpp b/esphome/components/pn7150/pn7150_mifare_classic.cpp index 0443929f69..dee81b610a 100644 --- a/esphome/components/pn7150/pn7150_mifare_classic.cpp +++ b/esphome/components/pn7150/pn7150_mifare_classic.cpp @@ -70,7 +70,8 @@ uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Timeout reading tag data"); return nfc::STATUS_FAILED; @@ -79,13 +80,13 @@ uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed"); return nfc::STATUS_FAILED; @@ -119,7 +121,7 @@ uint8_t PN7150::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, c if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) || (rx.get_message()[4] != nfc::STATUS_OK)) { ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num); - ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes_to(buf, rx.get_message())); return nfc::STATUS_FAILED; } @@ -238,7 +240,8 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed"); return nfc::STATUS_FAILED; @@ -247,7 +250,7 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write"); return nfc::STATUS_FAILED; @@ -256,7 +259,7 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed"); return nfc::STATUS_FAILED; diff --git a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp index b107f6f79e..ac15475bad 100644 --- a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp +++ b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp @@ -72,7 +72,8 @@ uint8_t PN7150::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_b } } - ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str()); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(buf, data)); return nfc::STATUS_OK; } diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp index a8edfadd8e..1a38dce5fd 100644 --- a/esphome/components/pn7160/pn7160.cpp +++ b/esphome/components/pn7160/pn7160.cpp @@ -215,7 +215,8 @@ uint8_t PN7160::set_test_mode(const TestMode test_mode, const std::vector features(rx.get_message().begin() + 4, rx.get_message().begin() + 8); - ESP_LOGD(TAG, "Hardware version: %u", hw_version); - ESP_LOGD(TAG, "ROM code version: %u", rom_code_version); - ESP_LOGD(TAG, "FLASH major version: %u", flash_major_version); - ESP_LOGD(TAG, "FLASH minor version: %u", flash_minor_version); - ESP_LOGD(TAG, "Features: %s", nfc::format_bytes(features).c_str()); + char feat_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGD(TAG, + "Hardware version: %u\n" + "ROM code version: %u\n" + "FLASH major version: %u\n" + "FLASH minor version: %u\n" + "Features: %s", + hw_version, rom_code_version, flash_major_version, flash_minor_version, + nfc::format_bytes_to(feat_buf, features)); return rx.get_simple_status_response(); } @@ -594,7 +606,8 @@ void PN7160::erase_tag_(const uint8_t tag_index) { for (auto *listener : this->tag_listeners_) { listener->tag_off(*this->discovered_endpoint_[tag_index].tag); } - ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); + char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; + ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid())); this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); } } @@ -791,26 +804,33 @@ void PN7160::process_message_() { ESP_LOGV(TAG, "Unimplemented NCI Core OID received: 0x%02X", rx.get_oid()); } } else { - ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes(rx.get_message()).c_str()); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes_to(buf, rx.get_message())); } break; - case nfc::NCI_PKT_MT_CTRL_RESPONSE: + case nfc::NCI_PKT_MT_CTRL_RESPONSE: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(), - nfc::format_bytes(rx.get_message()).c_str()); + nfc::format_bytes_to(buf, rx.get_message())); break; + } - case nfc::NCI_PKT_MT_CTRL_COMMAND: - ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes(rx.get_message()).c_str()); + case nfc::NCI_PKT_MT_CTRL_COMMAND: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes_to(buf, rx.get_message())); break; + } case nfc::NCI_PKT_MT_DATA: this->process_data_message_(rx); break; - default: - ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes(rx.get_message()).c_str()); + default: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes_to(buf, rx.get_message())); break; + } } } @@ -871,8 +891,8 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi case EP_WRITE: if (this->next_task_message_to_write_ != nullptr) { - ESP_LOGD(TAG, " Tag writing"); - ESP_LOGD(TAG, " Tag formatting"); + ESP_LOGD(TAG, " Tag writing\n" + " Tag formatting"); if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) { ESP_LOGE(TAG, " Tag could not be formatted for writing"); } else { @@ -891,8 +911,9 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi case EP_READ: default: if (!working_endpoint.trig_called) { + char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(), - nfc::format_uid(working_endpoint.tag->get_uid()).c_str()); + nfc::format_uid_to(uid_buf, working_endpoint.tag->get_uid())); if (this->read_endpoint_data_(*working_endpoint.tag) != nfc::STATUS_OK) { ESP_LOGW(TAG, " Unable to read NDEF record(s)"); } else if (working_endpoint.tag->has_ndef_message()) { @@ -983,7 +1004,8 @@ void PN7160::process_rf_deactivate_oid_(nfc::NciMessage &rx) { } void PN7160::process_data_message_(nfc::NciMessage &rx) { - ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message())); std::vector ndef_response; this->card_emu_t4t_get_response_(rx.get_message(), ndef_response); @@ -997,7 +1019,7 @@ void PN7160::process_data_message_(nfc::NciMessage &rx) { uint8_t(ndef_response_size & 0x00FF)}; tx_msg.insert(tx_msg.end(), ndef_response.begin(), ndef_response.end()); nfc::NciMessage tx(tx_msg); - ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes(tx.get_message()).c_str()); + ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes_to(buf, tx.get_message())); if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending reply for card emulation failed"); } @@ -1050,7 +1072,8 @@ void PN7160::card_emu_t4t_get_response_(std::vector &response, std::vec uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3]; uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4]; - ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes(ndef_message).c_str()); + char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes_to(ndef_buf, ndef_message)); if (length <= (ndef_msg_size + offset + 2)) { if (offset == 0) { @@ -1089,7 +1112,8 @@ void PN7160::card_emu_t4t_get_response_(std::vector &response, std::vec ndef_msg_written.insert(ndef_msg_written.end(), response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5, response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5 + length); - ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes(ndef_msg_written).c_str()); + char write_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(write_buf, ndef_msg_written)); ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); } } @@ -1098,6 +1122,7 @@ void PN7160::card_emu_t4t_get_response_(std::vector &response, std::vec uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout, const bool expect_notification) { uint8_t retries = NFCC_MAX_COMM_FAILS; + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; while (retries) { // first, send the message we need to send @@ -1105,7 +1130,7 @@ uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint ESP_LOGE(TAG, "Error sending message"); return nfc::STATUS_FAILED; } - ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes(tx.get_message()).c_str()); + ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes_to(buf, tx.get_message())); // next, the NFCC should send back a response if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { ESP_LOGW(TAG, "Error receiving message"); @@ -1117,24 +1142,24 @@ uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint break; } } - ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message())); // validate the response based on the message type that was sent (command vs. data) if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) { // for commands, the GID and OID should match and the status should be OK if ((rx.get_gid() != tx.get_gid()) || (rx.get_oid()) != tx.get_oid()) { - ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes_to(buf, rx.get_message())); return nfc::STATUS_FAILED; } if (!rx.simple_status_response_is(nfc::STATUS_OK)) { - ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes_to(buf, rx.get_message())); } return rx.get_simple_status_response(); } else { // when requesting data from the endpoint, the first response is from the NFCC; we must validate this, first if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.gid_is(nfc::NCI_CORE_GID)) || (!rx.oid_is(nfc::NCI_CORE_CONN_CREDITS_OID)) || (!rx.message_length_is(3))) { - ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes_to(buf, rx.get_message())); return nfc::STATUS_FAILED; } @@ -1144,7 +1169,7 @@ uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint ESP_LOGE(TAG, "Error receiving data from endpoint"); return nfc::STATUS_FAILED; } - ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message())); } return nfc::STATUS_OK; diff --git a/esphome/components/pn7160/pn7160_mifare_classic.cpp b/esphome/components/pn7160/pn7160_mifare_classic.cpp index fa63cc00d5..57d2042eaa 100644 --- a/esphome/components/pn7160/pn7160_mifare_classic.cpp +++ b/esphome/components/pn7160/pn7160_mifare_classic.cpp @@ -69,8 +69,9 @@ uint8_t PN7160::read_mifare_classic_tag_(nfc::NfcTag &tag) { uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num}); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str()); + ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message())); if (this->transceive_(tx, rx) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Timeout reading tag data"); return nfc::STATUS_FAILED; @@ -79,13 +80,13 @@ uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed"); return nfc::STATUS_FAILED; @@ -119,7 +121,7 @@ uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, c if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) || (rx.get_message()[4] != nfc::STATUS_OK)) { ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num); - ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str()); + ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes_to(buf, rx.get_message())); return nfc::STATUS_FAILED; } @@ -237,8 +239,9 @@ uint8_t PN7160::format_mifare_classic_ndef_() { uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num}); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str()); + ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes_to(buf, tx.get_message())); if (this->transceive_(tx, rx) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed"); return nfc::STATUS_FAILED; @@ -247,7 +250,7 @@ uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write"); return nfc::STATUS_FAILED; @@ -256,7 +259,7 @@ uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed"); return nfc::STATUS_FAILED; diff --git a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp index 65daac494f..584385f113 100644 --- a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp +++ b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp @@ -72,7 +72,8 @@ uint8_t PN7160::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_b } } - ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str()); + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(buf, data)); return nfc::STATUS_OK; } diff --git a/esphome/components/preferences/__init__.py b/esphome/components/preferences/__init__.py index 1da6d02045..c6bede891a 100644 --- a/esphome/components/preferences/__init__.py +++ b/esphome/components/preferences/__init__.py @@ -1,6 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.core import coroutine_with_priority +from esphome.coroutine import CoroPriority CODEOWNERS = ["@esphome/core"] @@ -16,6 +18,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) +@coroutine_with_priority(CoroPriority.PREFERENCES) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_write_interval(config[CONF_FLASH_WRITE_INTERVAL])) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 4b5d834ebf..e2639a2298 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -112,7 +112,11 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { std::string PrometheusHandler::relabel_id_(EntityBase *obj) { auto item = relabel_map_id_.find(obj); - return item == relabel_map_id_.end() ? obj->get_object_id() : item->second; + if (item != relabel_map_id_.end()) { + return item->second; + } + char object_id_buf[OBJECT_ID_MAX_LEN]; + return obj->get_object_id_to(object_id_buf).str(); } std::string PrometheusHandler::relabel_name_(EntityBase *obj) { @@ -190,7 +194,9 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor stream->print(ESPHOME_F("\",unit=\"")); stream->print(obj->get_unit_of_measurement_ref().c_str()); stream->print(ESPHOME_F("\"} ")); - stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str()); + char value_buf[VALUE_ACCURACY_MAX_LEN]; + value_accuracy_to_buf(value_buf, obj->state, obj->get_accuracy_decimals()); + stream->print(value_buf); stream->print(ESPHOME_F("\n")); } else { // Invalid state @@ -359,13 +365,14 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat // Skip effect metrics if light has no effects if (!obj->get_effects().empty()) { // Effect - std::string effect = obj->get_effect_name(); + StringRef effect = obj->get_effect_name(); print_metric_labels_(stream, ESPHOME_F("esphome_light_effect_active"), obj, area, node, friendly_name); stream->print(ESPHOME_F("\",effect=\"")); // Only vary based on effect if (effect == "None") { stream->print(ESPHOME_F("None\"} 0\n")); } else { + // c_str() is safe as effect names are null-terminated strings from codegen stream->print(effect.c_str()); stream->print(ESPHOME_F("\"} 1\n")); } @@ -595,7 +602,7 @@ void PrometheusHandler::event_row_(AsyncResponseStream *stream, event::Event *ob std::string &friendly_name) { if (obj->is_internal() && !this->include_internal_) return; - if (obj->get_last_event_type() != nullptr) { + if (obj->has_event()) { // We have a valid event type, output this value stream->print(ESPHOME_F("esphome_event_failed{id=\"")); stream->print(relabel_id_(obj).c_str()); @@ -614,7 +621,8 @@ void PrometheusHandler::event_row_(AsyncResponseStream *stream, event::Event *ob stream->print(ESPHOME_F("\",name=\"")); stream->print(relabel_name_(obj).c_str()); stream->print(ESPHOME_F("\",last_event_type=\"")); - stream->print(obj->get_last_event_type()); + // get_last_event_type() returns StringRef (null-terminated) + stream->print(obj->get_last_event_type().c_str()); stream->print(ESPHOME_F("\"} ")); stream->print(ESPHOME_F("1.0")); stream->print(ESPHOME_F("\n")); @@ -705,7 +713,8 @@ void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select stream->print(ESPHOME_F("\",name=\"")); stream->print(relabel_name_(obj).c_str()); stream->print(ESPHOME_F("\",value=\"")); - stream->print(obj->current_option()); + // c_str() is safe as option values are null-terminated strings from codegen + stream->print(obj->current_option().c_str()); stream->print(ESPHOME_F("\"} ")); stream->print(ESPHOME_F("1.0")); stream->print(ESPHOME_F("\n")); @@ -947,7 +956,7 @@ void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climat void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node, std::string &friendly_name, std::string &category, - std::string &climate_value) { + const char *climate_value) { stream->print(ESPHOME_F("esphome_climate_value{id=\"")); stream->print(relabel_id_(obj).c_str()); add_area_label_(stream, area); @@ -958,7 +967,7 @@ void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate: stream->print(ESPHOME_F("\",category=\"")); stream->print(category.c_str()); stream->print(ESPHOME_F("\"} ")); - stream->print(climate_value.c_str()); + stream->print(climate_value); stream->print(ESPHOME_F("\n")); } @@ -996,14 +1005,15 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima // Now see if traits is supported int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); + char value_buf[VALUE_ACCURACY_MAX_LEN]; // max temp std::string max_temp = "maximum_temperature"; - auto max_temp_value = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy); - climate_value_row_(stream, obj, area, node, friendly_name, max_temp, max_temp_value); - // max temp - std::string min_temp = "mininum_temperature"; - auto min_temp_value = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy); - climate_value_row_(stream, obj, area, node, friendly_name, min_temp, min_temp_value); + value_accuracy_to_buf(value_buf, traits.get_visual_max_temperature(), target_accuracy); + climate_value_row_(stream, obj, area, node, friendly_name, max_temp, value_buf); + // min temp + std::string min_temp = "minimum_temperature"; + value_accuracy_to_buf(value_buf, traits.get_visual_min_temperature(), target_accuracy); + climate_value_row_(stream, obj, area, node, friendly_name, min_temp, value_buf); // now check optional traits if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { std::string current_temp = "current_temperature"; @@ -1011,8 +1021,8 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima climate_failed_row_(stream, obj, area, node, friendly_name, current_temp, true); any_failures = true; } else { - auto current_temp_value = value_accuracy_to_string(obj->current_temperature, current_accuracy); - climate_value_row_(stream, obj, area, node, friendly_name, current_temp, current_temp_value); + value_accuracy_to_buf(value_buf, obj->current_temperature, current_accuracy); + climate_value_row_(stream, obj, area, node, friendly_name, current_temp, value_buf); climate_failed_row_(stream, obj, area, node, friendly_name, current_temp, false); } } @@ -1022,8 +1032,8 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima climate_failed_row_(stream, obj, area, node, friendly_name, current_humidity, true); any_failures = true; } else { - auto current_humidity_value = value_accuracy_to_string(obj->current_humidity, 0); - climate_value_row_(stream, obj, area, node, friendly_name, current_humidity, current_humidity_value); + value_accuracy_to_buf(value_buf, obj->current_humidity, 0); + climate_value_row_(stream, obj, area, node, friendly_name, current_humidity, value_buf); climate_failed_row_(stream, obj, area, node, friendly_name, current_humidity, false); } } @@ -1033,23 +1043,23 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima climate_failed_row_(stream, obj, area, node, friendly_name, target_humidity, true); any_failures = true; } else { - auto target_humidity_value = value_accuracy_to_string(obj->target_humidity, 0); - climate_value_row_(stream, obj, area, node, friendly_name, target_humidity, target_humidity_value); + value_accuracy_to_buf(value_buf, obj->target_humidity, 0); + climate_value_row_(stream, obj, area, node, friendly_name, target_humidity, value_buf); climate_failed_row_(stream, obj, area, node, friendly_name, target_humidity, false); } } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { std::string target_temp_low = "target_temperature_low"; - auto target_temp_low_value = value_accuracy_to_string(obj->target_temperature_low, target_accuracy); - climate_value_row_(stream, obj, area, node, friendly_name, target_temp_low, target_temp_low_value); + value_accuracy_to_buf(value_buf, obj->target_temperature_low, target_accuracy); + climate_value_row_(stream, obj, area, node, friendly_name, target_temp_low, value_buf); std::string target_temp_high = "target_temperature_high"; - auto target_temp_high_value = value_accuracy_to_string(obj->target_temperature_high, target_accuracy); - climate_value_row_(stream, obj, area, node, friendly_name, target_temp_high, target_temp_high_value); + value_accuracy_to_buf(value_buf, obj->target_temperature_high, target_accuracy); + climate_value_row_(stream, obj, area, node, friendly_name, target_temp_high, value_buf); } else { std::string target_temp = "target_temperature"; - auto target_temp_value = value_accuracy_to_string(obj->target_temperature, target_accuracy); - climate_value_row_(stream, obj, area, node, friendly_name, target_temp, target_temp_value); + value_accuracy_to_buf(value_buf, obj->target_temperature, target_accuracy); + climate_value_row_(stream, obj, area, node, friendly_name, target_temp, value_buf); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { std::string climate_trait_category = "action"; diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 24243c8c98..fc48ad67e3 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -207,7 +207,7 @@ class PrometheusHandler : public AsyncWebHandler, public Component { void climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node, std::string &friendly_name, std::string &setting, const LogString *setting_value); void climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node, - std::string &friendly_name, std::string &category, std::string &climate_value); + std::string &friendly_name, std::string &category, const char *climate_value); #endif web_server_base::WebServerBase *base_; diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 6300d6fe96..c0d74cef4a 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -68,8 +68,10 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { next_pcnt_channel = pcnt_channel_t(int(next_pcnt_channel) + 1); } - ESP_LOGCONFIG(TAG, " PCNT Unit Number: %u", this->pcnt_unit); - ESP_LOGCONFIG(TAG, " PCNT Channel Number: %u", this->pcnt_channel); + ESP_LOGCONFIG(TAG, + " PCNT Unit Number: %u\n" + " PCNT Channel Number: %u", + this->pcnt_unit, this->pcnt_channel); pcnt_count_mode_t rising = PCNT_COUNT_DIS, falling = PCNT_COUNT_DIS; switch (this->rising_edge_mode) { diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 8436633619..4d4a5466bb 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -1,4 +1,5 @@ #include "pvvx_display.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,14 +9,16 @@ namespace pvvx_mithermometer { static const char *const TAG = "display.pvvx_mithermometer"; void PVVXDisplay::dump_config() { + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGCONFIG(TAG, "PVVX MiThermometer display:\n" " MAC address : %s\n" " Service UUID : %s\n" " Characteristic UUID : %s\n" " Auto clear : %s", - this->parent_->address_str(), this->service_uuid_.to_string().c_str(), - this->char_uuid_.to_string().c_str(), YESNO(this->auto_clear_enabled_)); + this->parent_->address_str(), this->service_uuid_.to_str(service_buf), + this->char_uuid_.to_str(char_buf), YESNO(this->auto_clear_enabled_)); #ifdef USE_TIME ESP_LOGCONFIG(TAG, " Set time on connection: %s", YESNO(this->time_ != nullptr)); #endif diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.h b/esphome/components/pvvx_mithermometer/display/pvvx_display.h index 8637506bae..06837b94ab 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.h +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.h @@ -60,13 +60,13 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { * Valid values are from -99.5 to 1999.5. Smaller values are displayed as Lo, higher as Hi. * It will printed as it fits in the screen. */ - void print_bignum(float bignum) { this->bignum_ = bignum * 10; } + void print_bignum(float bignum) { this->bignum_ = static_cast(bignum * 10); } /** * Print the small number * * Valid values are from -9 to 99. Smaller values are displayed as Lo, higher as Hi. */ - void print_smallnum(float smallnum) { this->smallnum_ = smallnum; } + void print_smallnum(float smallnum) { this->smallnum_ = static_cast(smallnum); } /** * Print a happy face * @@ -107,8 +107,8 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent { bool auto_clear_enabled_{true}; uint32_t disconnect_delay_ms_ = 5000; uint16_t validity_period_ = 300; - uint16_t bignum_ = 0; - uint16_t smallnum_ = 0; + int16_t bignum_ = 0; + int16_t smallnum_ = 0; uint8_t cfg_ = 0; void setcfgbit_(uint8_t bit, bool value); diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 6975109952..5712447909 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -21,7 +21,9 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -32,7 +34,7 @@ bool PVVXMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &devic if (!(parse_message_(service_data.data, *res))) { continue; } - if (!(report_results_(res, device.address_str()))) { + if (!(report_results_(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -111,13 +113,13 @@ bool PVVXMiThermometer::parse_message_(const std::vector &message, Pars return true; } -bool PVVXMiThermometer::report_results_(const optional &result, const std::string &address) { +bool PVVXMiThermometer::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got PVVX MiThermometer (%s):", address.c_str()); + ESP_LOGD(TAG, "Got PVVX MiThermometer (%s):", address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.2f °C", *result->temperature); diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h index 9614a3c586..c15e1e7e22 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.h @@ -41,7 +41,7 @@ class PVVXMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevic optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace pvvx_mithermometer diff --git a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp index 55e02f3e33..8175477cb2 100644 --- a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp +++ b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp @@ -25,16 +25,16 @@ void PylontechTextSensor::on_line_read(PylontechListener::LineContents *line) { return; } if (this->base_state_text_sensor_ != nullptr) { - this->base_state_text_sensor_->publish_state(std::string(line->base_st)); + this->base_state_text_sensor_->publish_state(line->base_st); } if (this->voltage_state_text_sensor_ != nullptr) { - this->voltage_state_text_sensor_->publish_state(std::string(line->volt_st)); + this->voltage_state_text_sensor_->publish_state(line->volt_st); } if (this->current_state_text_sensor_ != nullptr) { - this->current_state_text_sensor_->publish_state(std::string(line->curr_st)); + this->current_state_text_sensor_->publish_state(line->curr_st); } if (this->temperature_state_text_sensor_ != nullptr) { - this->temperature_state_text_sensor_->publish_state(std::string(line->temp_st)); + this->temperature_state_text_sensor_->publish_state(line->temp_st); } } diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index d2041a2d52..693614581c 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -105,7 +105,9 @@ void QMC5883LComponent::update() { if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) { err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1); if (err != i2c::ERROR_OK) { - this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str()); + char buf[32]; + snprintf(buf, sizeof(buf), "status read failed (%d)", err); + this->status_set_warning(buf); return; } } @@ -127,7 +129,9 @@ void QMC5883LComponent::update() { } err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest); if (err != i2c::ERROR_OK) { - this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str()); + char buf[32]; + snprintf(buf, sizeof(buf), "mag read failed (%d)", err); + this->status_set_warning(buf); return; } @@ -155,7 +159,9 @@ void QMC5883LComponent::update() { uint16_t raw_temp; err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp); if (err != i2c::ERROR_OK) { - this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str()); + char buf[32]; + snprintf(buf, sizeof(buf), "temp read failed (%d)", err); + this->status_set_warning(buf); return; } temp = int16_t(raw_temp) * 0.01f; diff --git a/esphome/components/qmp6988/qmp6988.cpp b/esphome/components/qmp6988/qmp6988.cpp index 57f54b6432..4e1ef27d5e 100644 --- a/esphome/components/qmp6988/qmp6988.cpp +++ b/esphome/components/qmp6988/qmp6988.cpp @@ -127,9 +127,11 @@ bool QMP6988Component::get_calibration_data_() { qmp6988_data_.qmp6988_cali.COE_b21 = (int16_t) encode_uint16(a_data_uint8_tr[14], a_data_uint8_tr[15]); qmp6988_data_.qmp6988_cali.COE_bp3 = (int16_t) encode_uint16(a_data_uint8_tr[16], a_data_uint8_tr[17]); - ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n"); - ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0, - qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, qmp6988_data_.qmp6988_cali.COE_b00); + ESP_LOGV(TAG, + "<-----------calibration data-------------->\n" + "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]", + qmp6988_data_.qmp6988_cali.COE_a0, qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, + qmp6988_data_.qmp6988_cali.COE_b00); ESP_LOGV(TAG, "COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bt1, qmp6988_data_.qmp6988_cali.COE_bt2, qmp6988_data_.qmp6988_cali.COE_bp1, qmp6988_data_.qmp6988_cali.COE_b11); ESP_LOGV(TAG, "COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bp2, @@ -150,9 +152,10 @@ bool QMP6988Component::get_calibration_data_() { qmp6988_data_.ik.b12 = 6846L * (int64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L; // 29Q53 qmp6988_data_.ik.b21 = 13836L * (int64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L; // 29Q60 qmp6988_data_.ik.bp3 = 2915L * (int64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L; // 28Q65 - ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n"); - ESP_LOGV(TAG, "a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2, - qmp6988_data_.ik.b00); + ESP_LOGV(TAG, + "<----------- int calibration data -------------->\n" + "a0[%d] a1[%d] a2[%d] b00[%d]", + qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2, qmp6988_data_.ik.b00); ESP_LOGV(TAG, "bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n", qmp6988_data_.ik.bt1, qmp6988_data_.ik.bt2, qmp6988_data_.ik.bp1, qmp6988_data_.ik.b11); ESP_LOGV(TAG, "bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n", qmp6988_data_.ik.bp2, qmp6988_data_.ik.b12, @@ -330,8 +333,10 @@ void QMP6988Component::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); ESP_LOGCONFIG(TAG, " Temperature Oversampling: %s", oversampling_to_str(this->temperature_oversampling_)); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); - ESP_LOGCONFIG(TAG, " Pressure Oversampling: %s", oversampling_to_str(this->pressure_oversampling_)); - ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_)); + ESP_LOGCONFIG(TAG, + " Pressure Oversampling: %s\n" + " IIR Filter: %s", + oversampling_to_str(this->pressure_oversampling_), iir_filter_to_str(this->iir_filter_)); } void QMP6988Component::update() { diff --git a/esphome/components/qspi_dbi/display.py b/esphome/components/qspi_dbi/display.py index 74d837a794..e4440c9b81 100644 --- a/esphome/components/qspi_dbi/display.py +++ b/esphome/components/qspi_dbi/display.py @@ -154,7 +154,7 @@ CONFIG_SCHEMA = cv.All( upper=True, key=CONF_MODEL, ), - cv.only_with_esp_idf, + cv.only_on_esp32, ) diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index 6c95bb7cf2..d42f95dca3 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -1,10 +1,14 @@ -#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3) #include "qspi_dbi.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { namespace qspi_dbi { +// Maximum bytes to log in verbose hex output +static constexpr size_t QSPI_DBI_MAX_LOG_BYTES = 64; + void QspiDbi::setup() { this->spi_setup(); if (this->enable_pin_ != nullptr) { @@ -174,7 +178,11 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui this->disable(); } void QspiDbi::write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { - ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty(bytes, len).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(QSPI_DBI_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len, + format_hex_pretty_to(hex_buf, sizeof(hex_buf), bytes, len)); this->enable(); this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len); this->disable(); @@ -208,12 +216,14 @@ void QspiDbi::write_sequence_(const std::vector &vec) { void QspiDbi::dump_config() { ESP_LOGCONFIG("", "QSPI_DBI Display"); ESP_LOGCONFIG("", "Model: %s", this->model_); - ESP_LOGCONFIG(TAG, " Height: %u", this->height_); - ESP_LOGCONFIG(TAG, " Width: %u", this->width_); - ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_); + ESP_LOGCONFIG(TAG, + " Height: %u\n" + " Width: %u\n" + " Draw rounding: %u\n" + " SPI Data rate: %uMHz", + this->height_, this->width_, this->draw_rounding_, (unsigned) (this->data_rate_ / 1000000)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); } } // namespace qspi_dbi diff --git a/esphome/components/qspi_dbi/qspi_dbi.h b/esphome/components/qspi_dbi/qspi_dbi.h index f35f0e519c..3eee9bec47 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.h +++ b/esphome/components/qspi_dbi/qspi_dbi.h @@ -3,7 +3,7 @@ // #pragma once -#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" #include "esphome/components/display/display_buffer.h" diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp index 0c6165c691..2c3ef77add 100644 --- a/esphome/components/radon_eye_ble/radon_eye_listener.cpp +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -1,7 +1,6 @@ #include "radon_eye_listener.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include -#include #ifdef USE_ESP32 @@ -11,17 +10,11 @@ namespace radon_eye_ble { static const char *const TAG = "radon_eye_ble"; bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { - if (not device.get_name().empty()) { - // Vector containing the prefixes to search for - std::vector prefixes = {"FR:R", "FR:I", "FR:H"}; - - // Check if the device name starts with any of the prefixes - if (std::any_of(prefixes.begin(), prefixes.end(), - [&](const std::string &prefix) { return device.get_name().starts_with(prefix); })) { - // Device found - ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(), - device.address_str().c_str()); - } + // Radon Eye devices have names starting with "FR:" + if (device.get_name().starts_with("FR:")) { + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(), + device.address_str_to(addr_buf)); } return false; } diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index 3959178b94..f2d32d51de 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -1,4 +1,7 @@ #include "radon_eye_rd200.h" +#include "esphome/components/esp32_ble/ble_uuid.h" + +#include #ifdef USE_ESP32 @@ -7,6 +10,22 @@ namespace radon_eye_rd200 { static const char *const TAG = "radon_eye_rd200"; +static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001523-1212-efde-1523-785feabcd123"); +static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001524-1212-efde-1523-785feabcd123"); +static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V1 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001525-1212-efde-1523-785feabcd123"); +static const uint8_t WRITE_COMMAND_V1 = 0x50; + +static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001523-0000-1000-8000-00805f9b34fb"); +static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001524-0000-1000-8000-00805f9b34fb"); +static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V2 = + esp32_ble_tracker::ESPBTUUID::from_raw("00001525-0000-1000-8000-00805f9b34fb"); +static const uint8_t WRITE_COMMAND_V2 = 0x40; + void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { @@ -23,105 +42,150 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_SEARCH_CMPL_EVT: { + if (this->parent()->get_service(SERVICE_UUID_V1) != nullptr) { + service_uuid_ = SERVICE_UUID_V1; + sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V1; + sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V1; + write_command_ = WRITE_COMMAND_V1; + } else if (this->parent()->get_service(SERVICE_UUID_V2) != nullptr) { + service_uuid_ = SERVICE_UUID_V2; + sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V2; + sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V2; + write_command_ = WRITE_COMMAND_V2; + } else { + ESP_LOGW(TAG, "No supported device has been found, disconnecting"); + parent()->set_enabled(false); + break; + } + this->read_handle_ = 0; auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_); if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_read_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_str(service_buf), + sensors_read_characteristic_uuid_.to_str(char_buf)); break; } this->read_handle_ = chr->handle; - // Write a 0x50 to the write characteristic. auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_); if (write_chr == nullptr) { - ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_read_characteristic_uuid_.to_string().c_str()); + char service_buf[esp32_ble::UUID_STR_LEN]; + char char_buf[esp32_ble::UUID_STR_LEN]; + ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_str(service_buf), + sensors_write_characteristic_uuid_.to_str(char_buf)); break; } this->write_handle_ = write_chr->handle; - this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; - - write_query_message_(); - - request_read_values_(); + esp_err_t status = + esp_ble_gattc_register_for_notify(gattc_if, this->parent()->get_remote_bda(), this->read_handle_); + if (status) { + ESP_LOGW(TAG, "Error registering for sensor notify, status=%d", status); + } break; } - case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->get_conn_id()) - break; - if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + case ESP_GATTC_WRITE_DESCR_EVT: { + if (param->write.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "write descr failed, error status = %x", param->write.status); break; } - if (param->read.handle == this->read_handle_) { - read_sensors_(param->read.value, param->read.value_len); + ESP_LOGV(TAG, "Write descr success, writing 0x%02X at write_handle=%d", this->write_command_, + this->write_handle_); + esp_err_t status = + esp_ble_gattc_write_char(gattc_if, this->parent()->get_conn_id(), this->write_handle_, sizeof(write_command_), + (uint8_t *) &write_command_, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error writing 0x%02x command, status=%d", write_command_, status); } break; } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.is_notify) { + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value, %d bytes", param->notify.value_len); + } else { + ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value, %d bytes", param->notify.value_len); + } + read_sensors_(param->notify.value, param->notify.value_len); + break; + } + default: break; } } void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { - if (value_len < 20) { - ESP_LOGD(TAG, "Invalid read"); + if (value_len < 1) { + ESP_LOGW(TAG, "Unexpected empty message"); return; } - // Example data - // [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000 - ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X", - value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], - value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], - value[19]); + uint8_t command = value[0]; - if (value[0] != 0x50) { - // This isn't a sensor reading. + if ((command == WRITE_COMMAND_V1 && value_len < 20) || (command == WRITE_COMMAND_V2 && value_len < 68)) { + ESP_LOGW(TAG, "Unexpected command 0x%02X message length %d", command, value_len); return; } + // Example data V1: + // 501085EBB9400000000000000000220025000000 + // Example data V2: + // 4042323230313033525532303338330652443230304e56322e302e3200014a00060a00080000000300010079300000e01108001c00020000003822005c8f423fa4709d3f + ESP_LOGV(TAG, "radon sensors raw bytes"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, value, value_len, ESP_LOG_VERBOSE); + // Convert from pCi/L to Bq/m³ constexpr float convert_to_bwpm3 = 37.0; - RadonValue radon_value; - radon_value.chars[0] = value[2]; - radon_value.chars[1] = value[3]; - radon_value.chars[2] = value[4]; - radon_value.chars[3] = value[5]; - float radon_now = radon_value.number * convert_to_bwpm3; - if (is_valid_radon_value_(radon_now)) { - radon_sensor_->publish_state(radon_now); + float radon_now; // in Bq/m³ + float radon_day; // in Bq/m³ + float radon_month; // in Bq/m³ + if (command == WRITE_COMMAND_V1) { + // Use memcpy to avoid unaligned memory access + float temp; + memcpy(&temp, value + 2, sizeof(float)); + radon_now = temp * convert_to_bwpm3; + memcpy(&temp, value + 6, sizeof(float)); + radon_day = temp * convert_to_bwpm3; + memcpy(&temp, value + 10, sizeof(float)); + radon_month = temp * convert_to_bwpm3; + } else if (command == WRITE_COMMAND_V2) { + // Use memcpy to avoid unaligned memory access + uint16_t temp; + memcpy(&temp, value + 33, sizeof(uint16_t)); + radon_now = temp; + memcpy(&temp, value + 35, sizeof(uint16_t)); + radon_day = temp; + memcpy(&temp, value + 37, sizeof(uint16_t)); + radon_month = temp; + } else { + ESP_LOGW(TAG, "Unexpected command value: 0x%02X", command); + return; } - radon_value.chars[0] = value[6]; - radon_value.chars[1] = value[7]; - radon_value.chars[2] = value[8]; - radon_value.chars[3] = value[9]; - float radon_day = radon_value.number * convert_to_bwpm3; - - radon_value.chars[0] = value[10]; - radon_value.chars[1] = value[11]; - radon_value.chars[2] = value[12]; - radon_value.chars[3] = value[13]; - float radon_month = radon_value.number * convert_to_bwpm3; - - if (is_valid_radon_value_(radon_month)) { - ESP_LOGV(TAG, "Radon Long Term based on month"); - radon_long_term_sensor_->publish_state(radon_month); - } else if (is_valid_radon_value_(radon_day)) { - ESP_LOGV(TAG, "Radon Long Term based on day"); - radon_long_term_sensor_->publish_state(radon_day); + if (this->radon_sensor_ != nullptr) { + this->radon_sensor_->publish_state(radon_now); } - ESP_LOGV(TAG, " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f", radon_now, radon_day, radon_month); + if (this->radon_long_term_sensor_ != nullptr) { + if (radon_month > 0) { + ESP_LOGV(TAG, "Radon Long Term based on month"); + this->radon_long_term_sensor_->publish_state(radon_month); + } else { + ESP_LOGV(TAG, "Radon Long Term based on day"); + this->radon_long_term_sensor_->publish_state(radon_day); + } + } - ESP_LOGV(TAG, " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", radon_now / convert_to_bwpm3, - radon_day / convert_to_bwpm3, radon_month / convert_to_bwpm3); + ESP_LOGV(TAG, + " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f\n" + " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", + radon_now, radon_day, radon_month, radon_now / convert_to_bwpm3, radon_day / convert_to_bwpm3, + radon_month / convert_to_bwpm3); // This instance must not stay connected // so other clients can connect to it (e.g. the @@ -129,49 +193,23 @@ void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { parent()->set_enabled(false); } -bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; } - void RadonEyeRD200::update() { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (!parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); parent()->set_enabled(true); - parent()->connect(); } else { ESP_LOGW(TAG, "Connection in progress"); } } } -void RadonEyeRD200::write_query_message_() { - ESP_LOGV(TAG, "writing 0x50 to write service"); - int request = 0x50; - auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), - this->write_handle_, sizeof(request), (uint8_t *) &request, - ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); - } -} - -void RadonEyeRD200::request_read_values_() { - auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), - this->read_handle_, ESP_GATT_AUTH_REQ_NONE); - if (status) { - ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); - } -} - void RadonEyeRD200::dump_config() { LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); } -RadonEyeRD200::RadonEyeRD200() - : PollingComponent(10000), - service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), - sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)), - sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {} +RadonEyeRD200::RadonEyeRD200() : PollingComponent(10000) {} } // namespace radon_eye_rd200 } // namespace esphome diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.h b/esphome/components/radon_eye_rd200/radon_eye_rd200.h index 7b29be7bd8..f874c815f8 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.h +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.h @@ -14,10 +14,6 @@ namespace esphome { namespace radon_eye_rd200 { -static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123"; -static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123"; -static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123"; - class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode { public: RadonEyeRD200(); @@ -32,25 +28,17 @@ class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } protected: - bool is_valid_radon_value_(float radon); - void read_sensors_(uint8_t *value, uint16_t value_len); - void write_query_message_(); - void request_read_values_(); sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; + uint8_t write_command_; uint16_t read_handle_; uint16_t write_handle_; esp32_ble_tracker::ESPBTUUID service_uuid_; esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_; esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_; - - union RadonValue { - char chars[4]; - float number; - }; }; } // namespace radon_eye_rd200 diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index fa8564f614..91fae7fa34 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -12,6 +12,9 @@ static const uint8_t WAIT_I_RQ = 0x30; // RxIRq and IdleIRq static const char *const TAG = "rc522"; +// Max UID size for RFID tags (4, 7, or 10 bytes) +static constexpr size_t RC522_MAX_UID_SIZE = 10; + static const uint8_t RESET_COUNT = 5; void RC522::setup() { @@ -191,8 +194,9 @@ void RC522::loop() { if (status == STATUS_TIMEOUT) { ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status); } else { + char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_, - format_hex_pretty(buffer_, back_length_, '-', false).c_str()); + format_hex_pretty_to(hex_buf, buffer_, back_length_, '-')); } state_ = STATE_DONE; @@ -216,7 +220,7 @@ void RC522::loop() { std::vector rfid_uid(std::begin(uid_buffer_), std::begin(uid_buffer_) + uid_idx_); uid_idx_ = 0; - // ESP_LOGD(TAG, "Processing '%s'", format_hex_pretty(rfid_uid, '-', false).c_str()); + // ESP_LOGD(TAG, "Processing '%s'", format_hex_pretty(rfid_uid, '-', false).c_str()); // NOLINT pcd_antenna_off_(); state_ = STATE_INIT; // scan again on next update bool report = true; @@ -237,13 +241,18 @@ void RC522::loop() { trigger->process(rfid_uid); if (report) { - ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty(rfid_uid, '-', false).c_str()); + char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; + ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty_to(uid_buf, rfid_uid.data(), rfid_uid.size(), '-')); } break; } case STATE_DONE: { if (!this->current_uid_.empty()) { - ESP_LOGV(TAG, "Tag '%s' removed", format_hex_pretty(this->current_uid_, '-', false).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; + ESP_LOGV(TAG, "Tag '%s' removed", + format_hex_pretty_to(uid_buf, this->current_uid_.data(), this->current_uid_.size(), '-')); +#endif for (auto *trigger : this->triggers_ontagremoved_) trigger->process(this->current_uid_); } @@ -338,7 +347,10 @@ void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to * @return STATUS_OK on success, STATUS_??? otherwise. */ void RC522::pcd_transceive_data_(uint8_t send_len) { - ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty(buffer_, send_len, '-', false).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; + ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty_to(hex_buf, buffer_, send_len, '-')); +#endif delayMicroseconds(1000); // we need 1 ms delay between antenna on and those communication commands send_len_ = send_len; // Prepare values for BitFramingReg @@ -412,8 +424,11 @@ RC522::StatusCode RC522::await_transceive_() { error_reg_value); // TODO: is this always due to collissions? return STATUS_ERROR; } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; ESP_LOGV(TAG, "received %d bytes: %s", back_length_, - format_hex_pretty(buffer_ + send_len_, back_length_, '-', false).c_str()); + format_hex_pretty_to(hex_buf, buffer_ + send_len_, back_length_, '-')); +#endif return STATUS_OK; } @@ -477,7 +492,10 @@ bool RC522BinarySensor::process(std::vector &data) { this->found_ = result; return result; } -void RC522Trigger::process(std::vector &data) { this->trigger(format_hex_pretty(data, '-', false)); } +void RC522Trigger::process(std::vector &data) { + char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)]; + this->trigger(format_hex_pretty_to(uid_buf, data.data(), data.size(), '-')); +} } // namespace rc522 } // namespace esphome diff --git a/esphome/components/rd03d/__init__.py b/esphome/components/rd03d/__init__.py new file mode 100644 index 0000000000..52e9a2c09a --- /dev/null +++ b/esphome/components/rd03d/__init__.py @@ -0,0 +1,50 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_THROTTLE + +CODEOWNERS = ["@jasstrong"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +CONF_RD03D_ID = "rd03d_id" +CONF_TRACKING_MODE = "tracking_mode" + +rd03d_ns = cg.esphome_ns.namespace("rd03d") +RD03DComponent = rd03d_ns.class_("RD03DComponent", cg.Component, uart.UARTDevice) +TrackingMode = rd03d_ns.enum("TrackingMode", is_class=True) + +TRACKING_MODES = { + "single": TrackingMode.SINGLE_TARGET, + "multi": TrackingMode.MULTI_TARGET, +} + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RD03DComponent), + cv.Optional(CONF_TRACKING_MODE): cv.enum(TRACKING_MODES, lower=True), + cv.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "rd03d", + require_tx=False, + require_rx=True, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + if CONF_TRACKING_MODE in config: + cg.add(var.set_tracking_mode(config[CONF_TRACKING_MODE])) + + if CONF_THROTTLE in config: + cg.add(var.set_throttle(config[CONF_THROTTLE])) diff --git a/esphome/components/rd03d/binary_sensor.py b/esphome/components/rd03d/binary_sensor.py new file mode 100644 index 0000000000..afb7527aa1 --- /dev/null +++ b/esphome/components/rd03d/binary_sensor.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_TARGET, DEVICE_CLASS_OCCUPANCY + +from . import CONF_RD03D_ID, RD03DComponent + +DEPENDENCIES = ["rd03d"] + +MAX_TARGETS = 3 + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent), + cv.Optional(CONF_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, + ), + } +).extend( + { + cv.Optional(f"target_{i + 1}"): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, + ) + for i in range(MAX_TARGETS) + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_RD03D_ID]) + + if target_config := config.get(CONF_TARGET): + sens = await binary_sensor.new_binary_sensor(target_config) + cg.add(hub.set_target_binary_sensor(sens)) + + for i in range(MAX_TARGETS): + if target_config := config.get(f"target_{i + 1}"): + sens = await binary_sensor.new_binary_sensor(target_config) + cg.add(hub.set_target_binary_sensor(i, sens)) diff --git a/esphome/components/rd03d/rd03d.cpp b/esphome/components/rd03d/rd03d.cpp new file mode 100644 index 0000000000..d9b0b59fe9 --- /dev/null +++ b/esphome/components/rd03d/rd03d.cpp @@ -0,0 +1,263 @@ +#include "rd03d.h" +#include "esphome/core/log.h" +#include + +namespace esphome::rd03d { + +static const char *const TAG = "rd03d"; + +// Delay before sending configuration commands to allow radar to initialize +static constexpr uint32_t SETUP_TIMEOUT_MS = 100; + +// Data frame format (radar -> host) +static constexpr uint8_t FRAME_HEADER[] = {0xAA, 0xFF, 0x03, 0x00}; +static constexpr uint8_t FRAME_FOOTER[] = {0x55, 0xCC}; + +// Command frame format (host -> radar) +static constexpr uint8_t CMD_FRAME_HEADER[] = {0xFD, 0xFC, 0xFB, 0xFA}; +static constexpr uint8_t CMD_FRAME_FOOTER[] = {0x04, 0x03, 0x02, 0x01}; + +// RD-03D tracking mode commands +static constexpr uint16_t CMD_SINGLE_TARGET = 0x0080; +static constexpr uint16_t CMD_MULTI_TARGET = 0x0090; + +// Speed sentinel values (cm/s) - radar outputs these when no valid Doppler measurement +// FMCW radars detect motion via Doppler shift; targets with these speeds are likely noise +static constexpr int16_t SPEED_SENTINEL_248 = 248; +static constexpr int16_t SPEED_SENTINEL_256 = 256; + +// Decode coordinate/speed value from RD-03D format +// Per datasheet: MSB=1 means positive, MSB=0 means negative +static constexpr int16_t decode_value(uint8_t low_byte, uint8_t high_byte) { + int16_t value = ((high_byte & 0x7F) << 8) | low_byte; + if ((high_byte & 0x80) == 0) { + value = -value; + } + return value; +} + +// Check if speed value indicates a valid Doppler measurement +// Zero, ±248, or ±256 cm/s are sentinel values from the radar firmware +static constexpr bool is_speed_valid(int16_t speed) { + int16_t abs_speed = speed < 0 ? -speed : speed; + return speed != 0 && abs_speed != SPEED_SENTINEL_248 && abs_speed != SPEED_SENTINEL_256; +} + +void RD03DComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up RD-03D..."); + this->set_timeout(SETUP_TIMEOUT_MS, [this]() { this->apply_config_(); }); +} + +void RD03DComponent::dump_config() { + ESP_LOGCONFIG(TAG, "RD-03D:"); + if (this->tracking_mode_.has_value()) { + ESP_LOGCONFIG(TAG, " Tracking Mode: %s", + *this->tracking_mode_ == TrackingMode::SINGLE_TARGET ? "single" : "multi"); + } + if (this->throttle_ > 0) { + ESP_LOGCONFIG(TAG, " Throttle: %ums", this->throttle_); + } +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Target Count", this->target_count_sensor_); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_); +#endif + for (uint8_t i = 0; i < MAX_TARGETS; i++) { + ESP_LOGCONFIG(TAG, " Target %d:", i + 1); +#ifdef USE_SENSOR + LOG_SENSOR(" ", "X", this->targets_[i].x); + LOG_SENSOR(" ", "Y", this->targets_[i].y); + LOG_SENSOR(" ", "Speed", this->targets_[i].speed); + LOG_SENSOR(" ", "Distance", this->targets_[i].distance); + LOG_SENSOR(" ", "Resolution", this->targets_[i].resolution); + LOG_SENSOR(" ", "Angle", this->targets_[i].angle); +#endif +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Presence", this->target_presence_[i]); +#endif + } +} + +void RD03DComponent::loop() { + while (this->available()) { + uint8_t byte = this->read(); + ESP_LOGVV(TAG, "Received byte: 0x%02X, buffer_pos: %d", byte, this->buffer_pos_); + + // Check if we're looking for frame header + if (this->buffer_pos_ < FRAME_HEADER_SIZE) { + if (byte == FRAME_HEADER[this->buffer_pos_]) { + this->buffer_[this->buffer_pos_++] = byte; + } else if (byte == FRAME_HEADER[0]) { + // Start over if we see a potential new header + this->buffer_[0] = byte; + this->buffer_pos_ = 1; + } else { + this->buffer_pos_ = 0; + } + continue; + } + + // Accumulate data bytes + this->buffer_[this->buffer_pos_++] = byte; + + // Check if we have a complete frame + if (this->buffer_pos_ == FRAME_SIZE) { + // Validate footer + if (this->buffer_[FRAME_SIZE - 2] == FRAME_FOOTER[0] && this->buffer_[FRAME_SIZE - 1] == FRAME_FOOTER[1]) { + this->process_frame_(); + } else { + ESP_LOGW(TAG, "Invalid frame footer: 0x%02X 0x%02X (expected 0x55 0xCC)", this->buffer_[FRAME_SIZE - 2], + this->buffer_[FRAME_SIZE - 1]); + } + this->buffer_pos_ = 0; + } + } +} + +void RD03DComponent::process_frame_() { + // Apply throttle if configured + if (this->throttle_ > 0) { + uint32_t now = millis(); + if (now - this->last_publish_time_ < this->throttle_) { + return; + } + this->last_publish_time_ = now; + } + + uint8_t target_count = 0; + + for (uint8_t i = 0; i < MAX_TARGETS; i++) { + // Calculate offset for this target's data + // Header is 4 bytes, each target is 8 bytes + uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE); + + // Extract raw bytes for this target + uint8_t x_low = this->buffer_[offset + 0]; + uint8_t x_high = this->buffer_[offset + 1]; + uint8_t y_low = this->buffer_[offset + 2]; + uint8_t y_high = this->buffer_[offset + 3]; + uint8_t speed_low = this->buffer_[offset + 4]; + uint8_t speed_high = this->buffer_[offset + 5]; + uint8_t res_low = this->buffer_[offset + 6]; + uint8_t res_high = this->buffer_[offset + 7]; + + // Decode values per RD-03D format + int16_t x = decode_value(x_low, x_high); + int16_t y = decode_value(y_low, y_high); + int16_t speed = decode_value(speed_low, speed_high); + uint16_t resolution = (res_high << 8) | res_low; + + // Check if target is present + // Requires non-zero coordinates AND valid speed (not a sentinel value) + // FMCW radars detect motion via Doppler; sentinel speed indicates no real target + bool has_position = (x != 0 || y != 0); + bool has_valid_speed = is_speed_valid(speed); + bool target_present = has_position && has_valid_speed; + if (target_present) { + target_count++; + } + +#ifdef USE_SENSOR + this->publish_target_(i, x, y, speed, resolution); +#endif + +#ifdef USE_BINARY_SENSOR + if (this->target_presence_[i] != nullptr) { + this->target_presence_[i]->publish_state(target_present); + } +#endif + } + +#ifdef USE_SENSOR + if (this->target_count_sensor_ != nullptr) { + this->target_count_sensor_->publish_state(target_count); + } +#endif + +#ifdef USE_BINARY_SENSOR + if (this->target_binary_sensor_ != nullptr) { + this->target_binary_sensor_->publish_state(target_count > 0); + } +#endif +} + +#ifdef USE_SENSOR +void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution) { + TargetSensor &target = this->targets_[target_num]; + bool valid = is_speed_valid(speed); + + // Publish X coordinate (mm) - NaN if target invalid + if (target.x != nullptr) { + target.x->publish_state(valid ? static_cast(x) : NAN); + } + + // Publish Y coordinate (mm) - NaN if target invalid + if (target.y != nullptr) { + target.y->publish_state(valid ? static_cast(y) : NAN); + } + + // Publish speed (convert from cm/s to mm/s) - NaN if target invalid + if (target.speed != nullptr) { + target.speed->publish_state(valid ? static_cast(speed) * 10.0f : NAN); + } + + // Publish resolution (mm) + if (target.resolution != nullptr) { + target.resolution->publish_state(resolution); + } + + // Calculate and publish distance (mm) - NaN if target invalid + if (target.distance != nullptr) { + if (valid) { + target.distance->publish_state(std::hypot(static_cast(x), static_cast(y))); + } else { + target.distance->publish_state(NAN); + } + } + + // Calculate and publish angle (degrees) - NaN if target invalid + // Angle is measured from the Y axis (radar forward direction) + if (target.angle != nullptr) { + if (valid) { + float angle = std::atan2(static_cast(x), static_cast(y)) * 180.0f / M_PI; + target.angle->publish_state(angle); + } else { + target.angle->publish_state(NAN); + } + } +} +#endif + +void RD03DComponent::send_command_(uint16_t command, const uint8_t *data, uint8_t data_len) { + // Send header + this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER)); + + // Send length (command word + data) + uint16_t len = 2 + data_len; + this->write_byte(len & 0xFF); + this->write_byte((len >> 8) & 0xFF); + + // Send command word (little-endian) + this->write_byte(command & 0xFF); + this->write_byte((command >> 8) & 0xFF); + + // Send data if any + if (data != nullptr && data_len > 0) { + this->write_array(data, data_len); + } + + // Send footer + this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); + + ESP_LOGD(TAG, "Sent command 0x%04X with %d bytes of data", command, data_len); +} + +void RD03DComponent::apply_config_() { + if (this->tracking_mode_.has_value()) { + uint16_t mode_cmd = (*this->tracking_mode_ == TrackingMode::SINGLE_TARGET) ? CMD_SINGLE_TARGET : CMD_MULTI_TARGET; + this->send_command_(mode_cmd); + } +} + +} // namespace esphome::rd03d diff --git a/esphome/components/rd03d/rd03d.h b/esphome/components/rd03d/rd03d.h new file mode 100644 index 0000000000..7413fe38f2 --- /dev/null +++ b/esphome/components/rd03d/rd03d.h @@ -0,0 +1,93 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif + +#include + +namespace esphome::rd03d { + +static constexpr uint8_t MAX_TARGETS = 3; +static constexpr uint8_t FRAME_HEADER_SIZE = 4; +static constexpr uint8_t FRAME_FOOTER_SIZE = 2; +static constexpr uint8_t TARGET_DATA_SIZE = 8; +static constexpr uint8_t FRAME_SIZE = + FRAME_HEADER_SIZE + (MAX_TARGETS * TARGET_DATA_SIZE) + FRAME_FOOTER_SIZE; // 30 bytes + +enum class TrackingMode : uint8_t { + SINGLE_TARGET = 0, + MULTI_TARGET = 1, +}; + +#ifdef USE_SENSOR +struct TargetSensor { + sensor::Sensor *x{nullptr}; + sensor::Sensor *y{nullptr}; + sensor::Sensor *speed{nullptr}; + sensor::Sensor *distance{nullptr}; + sensor::Sensor *resolution{nullptr}; + sensor::Sensor *angle{nullptr}; +}; +#endif + +class RD03DComponent : public Component, public uart::UARTDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + +#ifdef USE_SENSOR + void set_target_count_sensor(sensor::Sensor *sensor) { this->target_count_sensor_ = sensor; } + void set_x_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].x = sensor; } + void set_y_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].y = sensor; } + void set_speed_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].speed = sensor; } + void set_distance_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].distance = sensor; } + void set_resolution_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].resolution = sensor; } + void set_angle_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].angle = sensor; } +#endif +#ifdef USE_BINARY_SENSOR + void set_target_binary_sensor(binary_sensor::BinarySensor *sensor) { this->target_binary_sensor_ = sensor; } + void set_target_binary_sensor(uint8_t target, binary_sensor::BinarySensor *sensor) { + this->target_presence_[target] = sensor; + } +#endif + + // Configuration setters (called from code generation) + void set_tracking_mode(TrackingMode mode) { this->tracking_mode_ = mode; } + void set_throttle(uint32_t throttle) { this->throttle_ = throttle; } + + protected: + void apply_config_(); + void send_command_(uint16_t command, const uint8_t *data = nullptr, uint8_t data_len = 0); + void process_frame_(); +#ifdef USE_SENSOR + void publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution); +#endif + +#ifdef USE_SENSOR + std::array targets_{}; + sensor::Sensor *target_count_sensor_{nullptr}; +#endif +#ifdef USE_BINARY_SENSOR + std::array target_presence_{}; + binary_sensor::BinarySensor *target_binary_sensor_{nullptr}; +#endif + + // Configuration (only sent if explicitly set) + optional tracking_mode_{}; + uint32_t throttle_{0}; + uint32_t last_publish_time_{0}; + + std::array buffer_{}; + uint8_t buffer_pos_{0}; +}; + +} // namespace esphome::rd03d diff --git a/esphome/components/rd03d/sensor.py b/esphome/components/rd03d/sensor.py new file mode 100644 index 0000000000..4b4fcfd4e4 --- /dev/null +++ b/esphome/components/rd03d/sensor.py @@ -0,0 +1,105 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ANGLE, + CONF_DISTANCE, + CONF_RESOLUTION, + CONF_SPEED, + CONF_X, + CONF_Y, + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_SPEED, + STATE_CLASS_MEASUREMENT, + UNIT_DEGREES, + UNIT_MILLIMETER, +) + +from . import CONF_RD03D_ID, RD03DComponent + +DEPENDENCIES = ["rd03d"] + +CONF_TARGET_COUNT = "target_count" + +MAX_TARGETS = 3 + +UNIT_MILLIMETER_PER_SECOND = "mm/s" + +TARGET_SCHEMA = cv.Schema( + { + cv.Optional(CONF_X): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_Y): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_SPEED): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER_PER_SECOND, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SPEED, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RESOLUTION): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ANGLE): sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent), + cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +).extend({cv.Optional(f"target_{i + 1}"): TARGET_SCHEMA for i in range(MAX_TARGETS)}) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_RD03D_ID]) + + if target_count_config := config.get(CONF_TARGET_COUNT): + sens = await sensor.new_sensor(target_count_config) + cg.add(hub.set_target_count_sensor(sens)) + + for i in range(MAX_TARGETS): + if target_config := config.get(f"target_{i + 1}"): + if x_config := target_config.get(CONF_X): + sens = await sensor.new_sensor(x_config) + cg.add(hub.set_x_sensor(i, sens)) + if y_config := target_config.get(CONF_Y): + sens = await sensor.new_sensor(y_config) + cg.add(hub.set_y_sensor(i, sens)) + if speed_config := target_config.get(CONF_SPEED): + sens = await sensor.new_sensor(speed_config) + cg.add(hub.set_speed_sensor(i, sens)) + if distance_config := target_config.get(CONF_DISTANCE): + sens = await sensor.new_sensor(distance_config) + cg.add(hub.set_distance_sensor(i, sens)) + if resolution_config := target_config.get(CONF_RESOLUTION): + sens = await sensor.new_sensor(resolution_config) + cg.add(hub.set_resolution_sensor(i, sens)) + if angle_config := target_config.get(CONF_ANGLE): + sens = await sensor.new_sensor(angle_config) + cg.add(hub.set_angle_sensor(i, sens)) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index d24d24b000..9d3e655c57 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -108,9 +108,6 @@ def register_trigger(name, type, data_type): validator = automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type), - cv.Optional(CONF_RECEIVER_ID): cv.invalid( - "This has been removed in ESPHome 2022.3.0 and the trigger attaches directly to the parent receiver." - ), } ) registerer = TRIGGER_REGISTRY.register(f"on_{name}", validator) @@ -207,13 +204,7 @@ validate_binary_sensor = cv.validate_registry_entry( "remote receiver", BINARY_SENSOR_REGISTRY ) TRIGGER_REGISTRY = SimpleRegistry() -DUMPER_REGISTRY = Registry( - { - cv.Optional(CONF_RECEIVER_ID): cv.invalid( - "This has been removed in ESPHome 1.20.0 and the dumper attaches directly to the parent receiver." - ), - } -) +DUMPER_REGISTRY = Registry() def validate_dumpers(value): @@ -480,10 +471,6 @@ COOLIX_BASE_SCHEMA = cv.Schema( { cv.Required(CONF_FIRST): cv.hex_int_range(0, 16777215), cv.Optional(CONF_SECOND, default=0): cv.hex_int_range(0, 16777215), - cv.Optional(CONF_DATA): cv.invalid( - "'data' option has been removed in ESPHome 2023.8. " - "Use the 'first' and 'second' options instead." - ), } ) diff --git a/esphome/components/remote_base/abbwelcome_protocol.cpp b/esphome/components/remote_base/abbwelcome_protocol.cpp index 88f928901b..352ae10ed7 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.cpp +++ b/esphome/components/remote_base/abbwelcome_protocol.cpp @@ -51,7 +51,8 @@ void ABBWelcomeProtocol::encode(RemoteTransmitData *dst, const ABBWelcomeData &s dst->reserve(reserve_count); for (size_t i = 0; i < src.size(); i++) this->encode_byte_(dst, src[i]); - ESP_LOGD(TAG, "Transmitting: %s", src.to_string().c_str()); + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; + ESP_LOGD(TAG, "Transmitting: %s", src.format_to(buf)); } bool ABBWelcomeProtocol::decode_byte_(RemoteReceiveData &src, bool &done, uint8_t &data) { @@ -94,7 +95,8 @@ optional ABBWelcomeProtocol::decode(RemoteReceiveData src) { for (; (received_bytes < length) && !done; received_bytes++) { uint8_t data = 0; if (!this->decode_byte_(src, done, data)) { - ESP_LOGW(TAG, "Received incomplete packet: %s", out.to_string(received_bytes).c_str()); + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; + ESP_LOGW(TAG, "Received incomplete packet: %s", out.format_to(buf, received_bytes)); return {}; } if (received_bytes == 2) { @@ -106,17 +108,19 @@ optional ABBWelcomeProtocol::decode(RemoteReceiveData src) { ESP_LOGVV(TAG, "Received Byte: 0x%02X", data); out[received_bytes] = data; } + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; if (out.is_valid()) { - ESP_LOGI(TAG, "Received: %s", out.to_string().c_str()); + ESP_LOGI(TAG, "Received: %s", out.format_to(buf)); return out; } - ESP_LOGW(TAG, "Received malformed packet: %s", out.to_string(received_bytes).c_str()); + ESP_LOGW(TAG, "Received malformed packet: %s", out.format_to(buf, received_bytes)); } return {}; } void ABBWelcomeProtocol::dump(const ABBWelcomeData &data) { - ESP_LOGD(TAG, "Received ABBWelcome: %s", data.to_string().c_str()); + char buf[ABBWelcomeData::FORMAT_BUFFER_SIZE]; + ESP_LOGD(TAG, "Received ABBWelcome: %s", data.format_to(buf)); } } // namespace remote_base diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index b8d9293c11..1dddedf8ce 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -136,22 +136,12 @@ class ABBWelcomeData { this->data_[1] = 0xff; this->data_[this->size() - 1] = this->calc_cs_(); } - std::string to_string(uint8_t max_print_bytes = 255) const { - std::string info; - if (this->is_valid()) { - info = str_sprintf(this->get_three_byte_address() ? "[%06" PRIX32 " %s %06" PRIX32 "] Type: %02X" - : "[%04" PRIX32 " %s %04" PRIX32 "] Type: %02X", - this->get_source_address(), this->get_retransmission() ? "»" : ">", - this->get_destination_address(), this->get_message_type()); - if (this->get_data_size()) - info += str_sprintf(", Data: %s", format_hex_pretty(this->get_data()).c_str()); - } else { - info = "[Invalid]"; - } - uint8_t print_bytes = std::min(this->size(), max_print_bytes); - if (print_bytes) - info = str_sprintf("%s %s", format_hex_pretty(this->data_.data(), print_bytes).c_str(), info.c_str()); - return info; + // Buffer size: max raw hex output (27*3-1=80) + space(1) + type_info(27) + data(52) + null(1) = 161, rounded up + static constexpr size_t FORMAT_BUFFER_SIZE = 192; + + template char *format_to(char (&buffer)[N], uint8_t max_print_bytes = 255) const { + static_assert(N >= FORMAT_BUFFER_SIZE, "Buffer too small for format_to()"); + return this->format_to_internal_(buffer, max_print_bytes); } bool operator==(const ABBWelcomeData &rhs) const { if (std::equal(this->data_.begin(), this->data_.begin() + this->size(), rhs.data_.begin())) @@ -168,6 +158,36 @@ class ABBWelcomeData { std::array data_; // Calculate checksum uint8_t calc_cs_() const; + // Internal format implementation - buffer guaranteed >= FORMAT_BUFFER_SIZE by caller + char *format_to_internal_(char *buffer, uint8_t max_print_bytes) const { + char *ptr = buffer; + char *end = buffer + FORMAT_BUFFER_SIZE; + + uint8_t print_bytes = std::min(this->size(), max_print_bytes); + if (print_bytes) { + char raw_hex[format_hex_pretty_size(12 + MAX_DATA_LENGTH)]; + format_hex_pretty_to(raw_hex, this->data_.data(), print_bytes, '.'); + ptr += snprintf(ptr, end - ptr, "%s ", raw_hex); + } + + if (this->is_valid()) { + ptr += snprintf(ptr, end - ptr, + this->get_three_byte_address() ? "[%06" PRIX32 " %s %06" PRIX32 "] Type: %02X" + : "[%04" PRIX32 " %s %04" PRIX32 "] Type: %02X", + this->get_source_address(), this->get_retransmission() ? "»" : ">", + this->get_destination_address(), this->get_message_type()); + if (this->get_data_size()) { + char data_hex[format_hex_pretty_size(MAX_DATA_LENGTH)]; + format_hex_pretty_to(data_hex, this->data_.data() + 5 + 2 * this->get_address_length(), this->get_data_size(), + '.'); + snprintf(ptr, end - ptr, ", Data: %s", data_hex); + } + } else { + snprintf(ptr, end - ptr, "[Invalid]"); + } + + return buffer; + } }; class ABBWelcomeProtocol : public RemoteProtocol { diff --git a/esphome/components/remote_base/haier_protocol.cpp b/esphome/components/remote_base/haier_protocol.cpp index ec5cb5775c..734f3c7789 100644 --- a/esphome/components/remote_base/haier_protocol.cpp +++ b/esphome/components/remote_base/haier_protocol.cpp @@ -1,4 +1,5 @@ #include "haier_protocol.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -12,6 +13,8 @@ constexpr uint32_t BIT_MARK_US = 540; constexpr uint32_t BIT_ONE_SPACE_US = 1650; constexpr uint32_t BIT_ZERO_SPACE_US = 580; constexpr unsigned int HAIER_IR_PACKET_BIT_SIZE = 112; +// Max data bytes in packet (excluding checksum) +constexpr size_t HAIER_MAX_DATA_BYTES = (HAIER_IR_PACKET_BIT_SIZE / 8); void HaierProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t item) { for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) { @@ -77,7 +80,8 @@ optional HaierProtocol::decode(RemoteReceiveData src) { } void HaierProtocol::dump(const HaierData &data) { - ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty(data.data).c_str()); + char hex_buf[format_hex_pretty_size(HAIER_MAX_DATA_BYTES)]; + ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } } // namespace remote_base diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index 8006fe4048..4fa717cf08 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -70,7 +70,10 @@ optional MideaProtocol::decode(RemoteReceiveData src) { return {}; } -void MideaProtocol::dump(const MideaData &data) { ESP_LOGI(TAG, "Received Midea: %s", data.to_string().c_str()); } +void MideaProtocol::dump(const MideaData &data) { + char buf[MideaData::TO_STR_BUFFER_SIZE]; + ESP_LOGI(TAG, "Received Midea: %s", data.to_str(buf)); +} } // namespace remote_base } // namespace esphome diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 94fb6f3d94..ddefff867a 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -29,7 +29,16 @@ class MideaData { bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); } void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); } bool is_compliment(const MideaData &rhs) const; - std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); } + /// @deprecated Allocates heap memory. Use to_str() instead. Removed in 2026.7.0. + ESPDEPRECATED("Allocates heap memory. Use to_str() instead. Removed in 2026.7.0.", "2026.1.0") + std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); } // NOLINT + /// Buffer size for to_str(): 6 bytes = "AA.BB.CC.DD.EE.FF\0" + static constexpr size_t TO_STR_BUFFER_SIZE = format_hex_pretty_size(6); + /// Format to buffer, returns pointer to buffer + const char *to_str(char *buffer) const { + format_hex_pretty_to(buffer, TO_STR_BUFFER_SIZE, this->data_.data(), this->data_.size(), '.'); + return buffer; + } // compare only 40-bits bool operator==(const MideaData &rhs) const { return std::equal(this->data_.begin(), this->data_.begin() + OFFSET_CS, rhs.data_.begin()); diff --git a/esphome/components/remote_base/mirage_protocol.cpp b/esphome/components/remote_base/mirage_protocol.cpp index 10d644a1cd..2ae877f193 100644 --- a/esphome/components/remote_base/mirage_protocol.cpp +++ b/esphome/components/remote_base/mirage_protocol.cpp @@ -1,4 +1,5 @@ #include "mirage_protocol.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -13,9 +14,12 @@ constexpr uint32_t BIT_ONE_SPACE_US = 1592; constexpr uint32_t BIT_ZERO_SPACE_US = 545; constexpr unsigned int MIRAGE_IR_PACKET_BIT_SIZE = 120; +// Max data bytes in packet (excluding checksum) +constexpr size_t MIRAGE_MAX_DATA_BYTES = (MIRAGE_IR_PACKET_BIT_SIZE / 8); void MirageProtocol::encode(RemoteTransmitData *dst, const MirageData &data) { - ESP_LOGI(TAG, "Transive Mirage: %s", format_hex_pretty(data.data).c_str()); + char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)]; + ESP_LOGI(TAG, "Transmit Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); dst->set_carrier_frequency(38000); dst->reserve(5 + ((data.data.size() + 1) * 2)); dst->mark(HEADER_MARK_US); @@ -77,7 +81,8 @@ optional MirageProtocol::decode(RemoteReceiveData src) { } void MirageProtocol::dump(const MirageData &data) { - ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty(data.data).c_str()); + char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)]; + ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size())); } } // namespace remote_base diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index 9fbc9e85ba..401a0976b2 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -104,8 +104,10 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vectordata_.clear(); + this->data_.reserve(count); + + while (len > 0) { + // Parse varint (inline, no dependency on api component) + uint32_t raw = 0; + uint32_t shift = 0; + uint32_t consumed = 0; + for (; consumed < len && consumed < 5; consumed++) { + uint8_t byte = data[consumed]; + raw |= (byte & 0x7F) << shift; + if ((byte & 0x80) == 0) { + consumed++; + break; + } + shift += 7; + } + if (consumed == 0) + break; // Parse error + + // Zigzag decode: (n >> 1) ^ -(n & 1) + int32_t decoded = static_cast((raw >> 1) ^ (~(raw & 1) + 1)); + this->data_.push_back(decoded); + data += consumed; + len -= consumed; + } +} + +/* RemoteTransmitterBase */ + void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE const auto &vec = this->temp_.get_data(); diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index 2cb79bf571..a11e0271af 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -31,6 +31,11 @@ class RemoteTransmitData { uint32_t get_carrier_frequency() const { return this->carrier_frequency_; } const RawTimings &get_data() const { return this->data_; } void set_data(const RawTimings &data) { this->data_ = data; } + /// Set data from packed protobuf sint32 buffer (zigzag + varint encoded) + /// @param data Pointer to packed zigzag-varint-encoded sint32 values + /// @param len Length of the buffer in bytes + /// @param count Number of values (for reserve optimization) + void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count); void reset() { this->data_.clear(); this->carrier_frequency_ = 0; diff --git a/esphome/components/remote_receiver/remote_receiver.cpp b/esphome/components/remote_receiver/remote_receiver.cpp index a7ac74199d..de47457dac 100644 --- a/esphome/components/remote_receiver/remote_receiver.cpp +++ b/esphome/components/remote_receiver/remote_receiver.cpp @@ -76,9 +76,8 @@ void RemoteReceiverComponent::setup() { } void RemoteReceiverComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Remote Receiver:"); - LOG_PIN(" Pin: ", this->pin_); ESP_LOGCONFIG(TAG, + "Remote Receiver:\n" " Buffer Size: %u\n" " Tolerance: %u%s\n" " Filter out pulses shorter than: %u us\n" @@ -86,6 +85,7 @@ void RemoteReceiverComponent::dump_config() { this->buffer_size_, this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->filter_us_, this->idle_us_); + LOG_PIN(" Pin: ", this->pin_); } void RemoteReceiverComponent::loop() { diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index bd0bc8e57b..eda8365169 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -117,9 +117,8 @@ void RemoteReceiverComponent::setup() { } void RemoteReceiverComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Remote Receiver:"); - LOG_PIN(" Pin: ", this->pin_); ESP_LOGCONFIG(TAG, + "Remote Receiver:\n" " Clock resolution: %" PRIu32 " hz\n" " RMT symbols: %" PRIu32 "\n" " Filter symbols: %" PRIu32 "\n" @@ -132,6 +131,7 @@ void RemoteReceiverComponent::dump_config() { this->clock_resolution_, this->rmt_symbols_, this->filter_symbols_, this->receive_symbols_, this->tolerance_, (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->carrier_frequency_, this->carrier_duty_percent_, this->filter_us_, this->idle_us_); + LOG_PIN(" Pin: ", this->pin_); if (this->is_failed()) { ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_), this->error_string_.c_str()); diff --git a/esphome/components/resampler/speaker/__init__.py b/esphome/components/resampler/speaker/__init__.py index def62547b2..7036862d14 100644 --- a/esphome/components/resampler/speaker/__init__.py +++ b/esphome/components/resampler/speaker/__init__.py @@ -63,9 +63,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_BUFFER_DURATION, default="100ms" ): cv.positive_time_period_milliseconds, - cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf - ), + cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean, cv.Optional(CONF_FILTERS, default=16): cv.int_range(min=2, max=1024), cv.Optional(CONF_TAPS, default=16): _validate_taps, } diff --git a/esphome/components/rp2040/gpio.cpp b/esphome/components/rp2040/gpio.cpp index 3927815e46..2b1699f888 100644 --- a/esphome/components/rp2040/gpio.cpp +++ b/esphome/components/rp2040/gpio.cpp @@ -64,10 +64,8 @@ void RP2040GPIOPin::pin_mode(gpio::Flags flags) { pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT } -std::string RP2040GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); - return buffer; +size_t RP2040GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%u", this->pin_); } bool RP2040GPIOPin::digital_read() { diff --git a/esphome/components/rp2040/gpio.h b/esphome/components/rp2040/gpio.h index 47a6fe17f2..a98e1dab14 100644 --- a/esphome/components/rp2040/gpio.h +++ b/esphome/components/rp2040/gpio.h @@ -18,7 +18,7 @@ class RP2040GPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return pin_; } diff --git a/esphome/components/rp2040_pwm/rp2040_pwm.cpp b/esphome/components/rp2040_pwm/rp2040_pwm.cpp index ec164b3c05..90a507b14f 100644 --- a/esphome/components/rp2040_pwm/rp2040_pwm.cpp +++ b/esphome/components/rp2040_pwm/rp2040_pwm.cpp @@ -36,9 +36,11 @@ void RP2040PWM::setup_pwm_() { } void RP2040PWM::dump_config() { - ESP_LOGCONFIG(TAG, "RP2040 PWM:"); + ESP_LOGCONFIG(TAG, + "RP2040 PWM:\n" + " Frequency: %.1f Hz", + this->frequency_); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); LOG_FLOAT_OUTPUT(this); } void HOT RP2040PWM::write_state(float state) { diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py index 8e9da43a74..e92eee7c0c 100644 --- a/esphome/components/rpi_dpi_rgb/display.py +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -122,7 +122,6 @@ CONFIG_SCHEMA = cv.All( ) ), only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, ) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index 042b8877e6..a81bb17dfc 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -126,8 +126,10 @@ void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) { void RpiDpiRgb::dump_config() { ESP_LOGCONFIG("", "RPI_DPI_RGB LCD"); - ESP_LOGCONFIG(TAG, " Height: %u", this->height_); - ESP_LOGCONFIG(TAG, " Width: %u", this->width_); + ESP_LOGCONFIG(TAG, + " Height: %u\n" + " Width: %u", + this->height_, this->width_); LOG_PIN(" DE Pin: ", this->de_pin_); LOG_PIN(" Enable Pin: ", this->enable_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index 8f27544108..6fd750d51e 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -1,18 +1,29 @@ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. -# For custom pin validators, put validate_pin() or validate_usage() -# in gpio.py file in this directory. -# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA -# in schema.py file in this directory. +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. + +To customize this component: + - Pin validators: Create gpio.py with validate_pin() or validate_usage() + - Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA + +Platform-specific code should be added to the main libretiny component +(__init__.py in esphome/components/libretiny/) rather than here. +""" from esphome import pins -import esphome.codegen as cg from esphome.components import libretiny from esphome.components.libretiny.const import ( COMPONENT_RTL87XX, - FAMILY_RTL8710B, KEY_COMPONENT_DATA, - KEY_FAMILY, KEY_LIBRETINY, LibreTinyComponent, ) @@ -24,13 +35,13 @@ CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["libretiny"] IS_TARGET_PLATFORM = True - COMPONENT_DATA = LibreTinyComponent( name=COMPONENT_RTL87XX, boards=RTL87XX_BOARDS, board_pins=RTL87XX_BOARD_PINS, pin_validation=None, usage_validation=None, + supports_atomics=True, ) @@ -48,11 +59,6 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data) async def to_code(config): - # Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+ - # https://github.com/esphome/esphome/issues/10220 - # Only for RTL8710B (ambz) - RTL8720C (ambz2) requires FreeRTOS 10.x - if CORE.data[KEY_LIBRETINY][KEY_FAMILY] == FAMILY_RTL8710B: - cg.add_platformio_option("custom_versions.freertos", "8.2.3") return await libretiny.component_to_code(config) diff --git a/esphome/components/rtl87xx/boards.py b/esphome/components/rtl87xx/boards.py index e737767a56..5a3228fb1d 100644 --- a/esphome/components/rtl87xx/boards.py +++ b/esphome/components/rtl87xx/boards.py @@ -1,21 +1,52 @@ -# This file was auto-generated by libretiny/generate_components.py -# Do not modify its contents. +""" +██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ +██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝ +██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗ +██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║ +╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝ + ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ + + AUTO-GENERATED FILE - DO NOT EDIT! + +This file was auto-generated by libretiny/generate_components.py. +Any manual changes WILL BE LOST on regeneration. +""" from esphome.components.libretiny.const import FAMILY_RTL8710B, FAMILY_RTL8720C RTL87XX_BOARDS = { - "bw12": { - "name": "BW12", + "wr3le": { + "name": "WR3LE Wi-Fi Module", "family": FAMILY_RTL8710B, }, - "bw15": { - "name": "BW15", - "family": FAMILY_RTL8720C, + "wr2": { + "name": "WR2 Wi-Fi Module", + "family": FAMILY_RTL8710B, }, "generic-rtl8710bn-2mb-468k": { "name": "Generic - RTL8710BN (2M/468k)", "family": FAMILY_RTL8710B, }, + "wr1e": { + "name": "WR1E Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr3e": { + "name": "WR3E Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "wr3": { + "name": "WR3 Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, + "afw121t": { + "name": "AFW121T", + "family": FAMILY_RTL8710B, + }, + "wr3n": { + "name": "WR3N Wi-Fi Module", + "family": FAMILY_RTL8710B, + }, "generic-rtl8710bn-2mb-788k": { "name": "Generic - RTL8710BN (2M/788k)", "family": FAMILY_RTL8710B, @@ -24,70 +55,827 @@ RTL87XX_BOARDS = { "name": "Generic - RTL8710BX (4M/980k)", "family": FAMILY_RTL8710B, }, - "generic-rtl8720cf-2mb-992k": { - "name": "Generic - RTL8720CF (2M/992k)", - "family": FAMILY_RTL8720C, - }, - "t102-v1.1": { - "name": "T102_V1.1", - "family": FAMILY_RTL8710B, - }, - "t103-v1.0": { - "name": "T103_V1.0", + "wr2e": { + "name": "WR2E Wi-Fi Module", "family": FAMILY_RTL8710B, }, "t112-v1.1": { "name": "T112_V1.1", "family": FAMILY_RTL8710B, }, - "wr1": { - "name": "WR1 Wi-Fi Module", - "family": FAMILY_RTL8710B, - }, - "wr1e": { - "name": "WR1E Wi-Fi Module", - "family": FAMILY_RTL8710B, - }, - "wr2": { - "name": "WR2 Wi-Fi Module", - "family": FAMILY_RTL8710B, - }, - "wr2e": { - "name": "WR2E Wi-Fi Module", - "family": FAMILY_RTL8710B, - }, - "wr2l": { - "name": "WR2L Wi-Fi Module", + "wr3l": { + "name": "WR3L Wi-Fi Module", "family": FAMILY_RTL8710B, }, "wr2le": { "name": "WR2LE Wi-Fi Module", "family": FAMILY_RTL8710B, }, - "wr3": { - "name": "WR3 Wi-Fi Module", + "bw15": { + "name": "BW15", + "family": FAMILY_RTL8720C, + }, + "t103-v1.0": { + "name": "T103_V1.0", "family": FAMILY_RTL8710B, }, - "wr3e": { - "name": "WR3E Wi-Fi Module", + "generic-rtl8720cf-2mb-992k": { + "name": "Generic - RTL8720CF (2M/992k)", + "family": FAMILY_RTL8720C, + }, + "bw12": { + "name": "BW12", "family": FAMILY_RTL8710B, }, - "wr3l": { - "name": "WR3L Wi-Fi Module", + "t102-v1.1": { + "name": "T102_V1.1", "family": FAMILY_RTL8710B, }, - "wr3le": { - "name": "WR3LE Wi-Fi Module", + "wr2l": { + "name": "WR2L Wi-Fi Module", "family": FAMILY_RTL8710B, }, - "wr3n": { - "name": "WR3N Wi-Fi Module", + "wr1": { + "name": "WR1 Wi-Fi Module", "family": FAMILY_RTL8710B, }, } RTL87XX_BOARD_PINS = { - "bw12": { + "wr3le": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 30, + "WIRE0_SDA_1": 19, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 14, + "D2": 15, + "D3": 22, + "D4": 0, + "D5": 30, + "D6": 19, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "wr2": { + "WIRE0_SCL": 29, + "WIRE0_SDA": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC2": 41, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 29, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 12, + "D1": 0, + "D2": 5, + "D4": 18, + "D5": 23, + "D6": 14, + "D7": 15, + "D8": 30, + "D9": 29, + "A1": 41, + }, + "generic-rtl8710bn-2mb-468k": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "FCS": 6, + "FD0": 9, + "FD1": 7, + "FD2": 8, + "FD3": 11, + "FSCK": 10, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA06": 6, + "PA6": 6, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 0, + "D1": 5, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 12, + "D9": 14, + "D10": 15, + "D11": 18, + "D12": 19, + "D13": 22, + "D14": 23, + "D15": 29, + "D16": 30, + "A0": 19, + "A1": 41, + }, + "wr1e": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 30, + "WIRE0_SDA_1": 19, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM3": 12, + "PWM4": 29, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 23, + "D1": 18, + "D2": 14, + "D3": 15, + "D4": 30, + "D5": 12, + "D6": 5, + "D7": 29, + "D8": 19, + "D9": 22, + "A0": 19, + "A1": 41, + }, + "wr3e": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 29, + "WIRE0_SCL_1": 22, + "WIRE0_SDA_0": 30, + "WIRE0_SDA_1": 19, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 14, + "D2": 15, + "D3": 22, + "D4": 0, + "D5": 30, + "D6": 19, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "wr3": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 22, + "D1": 19, + "D2": 14, + "D3": 15, + "D4": 0, + "D5": 29, + "D6": 30, + "D7": 5, + "D8": 12, + "D9": 18, + "D10": 23, + "A0": 19, + "A1": 41, + }, + "afw121t": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 29, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 14, + "D1": 15, + "D2": 0, + "D3": 12, + "D4": 29, + "D5": 5, + "D6": 18, + "D7": 19, + "D8": 22, + "D9": 23, + "D10": 30, + }, + "wr3n": { + "WIRE0_SCL": 29, + "WIRE0_SDA": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC2": 41, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 29, + "D1": 14, + "D2": 15, + "D3": 0, + "D4": 30, + "D5": 5, + "D6": 12, + "D7": 18, + "D8": 23, + "A1": 41, + }, + "generic-rtl8710bn-2mb-788k": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "FCS": 6, + "FD0": 9, + "FD1": 7, + "FD2": 8, + "FD3": 11, + "FSCK": 10, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA06": 6, + "PA6": 6, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 0, + "D1": 5, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 12, + "D9": 14, + "D10": 15, + "D11": 18, + "D12": 19, + "D13": 22, + "D14": 23, + "D15": 29, + "D16": 30, + "A0": 19, + "A1": 41, + }, + "generic-rtl8710bx-4mb-980k": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "FCS": 6, + "FD0": 9, + "FD1": 7, + "FD2": 8, + "FD3": 11, + "FSCK": 10, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA06": 6, + "PA6": 6, + "PA07": 7, + "PA7": 7, + "PA08": 8, + "PA8": 8, + "PA09": 9, + "PA9": 9, + "PA10": 10, + "PA11": 11, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 30, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 0, + "D1": 5, + "D2": 6, + "D3": 7, + "D4": 8, + "D5": 9, + "D6": 10, + "D7": 11, + "D8": 12, + "D9": 14, + "D10": 15, + "D11": 18, + "D12": 19, + "D13": 22, + "D14": 23, + "D15": 29, + "D16": 30, + "A0": 19, + }, + "wr2e": { + "WIRE0_SCL": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MOSI0": 23, + "MOSI1": 23, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM3": 12, + "PWM4": 29, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 12, + "D1": 19, + "D2": 5, + "D3": 18, + "D4": 23, + "D5": 14, + "D6": 15, + "D7": 30, + "D8": 29, + "A0": 19, + "A1": 41, + }, + "t112-v1.1": { "SPI0_CS": 19, "SPI0_MISO": 22, "SPI0_MOSI": 23, @@ -143,18 +931,111 @@ RTL87XX_BOARD_PINS = { "SDA1": 23, "TX0": 23, "TX2": 30, - "D0": 5, - "D1": 29, - "D2": 0, - "D3": 19, - "D4": 22, - "D5": 30, - "D6": 14, + "D0": 29, + "D1": 19, + "D2": 15, + "D3": 14, + "D4": 0, + "D5": 5, + "D6": 18, "D7": 12, - "D8": 15, + "D8": 23, + "D9": 22, + "D10": 30, + "A0": 19, + }, + "wr3l": { + "SPI0_CS": 19, + "SPI0_MISO": 22, + "SPI0_MOSI": 23, + "SPI0_SCK": 18, + "SPI1_CS": 19, + "SPI1_MISO": 22, + "SPI1_MOSI": 23, + "SPI1_SCK": 18, + "WIRE0_SCL_0": 22, + "WIRE0_SCL_1": 29, + "WIRE0_SDA_0": 19, + "WIRE0_SDA_1": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_CTS": 19, + "SERIAL0_RTS": 22, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "ADC1": 19, + "ADC2": 41, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "MISO0": 22, + "MISO1": 22, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA19": 19, + "PA22": 22, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL1": 18, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 22, + "D1": 19, + "D2": 14, + "D3": 15, + "D4": 0, + "D5": 29, + "D6": 30, + "D7": 5, + "D8": 12, "D9": 18, "D10": 23, "A0": 19, + "A1": 41, + }, + "wr2le": { + "MISO0": 22, + "MISO1": 22, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA22": 22, + "PWM0": 14, + "PWM1": 15, + "PWM3": 12, + "PWM4": 5, + "PWM5": 22, + "RTS0": 22, + "SCL0": 22, + "D0": 15, + "D1": 14, + "D2": 5, + "D3": 22, + "D4": 12, }, "bw15": { "SPI0_CS_0": 2, @@ -226,7 +1107,7 @@ RTL87XX_BOARD_PINS = { "D11": 13, "D12": 14, }, - "generic-rtl8710bn-2mb-468k": { + "t103-v1.0": { "SPI0_CS": 19, "SPI0_MISO": 22, "SPI0_MOSI": 23, @@ -252,12 +1133,6 @@ RTL87XX_BOARD_PINS = { "CS0": 19, "CS1": 19, "CTS0": 19, - "FCS": 6, - "FD0": 9, - "FD1": 7, - "FD2": 8, - "FD3": 11, - "FSCK": 10, "MISO0": 22, "MISO1": 22, "MOSI0": 23, @@ -266,16 +1141,6 @@ RTL87XX_BOARD_PINS = { "PA0": 0, "PA05": 5, "PA5": 5, - "PA06": 6, - "PA6": 6, - "PA07": 7, - "PA7": 7, - "PA08": 8, - "PA8": 8, - "PA09": 9, - "PA9": 9, - "PA10": 10, - "PA11": 11, "PA12": 12, "PA14": 14, "PA15": 15, @@ -288,7 +1153,7 @@ RTL87XX_BOARD_PINS = { "PWM1": 15, "PWM2": 0, "PWM3": 12, - "PWM4": 30, + "PWM4": 5, "PWM5": 22, "RTS0": 22, "RX0": 18, @@ -299,210 +1164,20 @@ RTL87XX_BOARD_PINS = { "SDA1": 23, "TX0": 23, "TX2": 30, - "D0": 0, - "D1": 5, - "D2": 6, - "D3": 7, - "D4": 8, - "D5": 9, - "D6": 10, - "D7": 11, + "D0": 19, + "D1": 14, + "D2": 15, + "D3": 0, + "D4": 22, + "D5": 29, + "D6": 30, + "D7": 5, "D8": 12, - "D9": 14, - "D10": 15, - "D11": 18, - "D12": 19, - "D13": 22, - "D14": 23, - "D15": 29, - "D16": 30, + "D9": 18, + "D10": 23, "A0": 19, "A1": 41, }, - "generic-rtl8710bn-2mb-788k": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 22, - "WIRE0_SCL_1": 29, - "WIRE0_SDA_0": 19, - "WIRE0_SDA_1": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "FCS": 6, - "FD0": 9, - "FD1": 7, - "FD2": 8, - "FD3": 11, - "FSCK": 10, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA06": 6, - "PA6": 6, - "PA07": 7, - "PA7": 7, - "PA08": 8, - "PA8": 8, - "PA09": 9, - "PA9": 9, - "PA10": 10, - "PA11": 11, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 30, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 0, - "D1": 5, - "D2": 6, - "D3": 7, - "D4": 8, - "D5": 9, - "D6": 10, - "D7": 11, - "D8": 12, - "D9": 14, - "D10": 15, - "D11": 18, - "D12": 19, - "D13": 22, - "D14": 23, - "D15": 29, - "D16": 30, - "A0": 19, - "A1": 41, - }, - "generic-rtl8710bx-4mb-980k": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 22, - "WIRE0_SCL_1": 29, - "WIRE0_SDA_0": 19, - "WIRE0_SDA_1": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "FCS": 6, - "FD0": 9, - "FD1": 7, - "FD2": 8, - "FD3": 11, - "FSCK": 10, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA06": 6, - "PA6": 6, - "PA07": 7, - "PA7": 7, - "PA08": 8, - "PA8": 8, - "PA09": 9, - "PA9": 9, - "PA10": 10, - "PA11": 11, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 30, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 0, - "D1": 5, - "D2": 6, - "D3": 7, - "D4": 8, - "D5": 9, - "D6": 10, - "D7": 11, - "D8": 12, - "D9": 14, - "D10": 15, - "D11": 18, - "D12": 19, - "D13": 22, - "D14": 23, - "D15": 29, - "D16": 30, - "A0": 19, - }, "generic-rtl8720cf-2mb-992k": { "SPI0_CS_0": 2, "SPI0_CS_1": 7, @@ -601,124 +1276,7 @@ RTL87XX_BOARD_PINS = { "D18": 20, "D19": 23, }, - "t102-v1.1": { - "WIRE0_SCL": 29, - "WIRE0_SDA": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 29, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL0": 29, - "SCL1": 18, - "SDA0": 30, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 12, - "D1": 0, - "D2": 5, - "D3": 30, - "D4": 29, - "D5": 18, - "D6": 23, - "D7": 14, - "D8": 15, - }, - "t103-v1.0": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 22, - "WIRE0_SCL_1": 29, - "WIRE0_SDA_0": 19, - "WIRE0_SDA_1": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 5, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 19, - "D1": 14, - "D2": 15, - "D3": 0, - "D4": 22, - "D5": 29, - "D6": 30, - "D7": 5, - "D8": 12, - "D9": 18, - "D10": 23, - "A0": 19, - "A1": 41, - }, - "t112-v1.1": { + "bw12": { "SPI0_CS": 19, "SPI0_MISO": 22, "SPI0_MOSI": 23, @@ -774,17 +1332,86 @@ RTL87XX_BOARD_PINS = { "SDA1": 23, "TX0": 23, "TX2": 30, - "D0": 29, - "D1": 19, - "D2": 15, - "D3": 14, - "D4": 0, - "D5": 5, - "D6": 18, + "D0": 5, + "D1": 29, + "D2": 0, + "D3": 19, + "D4": 22, + "D5": 30, + "D6": 14, "D7": 12, - "D8": 23, - "D9": 22, - "D10": 30, + "D8": 15, + "D9": 18, + "D10": 23, + "A0": 19, + }, + "t102-v1.1": { + "WIRE0_SCL": 29, + "WIRE0_SDA": 30, + "WIRE1_SCL": 18, + "WIRE1_SDA": 23, + "SERIAL0_RX": 18, + "SERIAL0_TX": 23, + "SERIAL2_RX": 29, + "SERIAL2_TX": 30, + "MOSI0": 23, + "MOSI1": 23, + "PA00": 0, + "PA0": 0, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA18": 18, + "PA23": 23, + "PA29": 29, + "PA30": 30, + "PWM1": 15, + "PWM2": 0, + "PWM3": 12, + "PWM4": 29, + "RX0": 18, + "RX2": 29, + "SCK0": 18, + "SCK1": 18, + "SCL0": 29, + "SCL1": 18, + "SDA0": 30, + "SDA1": 23, + "TX0": 23, + "TX2": 30, + "D0": 12, + "D1": 0, + "D2": 5, + "D3": 30, + "D4": 29, + "D5": 18, + "D6": 23, + "D7": 14, + "D8": 15, + }, + "wr2l": { + "ADC1": 19, + "CS0": 19, + "CS1": 19, + "CTS0": 19, + "PA05": 5, + "PA5": 5, + "PA12": 12, + "PA14": 14, + "PA15": 15, + "PA19": 19, + "PWM0": 14, + "PWM1": 15, + "PWM3": 12, + "PWM4": 5, + "SDA0": 19, + "D0": 15, + "D1": 14, + "D2": 5, + "D3": 19, + "D4": 12, "A0": 19, }, "wr1": { @@ -855,550 +1482,6 @@ RTL87XX_BOARD_PINS = { "A0": 19, "A1": 41, }, - "wr1e": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 29, - "WIRE0_SCL_1": 22, - "WIRE0_SDA_0": 30, - "WIRE0_SDA_1": 19, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM3": 12, - "PWM4": 29, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 23, - "D1": 18, - "D2": 14, - "D3": 15, - "D4": 30, - "D5": 12, - "D6": 5, - "D7": 29, - "D8": 19, - "D9": 22, - "A0": 19, - "A1": 41, - }, - "wr2": { - "WIRE0_SCL": 29, - "WIRE0_SDA": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC2": 41, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 29, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL0": 29, - "SCL1": 18, - "SDA0": 30, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 12, - "D1": 0, - "D2": 5, - "D4": 18, - "D5": 23, - "D6": 14, - "D7": 15, - "D8": 30, - "D9": 29, - "A1": 41, - }, - "wr2e": { - "WIRE0_SCL": 29, - "WIRE0_SDA_0": 19, - "WIRE0_SDA_1": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "MOSI0": 23, - "MOSI1": 23, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM3": 12, - "PWM4": 29, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL0": 29, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 12, - "D1": 19, - "D2": 5, - "D3": 18, - "D4": 23, - "D5": 14, - "D6": 15, - "D7": 30, - "D8": 29, - "A0": 19, - "A1": 41, - }, - "wr2l": { - "ADC1": 19, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA19": 19, - "PWM0": 14, - "PWM1": 15, - "PWM3": 12, - "PWM4": 5, - "SDA0": 19, - "D0": 15, - "D1": 14, - "D2": 5, - "D3": 19, - "D4": 12, - "A0": 19, - }, - "wr2le": { - "MISO0": 22, - "MISO1": 22, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA22": 22, - "PWM0": 14, - "PWM1": 15, - "PWM3": 12, - "PWM4": 5, - "PWM5": 22, - "RTS0": 22, - "SCL0": 22, - "D0": 15, - "D1": 14, - "D2": 5, - "D3": 22, - "D4": 12, - }, - "wr3": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 22, - "WIRE0_SCL_1": 29, - "WIRE0_SDA_0": 19, - "WIRE0_SDA_1": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 5, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 22, - "D1": 19, - "D2": 14, - "D3": 15, - "D4": 0, - "D5": 29, - "D6": 30, - "D7": 5, - "D8": 12, - "D9": 18, - "D10": 23, - "A0": 19, - "A1": 41, - }, - "wr3e": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 29, - "WIRE0_SCL_1": 22, - "WIRE0_SDA_0": 30, - "WIRE0_SDA_1": 19, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 5, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 29, - "D1": 14, - "D2": 15, - "D3": 22, - "D4": 0, - "D5": 30, - "D6": 19, - "D7": 5, - "D8": 12, - "D9": 18, - "D10": 23, - "A0": 19, - "A1": 41, - }, - "wr3l": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 22, - "WIRE0_SCL_1": 29, - "WIRE0_SDA_0": 19, - "WIRE0_SDA_1": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 5, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 22, - "D1": 19, - "D2": 14, - "D3": 15, - "D4": 0, - "D5": 29, - "D6": 30, - "D7": 5, - "D8": 12, - "D9": 18, - "D10": 23, - "A0": 19, - "A1": 41, - }, - "wr3le": { - "SPI0_CS": 19, - "SPI0_MISO": 22, - "SPI0_MOSI": 23, - "SPI0_SCK": 18, - "SPI1_CS": 19, - "SPI1_MISO": 22, - "SPI1_MOSI": 23, - "SPI1_SCK": 18, - "WIRE0_SCL_0": 29, - "WIRE0_SCL_1": 22, - "WIRE0_SDA_0": 30, - "WIRE0_SDA_1": 19, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_CTS": 19, - "SERIAL0_RTS": 22, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC1": 19, - "ADC2": 41, - "CS0": 19, - "CS1": 19, - "CTS0": 19, - "MISO0": 22, - "MISO1": 22, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA19": 19, - "PA22": 22, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 5, - "PWM5": 22, - "RTS0": 22, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL1": 18, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 29, - "D1": 14, - "D2": 15, - "D3": 22, - "D4": 0, - "D5": 30, - "D6": 19, - "D7": 5, - "D8": 12, - "D9": 18, - "D10": 23, - "A0": 19, - "A1": 41, - }, - "wr3n": { - "WIRE0_SCL": 29, - "WIRE0_SDA": 30, - "WIRE1_SCL": 18, - "WIRE1_SDA": 23, - "SERIAL0_RX": 18, - "SERIAL0_TX": 23, - "SERIAL2_RX": 29, - "SERIAL2_TX": 30, - "ADC2": 41, - "MOSI0": 23, - "MOSI1": 23, - "PA00": 0, - "PA0": 0, - "PA05": 5, - "PA5": 5, - "PA12": 12, - "PA14": 14, - "PA15": 15, - "PA18": 18, - "PA23": 23, - "PA29": 29, - "PA30": 30, - "PWM1": 15, - "PWM2": 0, - "PWM3": 12, - "PWM4": 5, - "RX0": 18, - "RX2": 29, - "SCK0": 18, - "SCK1": 18, - "SCL0": 29, - "SCL1": 18, - "SDA0": 30, - "SDA1": 23, - "TX0": 23, - "TX2": 30, - "D0": 29, - "D1": 14, - "D2": 15, - "D3": 0, - "D4": 30, - "D5": 5, - "D6": 12, - "D7": 18, - "D8": 23, - "A1": 41, - }, } BOARDS = RTL87XX_BOARDS diff --git a/esphome/components/runtime_stats/runtime_stats.cpp b/esphome/components/runtime_stats/runtime_stats.cpp index f95be5291f..9a1e1a109a 100644 --- a/esphome/components/runtime_stats/runtime_stats.cpp +++ b/esphome/components/runtime_stats/runtime_stats.cpp @@ -27,8 +27,10 @@ void RuntimeStatsCollector::record_component_time(Component *component, uint32_t } void RuntimeStatsCollector::log_stats_() { - ESP_LOGI(TAG, "Component Runtime Statistics"); - ESP_LOGI(TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_); + ESP_LOGI(TAG, + "Component Runtime Statistics\n" + " Period stats (last %" PRIu32 "ms):", + this->log_interval_); // First collect stats we want to display std::vector stats_to_display; @@ -53,7 +55,7 @@ void RuntimeStatsCollector::log_stats_() { } // Log total stats since boot - ESP_LOGI(TAG, "Total stats (since boot):"); + ESP_LOGI(TAG, " Total stats (since boot):"); // Re-sort by total runtime for all-time stats std::sort(stats_to_display.begin(), stats_to_display.end(), diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.cpp b/esphome/components/ruuvi_ble/ruuvi_ble.cpp index bdd012cf5c..1b126bdef0 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.cpp +++ b/esphome/components/ruuvi_ble/ruuvi_ble.cpp @@ -99,7 +99,8 @@ bool RuuviListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!res.has_value()) return false; - ESP_LOGD(TAG, "Got RuuviTag (%s):", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGD(TAG, "Got RuuviTag (%s):", device.address_str_to(addr_buf)); if (res->humidity.has_value()) { ESP_LOGD(TAG, " Humidity: %.2f%%", *res->humidity); diff --git a/esphome/components/safe_mode/__init__.py b/esphome/components/safe_mode/__init__.py index 9944d71722..d1754aaad7 100644 --- a/esphome/components/safe_mode/__init__.py +++ b/esphome/components/safe_mode/__init__.py @@ -59,9 +59,11 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - for conf in config.get(CONF_ON_SAFE_MODE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + if on_safe_mode_config := config.get(CONF_ON_SAFE_MODE): + cg.add_define("USE_SAFE_MODE_CALLBACK") + for conf in on_safe_mode_config: + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) condition = var.should_enter_safe_mode( config[CONF_NUM_ATTEMPTS], diff --git a/esphome/components/safe_mode/automation.h b/esphome/components/safe_mode/automation.h index 1ffa86a588..952ed4da33 100644 --- a/esphome/components/safe_mode/automation.h +++ b/esphome/components/safe_mode/automation.h @@ -1,10 +1,12 @@ #pragma once +#include "esphome/core/defines.h" + +#ifdef USE_SAFE_MODE_CALLBACK #include "safe_mode.h" #include "esphome/core/automation.h" -namespace esphome { -namespace safe_mode { +namespace esphome::safe_mode { class SafeModeTrigger : public Trigger<> { public: @@ -13,5 +15,6 @@ class SafeModeTrigger : public Trigger<> { } }; -} // namespace safe_mode -} // namespace esphome +} // namespace esphome::safe_mode + +#endif // USE_SAFE_MODE_CALLBACK diff --git a/esphome/components/safe_mode/safe_mode.cpp b/esphome/components/safe_mode/safe_mode.cpp index 62bbca4fb1..ef6ebea247 100644 --- a/esphome/components/safe_mode/safe_mode.cpp +++ b/esphome/components/safe_mode/safe_mode.cpp @@ -9,8 +9,11 @@ #include #include -namespace esphome { -namespace safe_mode { +#ifdef USE_OTA_ROLLBACK +#include +#endif + +namespace esphome::safe_mode { static const char *const TAG = "safe_mode"; @@ -32,6 +35,16 @@ void SafeModeComponent::dump_config() { ESP_LOGW(TAG, "SAFE MODE IS ACTIVE"); } } + +#ifdef USE_OTA_ROLLBACK + const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition(); + if (last_invalid != nullptr) { + ESP_LOGW(TAG, + "OTA rollback detected! Rolled back from partition '%s'\n" + "The device reset before the boot was marked successful", + last_invalid->label); + } +#endif } float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; } @@ -42,6 +55,10 @@ void SafeModeComponent::loop() { ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter"); this->clean_rtc(); this->boot_successful_ = true; +#ifdef USE_OTA_ROLLBACK + // Mark OTA partition as valid to prevent rollback + esp_ota_mark_app_valid_cancel_rollback(); +#endif // Disable loop since we no longer need to check this->disable_loop(); } @@ -108,7 +125,9 @@ bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t en ESP_LOGW(TAG, "SAFE MODE IS ACTIVE"); +#ifdef USE_SAFE_MODE_CALLBACK this->safe_mode_callback_.call(); +#endif return true; } @@ -125,12 +144,18 @@ uint32_t SafeModeComponent::read_rtc_() { return val; } -void SafeModeComponent::clean_rtc() { this->write_rtc_(0); } +void SafeModeComponent::clean_rtc() { + // Save without sync - preferences will be written at shutdown or by IntervalSyncer. + // This avoids blocking the loop for 50+ ms on flash write. If the device crashes + // before sync, the boot wasn't really successful anyway and the counter should + // remain incremented. + uint32_t val = 0; + this->rtc_.save(&val); +} void SafeModeComponent::on_safe_shutdown() { if (this->read_rtc_() != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) this->clean_rtc(); } -} // namespace safe_mode -} // namespace esphome +} // namespace esphome::safe_mode diff --git a/esphome/components/safe_mode/safe_mode.h b/esphome/components/safe_mode/safe_mode.h index 028b7b11cb..4aefd11458 100644 --- a/esphome/components/safe_mode/safe_mode.h +++ b/esphome/components/safe_mode/safe_mode.h @@ -5,8 +5,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -namespace esphome { -namespace safe_mode { +namespace esphome::safe_mode { /// SafeModeComponent provides a safe way to recover from repeated boot failures class SafeModeComponent : public Component { @@ -25,9 +24,11 @@ class SafeModeComponent : public Component { void on_safe_shutdown() override; +#ifdef USE_SAFE_MODE_CALLBACK void add_on_safe_mode_callback(std::function &&callback) { this->safe_mode_callback_.add(std::move(callback)); } +#endif protected: void write_rtc_(uint32_t val); @@ -43,11 +44,12 @@ class SafeModeComponent : public Component { uint8_t safe_mode_num_attempts_{0}; // Larger objects at the end ESPPreferenceObject rtc_; +#ifdef USE_SAFE_MODE_CALLBACK CallbackManager safe_mode_callback_{}; +#endif static const uint32_t ENTER_SAFE_MODE_MAGIC = 0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot }; -} // namespace safe_mode -} // namespace esphome +} // namespace esphome::safe_mode diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp index c815c98419..b9ce1f9151 100644 --- a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -10,6 +10,9 @@ namespace seeed_mr60bha2 { static const char *const TAG = "seeed_mr60bha2"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MR60BHA2_MAX_LOG_BYTES = 64; + // Prints the component's configuration data. dump_config() prints all of the component's configuration // items in an easy-to-read format, including the configuration key-value pairs. void MR60BHA2Component::dump_config() { @@ -110,7 +113,10 @@ bool MR60BHA2Component::validate_message_() { if (at == 7) { if (!validate_checksum(data, 7, header_checksum)) { ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum); - ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data, 8)); return false; } return true; @@ -125,14 +131,22 @@ bool MR60BHA2Component::validate_message_() { if (at == 8 + length) { if (!validate_checksum(data + 8, length, data_checksum)) { ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum); - ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; +#endif + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data, 8 + length)); return false; } } const uint8_t *frame_data = data + 8; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf1[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; + char hex_buf2[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)]; +#endif ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type, - format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str()); + format_hex_pretty_to(hex_buf1, sizeof(hex_buf1), frame_data, length), + format_hex_pretty_to(hex_buf2, sizeof(hex_buf2), this->rx_message_.data(), this->rx_message_.size())); this->process_frame_(frame_id, frame_type, data + 8, length); // Return false to reset rx buffer diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp index 7f8bd6a43c..b5b5b4d05a 100644 --- a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -10,6 +10,9 @@ namespace seeed_mr60fda2 { static const char *const TAG = "seeed_mr60fda2"; +// Maximum bytes to log in verbose hex output +static constexpr size_t MR60FDA2_MAX_LOG_BYTES = 64; + // Prints the component's configuration data. dump_config() prints all of the component's configuration // items in an easy-to-read format, including the configuration key-value pairs. void MR60FDA2Component::dump_config() { @@ -202,9 +205,13 @@ void MR60FDA2Component::split_frame_(uint8_t buffer) { this->current_frame_locate_++; } else { ESP_LOGD(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", buffer); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char frame_buf[format_hex_pretty_size(MR60FDA2_MAX_LOG_BYTES)]; + char byte_buf[format_hex_pretty_size(1)]; +#endif ESP_LOGV(TAG, "CURRENT_FRAME: %s %s", - format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), - format_hex_pretty(&buffer, 1).c_str()); + format_hex_pretty_to(frame_buf, this->current_frame_buf_, this->current_frame_len_), + format_hex_pretty_to(byte_buf, &buffer, 1)); this->current_frame_locate_ = LOCATE_FRAME_HEADER; } break; @@ -228,9 +235,13 @@ void MR60FDA2Component::split_frame_(uint8_t buffer) { this->process_frame_(); } else { ESP_LOGD(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", buffer); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char frame_buf[format_hex_pretty_size(MR60FDA2_MAX_LOG_BYTES)]; + char byte_buf[format_hex_pretty_size(1)]; +#endif ESP_LOGV(TAG, "GET CURRENT_FRAME: %s %s", - format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), - format_hex_pretty(&buffer, 1).c_str()); + format_hex_pretty_to(frame_buf, this->current_frame_buf_, this->current_frame_len_), + format_hex_pretty_to(byte_buf, &buffer, 1)); this->current_frame_locate_ = LOCATE_FRAME_HEADER; } @@ -328,7 +339,10 @@ void MR60FDA2Component::set_install_height(uint8_t index) { float_to_bytes(INSTALL_HEIGHT[index], &send_data[8]); send_data[12] = calculate_checksum(send_data + 8, 4); this->write_array(send_data, 13); - ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty(send_data, 13).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(13)]; +#endif + ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty_to(hex_buf, send_data, 13)); } void MR60FDA2Component::set_height_threshold(uint8_t index) { @@ -336,7 +350,10 @@ void MR60FDA2Component::set_height_threshold(uint8_t index) { float_to_bytes(HEIGHT_THRESHOLD[index], &send_data[8]); send_data[12] = calculate_checksum(send_data + 8, 4); this->write_array(send_data, 13); - ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty(send_data, 13).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(13)]; +#endif + ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty_to(hex_buf, send_data, 13)); } void MR60FDA2Component::set_sensitivity(uint8_t index) { @@ -346,19 +363,28 @@ void MR60FDA2Component::set_sensitivity(uint8_t index) { send_data[12] = calculate_checksum(send_data + 8, 4); this->write_array(send_data, 13); - ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty(send_data, 13).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(13)]; +#endif + ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty_to(hex_buf, send_data, 13)); } void MR60FDA2Component::get_radar_parameters() { uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x06, 0xF6}; this->write_array(send_data, 8); - ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty(send_data, 8).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(8)]; +#endif + ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty_to(hex_buf, send_data, 8)); } void MR60FDA2Component::factory_reset() { uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x21, 0x10, 0xCF}; this->write_array(send_data, 8); - ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty(send_data, 8).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(8)]; +#endif + ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty_to(hex_buf, send_data, 8)); this->get_radar_parameters(); } diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 768f2621f7..dda5403557 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -8,9 +8,13 @@ namespace esphome::select { class SelectStateTrigger : public Trigger { public: - explicit SelectStateTrigger(Select *parent) { - parent->add_on_state_callback([this](const std::string &value, size_t index) { this->trigger(value, index); }); + explicit SelectStateTrigger(Select *parent) : parent_(parent) { + parent->add_on_state_callback( + [this](size_t index) { this->trigger(std::string(this->parent_->option_at(index)), index); }); } + + protected: + Select *parent_; }; template class SelectSetAction : public Action { diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 4fc4d79b08..3d70e94d47 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -32,16 +32,17 @@ void Select::publish_state(size_t index) { this->state = option; // Update deprecated member for backward compatibility #pragma GCC diagnostic pop ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index); - // Callback signature requires std::string, create temporary for compatibility - this->state_callback_.call(std::string(option), index); + this->state_callback_.call(index); #if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_select_update(this); #endif } -const char *Select::current_option() const { return this->has_state() ? this->option_at(this->active_index_) : ""; } +StringRef Select::current_option() const { + return this->has_state() ? StringRef(this->option_at(this->active_index_)) : StringRef(); +} -void Select::add_on_state_callback(std::function &&callback) { +void Select::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 63707f6bd6..8b05487704 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include "select_call.h" #include "select_traits.h" @@ -33,8 +34,8 @@ class Select : public EntityBase { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - /// @deprecated Use current_option() instead. This member will be removed in ESPHome 2026.5.0. - ESPDEPRECATED("Use current_option() instead of .state. Will be removed in 2026.5.0", "2025.11.0") + /// @deprecated Use current_option() instead. This member will be removed in ESPHome 2026.7.0. + ESPDEPRECATED("Use current_option() instead of .state. Will be removed in 2026.7.0", "2026.1.0") std::string state{}; Select() = default; @@ -45,8 +46,10 @@ class Select : public EntityBase { void publish_state(const char *state); void publish_state(size_t index); - /// Return the currently selected option (as const char* from flash). - const char *current_option() const; + /// Return the currently selected option, or empty StringRef if no state. + /// The returned StringRef points to string literals from codegen (static storage). + /// Traits are set once at startup and valid for the lifetime of the program. + StringRef current_option() const; /// Instantiate a SelectCall object to modify this select component's state. SelectCall make_call() { return SelectCall(this); } @@ -75,7 +78,7 @@ class Select : public EntityBase { /// Return the option value at the provided index offset (as const char* from flash). const char *option_at(size_t index) const; - void add_on_state_callback(std::function &&callback); + void add_on_state_callback(std::function &&callback); protected: friend class SelectCall; @@ -111,7 +114,7 @@ class Select : public EntityBase { } } - CallbackManager state_callback_; + LazyCallbackManager state_callback_; }; } // namespace esphome::select diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index ffb9e2bc02..d5c9dfa3ae 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -1,4 +1,5 @@ #include "sen5x.h" +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -154,10 +155,10 @@ void SEN5XComponent::setup() { if (this->voc_sensor_ && this->store_baseline_) { uint32_t combined_serial = encode_uint24(this->serial_number_[0], this->serial_number_[1], this->serial_number_[2]); - // Hash with compilation time and serial number + // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA - // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(combined_serial)); + // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), combined_serial); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 9668a253c0..9c3114b9e2 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -4,17 +4,28 @@ import esphome.codegen as cg from esphome.components import i2c, sensirion_common, sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ALGORITHM_TUNING, CONF_GAIN_FACTOR, + CONF_GATING_MAX_DURATION_MINUTES, CONF_HUMIDITY, CONF_ID, + CONF_INDEX_OFFSET, + CONF_LEARNING_TIME_GAIN_HOURS, + CONF_LEARNING_TIME_OFFSET_HOURS, + CONF_NORMALIZED_OFFSET_SLOPE, + CONF_NOX, CONF_OFFSET, CONF_PM_1_0, CONF_PM_2_5, CONF_PM_4_0, CONF_PM_10_0, + CONF_STD_INITIAL, CONF_STORE_BASELINE, CONF_TEMPERATURE, CONF_TEMPERATURE_COMPENSATION, + CONF_TIME_CONSTANT, + CONF_VOC, + CONF_VOC_BASELINE, DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PM1, @@ -42,18 +53,7 @@ SEN5XComponent = sen5x_ns.class_( RhtAccelerationMode = sen5x_ns.enum("RhtAccelerationMode") CONF_ACCELERATION_MODE = "acceleration_mode" -CONF_ALGORITHM_TUNING = "algorithm_tuning" CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval" -CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" -CONF_INDEX_OFFSET = "index_offset" -CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" -CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" -CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" -CONF_NOX = "nox" -CONF_STD_INITIAL = "std_initial" -CONF_TIME_CONSTANT = "time_constant" -CONF_VOC = "voc" -CONF_VOC_BASELINE = "voc_baseline" # Actions diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 027d9a69b8..2ac45a55ac 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -3,7 +3,7 @@ import math from esphome import automation import esphome.codegen as cg -from esphome.components import mqtt, web_server +from esphome.components import mqtt, web_server, zigbee import esphome.config_validation as cv from esphome.const import ( CONF_ABOVE, @@ -295,6 +295,7 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") _SENSOR_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend(zigbee.SENSOR_SCHEMA) .extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), @@ -304,9 +305,6 @@ _SENSOR_SCHEMA = ( cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category, - cv.Optional("last_reset_type"): cv.invalid( - "last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values." - ), cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), @@ -338,6 +336,7 @@ _SENSOR_SCHEMA = ( ) _SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor")) +_SENSOR_SCHEMA.add_extra(zigbee.validate_sensor) def sensor_schema( @@ -921,6 +920,8 @@ async def setup_sensor_core_(var, config): if web_server_config := config.get(CONF_WEB_SERVER): await web_server.add_entity_config(var, web_server_config) + await zigbee.setup_sensor(var, config) + async def register_sensor(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/sensor/automation.cpp b/esphome/components/sensor/automation.cpp index f53c43d1f6..977719db9b 100644 --- a/esphome/components/sensor/automation.cpp +++ b/esphome/components/sensor/automation.cpp @@ -1,10 +1,8 @@ #include "automation.h" #include "esphome/core/log.h" -namespace esphome { -namespace sensor { +namespace esphome::sensor { static const char *const TAG = "sensor.automation"; -} // namespace sensor -} // namespace esphome +} // namespace esphome::sensor diff --git a/esphome/components/sensor/automation.h b/esphome/components/sensor/automation.h index df7d31a0c9..996c7fc9b5 100644 --- a/esphome/components/sensor/automation.h +++ b/esphome/components/sensor/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/automation.h" #include "esphome/components/sensor/sensor.h" -namespace esphome { -namespace sensor { +namespace esphome::sensor { class SensorStateTrigger : public Trigger { public: @@ -107,5 +106,4 @@ template class SensorInRangeCondition : public Condition float max_{NAN}; }; -} // namespace sensor -} // namespace esphome +} // namespace esphome::sensor diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index c8c6540112..8450ec4c4e 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -5,8 +5,7 @@ #include "esphome/core/log.h" #include "sensor.h" -namespace esphome { -namespace sensor { +namespace esphome::sensor { static const char *const TAG = "sensor.filter"; @@ -574,5 +573,4 @@ void StreamingMovingAverageFilter::reset_batch() { this->valid_count_ = 0; } -} // namespace sensor -} // namespace esphome +} // namespace esphome::sensor diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 92a9184c18..15c7656a7b 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -7,8 +7,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" -namespace esphome { -namespace sensor { +namespace esphome::sensor { class Sensor; @@ -632,5 +631,4 @@ class StreamingMovingAverageFilter : public StreamingFilter { size_t valid_count_{0}; }; -} // namespace sensor -} // namespace esphome +} // namespace esphome::sensor diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 49dc56edaa..64678f8d0c 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -3,8 +3,7 @@ #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" -namespace esphome { -namespace sensor { +namespace esphome::sensor { static const char *const TAG = "sensor"; @@ -76,9 +75,7 @@ StateClass Sensor::get_state_class() { void Sensor::publish_state(float state) { this->raw_state = state; - if (this->raw_callback_) { - this->raw_callback_->call(state); - } + this->raw_callback_.call(state); ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state); @@ -91,10 +88,7 @@ void Sensor::publish_state(float state) { void Sensor::add_on_state_callback(std::function &&callback) { this->callback_.add(std::move(callback)); } void Sensor::add_on_raw_state_callback(std::function &&callback) { - if (!this->raw_callback_) { - this->raw_callback_ = make_unique>(); - } - this->raw_callback_->add(std::move(callback)); + this->raw_callback_.add(std::move(callback)); } void Sensor::add_filter(Filter *filter) { @@ -140,5 +134,4 @@ void Sensor::internal_send_state_to_frontend(float state) { #endif } -} // namespace sensor -} // namespace esphome +} // namespace esphome::sensor diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 5d387a1ad7..d9046020f6 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -9,8 +9,7 @@ #include #include -namespace esphome { -namespace sensor { +namespace esphome::sensor { void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *obj); @@ -125,8 +124,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa void internal_send_state_to_frontend(float state); protected: - std::unique_ptr> raw_callback_; ///< Storage for raw state callbacks (lazy allocated). - CallbackManager callback_; ///< Storage for filtered state callbacks. + LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. + LazyCallbackManager callback_; ///< Storage for filtered state callbacks. Filter *filter_list_{nullptr}; ///< Store all active filters. @@ -143,5 +142,4 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa } sensor_flags_{}; }; -} // namespace sensor -} // namespace esphome +} // namespace esphome::sensor diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index fa548ce94e..18814405d4 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -72,10 +72,10 @@ void SGP30Component::setup() { return; } - // Hash with compilation time and serial number + // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA - // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); + // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) { diff --git a/esphome/components/sgp4x/sensor.py b/esphome/components/sgp4x/sensor.py index 7c6fe580b2..ab78ab59d9 100644 --- a/esphome/components/sgp4x/sensor.py +++ b/esphome/components/sgp4x/sensor.py @@ -2,11 +2,20 @@ import esphome.codegen as cg from esphome.components import i2c, sensirion_common, sensor import esphome.config_validation as cv from esphome.const import ( + CONF_ALGORITHM_TUNING, CONF_COMPENSATION, CONF_GAIN_FACTOR, + CONF_GATING_MAX_DURATION_MINUTES, CONF_ID, + CONF_INDEX_OFFSET, + CONF_LEARNING_TIME_GAIN_HOURS, + CONF_LEARNING_TIME_OFFSET_HOURS, + CONF_NOX, + CONF_STD_INITIAL, CONF_STORE_BASELINE, CONF_TEMPERATURE_SOURCE, + CONF_VOC, + CONF_VOC_BASELINE, DEVICE_CLASS_AQI, ICON_RADIATOR, STATE_CLASS_MEASUREMENT, @@ -24,16 +33,7 @@ SGP4xComponent = sgp4x_ns.class_( sensirion_common.SensirionI2CDevice, ) -CONF_ALGORITHM_TUNING = "algorithm_tuning" -CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" CONF_HUMIDITY_SOURCE = "humidity_source" -CONF_INDEX_OFFSET = "index_offset" -CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" -CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" -CONF_NOX = "nox" -CONF_STD_INITIAL = "std_initial" -CONF_VOC = "voc" -CONF_VOC_BASELINE = "voc_baseline" def validate_sensors(config): diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index a0c957d608..23589265ca 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -1,4 +1,5 @@ #include "sgp4x.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" #include @@ -56,10 +57,10 @@ void SGP4xComponent::setup() { ESP_LOGD(TAG, "Version 0x%0X", featureset); if (this->store_baseline_) { - // Hash with compilation time and serial number + // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA - // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); + // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/sha256/__init__.py b/esphome/components/sha256/__init__.py index f07157416d..5db0e77b76 100644 --- a/esphome/components/sha256/__init__.py +++ b/esphome/components/sha256/__init__.py @@ -12,6 +12,8 @@ CONFIG_SCHEMA = cv.Schema({}) async def to_code(config: ConfigType) -> None: + cg.add_define("USE_SHA256") + # Add OpenSSL library for host platform if not CORE.is_host: return diff --git a/esphome/components/sha256/sha256.cpp b/esphome/components/sha256/sha256.cpp index 32abbd739d..48559d7c73 100644 --- a/esphome/components/sha256/sha256.cpp +++ b/esphome/components/sha256/sha256.cpp @@ -10,23 +10,26 @@ namespace esphome::sha256 { #if defined(USE_ESP32) || defined(USE_LIBRETINY) -// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS: +// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x): // // The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains -// internal state that the DMA engine references. This imposes two critical constraints: +// internal state that the DMA engine references. This imposes three critical constraints: // -// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to +// 1. ALIGNMENT: The SHA256 object MUST be declared with `alignas(32)` for proper DMA alignment. +// Without this, the DMA engine may crash with an abort in sha_hal_read_digest(). +// +// 2. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to // write to incorrect memory locations. This results in null pointer dereferences and crashes. // ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]). // -// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same +// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same // function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack // frame changes (function call/return), the DMA references become invalid and will produce // truncated hash output (20 bytes instead of 32) or corrupt memory. // // CORRECT USAGE: // void my_function() { -// sha256::SHA256 hasher; // Created locally +// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment // hasher.init(); // hasher.add(data, len); // Any size, no chunking needed // hasher.calculate(); @@ -36,7 +39,7 @@ namespace esphome::sha256 { // // INCORRECT USAGE (WILL FAIL ON ESP32-S3): // void my_function() { -// sha256::SHA256 hasher; +// sha256::SHA256 hasher; // WRONG: Missing alignas(32) // helper(&hasher); // WRONG: Passed to different stack frame // } // void helper(HashBase *h) { diff --git a/esphome/components/sha256/sha256.h b/esphome/components/sha256/sha256.h index a2b62799e1..17d80636f1 100644 --- a/esphome/components/sha256/sha256.h +++ b/esphome/components/sha256/sha256.h @@ -22,6 +22,18 @@ namespace esphome::sha256 { +/// SHA256 hash implementation. +/// +/// CRITICAL for ESP32-S3 with IDF 5.5.x hardware SHA acceleration: +/// 1. SHA256 objects MUST be declared with `alignas(32)` for proper DMA alignment +/// 2. The object MUST stay in the same stack frame (no passing to other functions) +/// 3. NO Variable Length Arrays (VLAs) in the same function +/// +/// Example usage: +/// alignas(32) sha256::SHA256 hasher; +/// hasher.init(); +/// hasher.add(data, len); +/// hasher.calculate(); class SHA256 : public esphome::HashBase { public: SHA256() = default; @@ -39,10 +51,8 @@ class SHA256 : public esphome::HashBase { protected: #if defined(USE_ESP32) || defined(USE_LIBRETINY) - // CRITICAL: The mbedtls context MUST be stack-allocated (not a pointer) for ESP32-S3 hardware SHA acceleration. - // The ESP32-S3 DMA engine references this structure's memory addresses. If the context is passed to another - // function (crossing stack frames) or if VLAs are present, the DMA operations will corrupt memory and produce - // truncated/incorrect hash results. + // The mbedtls context for ESP32-S3 hardware SHA requires proper alignment and stack frame constraints. + // See class documentation above for critical requirements. mbedtls_sha256_context ctx_{}; #elif defined(USE_ESP8266) || defined(USE_RP2040) br_sha256_context ctx_{}; diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index b336bbcb65..bdb33d31af 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -113,26 +113,20 @@ void ShellyDimmer::setup() { void ShellyDimmer::update() { this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0); } void ShellyDimmer::dump_config() { - ESP_LOGCONFIG(TAG, "ShellyDimmer:"); - LOG_PIN(" NRST Pin: ", this->pin_nrst_); - LOG_PIN(" BOOT0 Pin: ", this->pin_boot0_); - ESP_LOGCONFIG(TAG, + "ShellyDimmer:\n" " Leading Edge: %s\n" " Warmup Brightness: %d\n" " Minimum Brightness: %d\n" - " Maximum Brightness: %d", - YESNO(this->leading_edge_), this->warmup_brightness_, this->min_brightness_, this->max_brightness_); - // ESP_LOGCONFIG(TAG, " Warmup Time: %d", this->warmup_time_); - // ESP_LOGCONFIG(TAG, " Fade Rate: %d", this->fade_rate_); - - LOG_UPDATE_INTERVAL(this); - - ESP_LOGCONFIG(TAG, - " STM32 current firmware version: %d.%d \n" + " Maximum Brightness: %d\n" + " STM32 current firmware version: %d.%d\n" " STM32 required firmware version: %d.%d", + YESNO(this->leading_edge_), this->warmup_brightness_, this->min_brightness_, this->max_brightness_, this->version_major_, this->version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION); + LOG_PIN(" NRST Pin: ", this->pin_nrst_); + LOG_PIN(" BOOT0 Pin: ", this->pin_boot0_); + LOG_UPDATE_INTERVAL(this); if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION || this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) { @@ -270,7 +264,10 @@ void ShellyDimmer::send_settings_() { } bool ShellyDimmer::send_command_(uint8_t cmd, const uint8_t *const payload, uint8_t len) { - ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, format_hex(payload, len).c_str()); + // Buffer for hex formatting: max frame size * 2 + null (covers any payload) + char hex_buf[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE * 2 + 1]; + ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, + format_hex_to(hex_buf, sizeof(hex_buf), payload, len)); // Prepare a command frame. uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE]; @@ -436,13 +433,15 @@ bool ShellyDimmer::handle_frame_() { current = CURRENT_SCALING_FACTOR / static_cast(current_raw); } - ESP_LOGI(TAG, "Got dimmer data:"); - ESP_LOGI(TAG, " HW version: %d", hw_version); - ESP_LOGI(TAG, " Brightness: %d", brightness); - ESP_LOGI(TAG, " Fade rate: %d", fade_rate); - ESP_LOGI(TAG, " Power: %f W", power); - ESP_LOGI(TAG, " Voltage: %f V", voltage); - ESP_LOGI(TAG, " Current: %f A", current); + ESP_LOGI(TAG, + "Got dimmer data:\n" + " HW version: %d\n" + " Brightness: %d\n" + " Fade rate: %d\n" + " Power: %f W\n" + " Voltage: %f V\n" + " Current: %f A", + hw_version, brightness, fade_rate, power, voltage, current); // Update sensors. if (this->power_sensor_ != nullptr) { diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 79f1674020..d473df43c7 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -60,8 +60,10 @@ void SHT3XDComponent::dump_config() { ESP_LOGE(TAG, " Communication with SHT3xD failed!"); return; } - ESP_LOGD(TAG, " Serial Number: 0x%08" PRIX32, this->serial_number_); - ESP_LOGD(TAG, " Heater Enabled: %s", this->heater_enabled_ ? "true" : "false"); + ESP_LOGD(TAG, + " Serial Number: 0x%08" PRIX32 "\n" + " Heater Enabled: %s", + this->serial_number_, TRUEFALSE(this->heater_enabled_)); LOG_I2C_DEVICE(this); LOG_UPDATE_INTERVAL(this); diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index d532bd7f44..933dd9bde9 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -49,8 +49,10 @@ void SHTCXComponent::setup() { } void SHTCXComponent::dump_config() { - ESP_LOGCONFIG(TAG, "SHTCx:"); - ESP_LOGCONFIG(TAG, " Model: %s (%04x)", to_string(this->type_), this->sensor_id_); + ESP_LOGCONFIG(TAG, + "SHTCx:\n" + " Model: %s (%04x)", + to_string(this->type_), this->sensor_id_); LOG_I2C_DEVICE(this); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index 55cadcf182..e3edda0e72 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -323,8 +323,10 @@ void Sim800LComponent::parse_cmd_(std::string message) { kick ESPHome callback now */ if (ok || message.compare(0, 6, "+CMGL:") == 0) { - ESP_LOGD(TAG, "Received SMS from: %s", this->sender_.c_str()); - ESP_LOGD(TAG, "%s", this->message_.c_str()); + ESP_LOGD(TAG, + "Received SMS from: %s\n" + "%s", + this->sender_.c_str(), this->message_.c_str()); this->sms_received_callback_.call(this->message_, this->sender_); this->state_ = STATE_RECEIVED_SMS; } else { diff --git a/esphome/components/sm16716/sm16716.cpp b/esphome/components/sm16716/sm16716.cpp index aa33b7b679..b8e293929b 100644 --- a/esphome/components/sm16716/sm16716.cpp +++ b/esphome/components/sm16716/sm16716.cpp @@ -14,11 +14,13 @@ void SM16716::setup() { this->pwm_amounts_.resize(this->num_channels_, 0); } void SM16716::dump_config() { - ESP_LOGCONFIG(TAG, "SM16716:"); + ESP_LOGCONFIG(TAG, + "SM16716:\n" + " Total number of channels: %u\n" + " Number of chips: %u", + this->num_channels_, this->num_chips_); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); - ESP_LOGCONFIG(TAG, " Total number of channels: %u", this->num_channels_); - ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); } void SM16716::loop() { if (!this->update_) diff --git a/esphome/components/sm2135/sm2135.cpp b/esphome/components/sm2135/sm2135.cpp index e55f836929..1293c3f321 100644 --- a/esphome/components/sm2135/sm2135.cpp +++ b/esphome/components/sm2135/sm2135.cpp @@ -34,11 +34,13 @@ void SM2135::setup() { } void SM2135::dump_config() { - ESP_LOGCONFIG(TAG, "SM2135:"); + ESP_LOGCONFIG(TAG, + "SM2135:\n" + " CW Current: %dmA\n" + " RGB Current: %dmA", + 10 + (this->cw_current_ * 5), 10 + (this->rgb_current_ * 5)); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); - ESP_LOGCONFIG(TAG, " CW Current: %dmA", 10 + (this->cw_current_ * 5)); - ESP_LOGCONFIG(TAG, " RGB Current: %dmA", 10 + (this->rgb_current_ * 5)); } void SM2135::write_byte_(uint8_t data) { diff --git a/esphome/components/sm2235/sm2235.cpp b/esphome/components/sm2235/sm2235.cpp index 820fcb521a..4476862318 100644 --- a/esphome/components/sm2235/sm2235.cpp +++ b/esphome/components/sm2235/sm2235.cpp @@ -15,13 +15,13 @@ void SM2235::setup() { } void SM2235::dump_config() { - ESP_LOGCONFIG(TAG, "sm2235:"); - LOG_PIN(" Data Pin: ", this->data_pin_); - LOG_PIN(" Clock Pin: ", this->clock_pin_); ESP_LOGCONFIG(TAG, + "SM2235:\n" " Color Channels Max Power: %u\n" " White Channels Max Power: %u", this->max_power_color_channels_, this->max_power_white_channels_); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); } } // namespace sm2235 diff --git a/esphome/components/sm2335/sm2335.cpp b/esphome/components/sm2335/sm2335.cpp index 0580a782f5..f860517021 100644 --- a/esphome/components/sm2335/sm2335.cpp +++ b/esphome/components/sm2335/sm2335.cpp @@ -15,13 +15,13 @@ void SM2335::setup() { } void SM2335::dump_config() { - ESP_LOGCONFIG(TAG, "sm2335:"); - LOG_PIN(" Data Pin: ", this->data_pin_); - LOG_PIN(" Clock Pin: ", this->clock_pin_); ESP_LOGCONFIG(TAG, + "sm2335:\n" " Color Channels Max Power: %u\n" " White Channels Max Power: %u", this->max_power_color_channels_, this->max_power_white_channels_); + LOG_PIN(" Data Pin: ", this->data_pin_); + LOG_PIN(" Clock Pin: ", this->clock_pin_); } } // namespace sm2335 diff --git a/esphome/components/sml/__init__.py b/esphome/components/sml/__init__.py index 936efd8561..eaeddce390 100644 --- a/esphome/components/sml/__init__.py +++ b/esphome/components/sml/__init__.py @@ -4,7 +4,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_DATA, CONF_TRIGGER_ID CODEOWNERS = ["@alengwenus"] @@ -17,7 +17,6 @@ MULTI_CONF = True CONF_SML_ID = "sml_id" CONF_OBIS_CODE = "obis_code" CONF_SERVER_ID = "server_id" -CONF_ON_DATA = "on_data" sml_ns = cg.esphome_ns.namespace("sml") diff --git a/esphome/components/sml/text_sensor/sml_text_sensor.cpp b/esphome/components/sml/text_sensor/sml_text_sensor.cpp index 64f10698f0..17b93ecccf 100644 --- a/esphome/components/sml/text_sensor/sml_text_sensor.cpp +++ b/esphome/components/sml/text_sensor/sml_text_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "sml_text_sensor.h" #include "../sml_parser.h" +#include namespace esphome { namespace sml { @@ -21,22 +22,31 @@ void SmlTextSensor::publish_val(const ObisInfo &obis_info) { switch (value_type) { case SML_HEX: { - publish_state("0x" + bytes_repr(obis_info.value)); + // Buffer for "0x" + up to 32 bytes as hex + null + char buf[67]; + // Max 32 bytes of data fit in buffer ((67-3)/2) + size_t hex_bytes = std::min(obis_info.value.size(), size_t(32)); + format_hex_prefixed_to(buf, obis_info.value.begin(), hex_bytes); + publish_state(buf, 2 + hex_bytes * 2); break; } case SML_INT: { - publish_state(to_string(bytes_to_int(obis_info.value))); + char buf[21]; // Enough for int64_t (-9223372036854775808) + int len = snprintf(buf, sizeof(buf), "%" PRId64, bytes_to_int(obis_info.value)); + publish_state(buf, static_cast(len)); break; } case SML_BOOL: publish_state(bytes_to_uint(obis_info.value) ? "True" : "False"); break; case SML_UINT: { - publish_state(to_string(bytes_to_uint(obis_info.value))); + char buf[21]; // Enough for uint64_t (18446744073709551615) + int len = snprintf(buf, sizeof(buf), "%" PRIu64, bytes_to_uint(obis_info.value)); + publish_state(buf, static_cast(len)); break; } case SML_OCTET: { - publish_state(std::string(obis_info.value.begin(), obis_info.value.end())); + publish_state(reinterpret_cast(obis_info.value.begin()), obis_info.value.size()); break; } } diff --git a/esphome/components/sn74hc165/sn74hc165.cpp b/esphome/components/sn74hc165/sn74hc165.cpp index 416d9db293..718e0b86ed 100644 --- a/esphome/components/sn74hc165/sn74hc165.cpp +++ b/esphome/components/sn74hc165/sn74hc165.cpp @@ -64,7 +64,9 @@ float SN74HC165Component::get_setup_priority() const { return setup_priority::IO bool SN74HC165GPIOPin::digital_read() { return this->parent_->digital_read_(this->pin_) != this->inverted_; } -std::string SN74HC165GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC165", 18, pin_); } +size_t SN74HC165GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via SN74HC165", this->pin_); +} } // namespace sn74hc165 } // namespace esphome diff --git a/esphome/components/sn74hc165/sn74hc165.h b/esphome/components/sn74hc165/sn74hc165.h index 4684844687..5a3f3fe8ef 100644 --- a/esphome/components/sn74hc165/sn74hc165.h +++ b/esphome/components/sn74hc165/sn74hc165.h @@ -47,7 +47,7 @@ class SN74HC165GPIOPin : public GPIOPin, public Parented { void pin_mode(gpio::Flags flags) override {} bool digital_read() override; void digital_write(bool value) override{}; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_pin(uint16_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index a9ada432e4..6b5c5d9fc4 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -93,7 +93,9 @@ float SN74HC595Component::get_setup_priority() const { return setup_priority::IO void SN74HC595GPIOPin::digital_write(bool value) { this->parent_->digital_write_(this->pin_, value != this->inverted_); } -std::string SN74HC595GPIOPin::dump_summary() const { return str_snprintf("%u via SN74HC595", 18, pin_); } +size_t SN74HC595GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via SN74HC595", this->pin_); +} } // namespace sn74hc595 } // namespace esphome diff --git a/esphome/components/sn74hc595/sn74hc595.h b/esphome/components/sn74hc595/sn74hc595.h index 181015b1e6..1cf70c86b5 100644 --- a/esphome/components/sn74hc595/sn74hc595.h +++ b/esphome/components/sn74hc595/sn74hc595.h @@ -54,7 +54,7 @@ class SN74HC595GPIOPin : public GPIOPin, public Parented { void pin_mode(gpio::Flags flags) override {} bool digital_read() override { return false; } void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_pin(uint16_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index c7cca62027..b670b9c068 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -12,43 +12,18 @@ #include #endif -namespace esphome { -namespace socket { +namespace esphome::socket { -std::string format_sockaddr(const struct sockaddr_storage &storage) { - if (storage.ss_family == AF_INET) { - const struct sockaddr_in *addr = reinterpret_cast(&storage); - char buf[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) != nullptr) - return std::string{buf}; - } -#if LWIP_IPV6 - else if (storage.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr = reinterpret_cast(&storage); - char buf[INET6_ADDRSTRLEN]; - // Format IPv4-mapped IPv6 addresses as regular IPv4 addresses - if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 && - addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) && - inet_ntop(AF_INET, &addr->sin6_addr.un.u32_addr[3], buf, sizeof(buf)) != nullptr) { - return std::string{buf}; - } - if (inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)) != nullptr) - return std::string{buf}; - } -#endif - return {}; -} - -class BSDSocketImpl : public Socket { +class BSDSocketImpl final : public Socket { public: BSDSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT // Register new socket with the application for select() if monitoring requested - if (monitor_loop && fd_ >= 0) { + if (monitor_loop && this->fd_ >= 0) { // Only set loop_monitored_ to true if registration succeeds - loop_monitored_ = App.register_socket_fd(fd_); + this->loop_monitored_ = App.register_socket_fd(this->fd_); } else { - loop_monitored_ = false; + this->loop_monitored_ = false; } #else // Without select support, ignore monitor_loop parameter @@ -56,70 +31,61 @@ class BSDSocketImpl : public Socket { #endif } ~BSDSocketImpl() override { - if (!closed_) { - close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) + if (!this->closed_) { + this->close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) } } - int connect(const struct sockaddr *addr, socklen_t addrlen) override { return ::connect(fd_, addr, addrlen); } + int connect(const struct sockaddr *addr, socklen_t addrlen) override { return ::connect(this->fd_, addr, addrlen); } std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, false); - } - std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, true); - } - - private: - std::unique_ptr accept_impl_(struct sockaddr *addr, socklen_t *addrlen, bool loop_monitored) { - int fd = ::accept(fd_, addr, addrlen); + int fd = ::accept(this->fd_, addr, addrlen); if (fd == -1) return {}; - return make_unique(fd, loop_monitored); + return make_unique(fd, false); + } + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { + int fd = ::accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, true); } - public: - int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); } + int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(this->fd_, addr, addrlen); } int close() override { - if (!closed_) { + if (!this->closed_) { #ifdef USE_SOCKET_SELECT_SUPPORT // Unregister from select() before closing if monitored - if (loop_monitored_) { - App.unregister_socket_fd(fd_); + if (this->loop_monitored_) { + App.unregister_socket_fd(this->fd_); } #endif - int ret = ::close(fd_); - closed_ = true; + int ret = ::close(this->fd_); + this->closed_ = true; return ret; } return 0; } - int shutdown(int how) override { return ::shutdown(fd_, how); } + int shutdown(int how) override { return ::shutdown(this->fd_, how); } - int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return ::getpeername(fd_, addr, addrlen); } - std::string getpeername() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - int err = this->getpeername((struct sockaddr *) &storage, &len); - if (err != 0) - return {}; - return format_sockaddr(storage); + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { + return ::getpeername(this->fd_, addr, addrlen); } - int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return ::getsockname(fd_, addr, addrlen); } - std::string getsockname() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - int err = this->getsockname((struct sockaddr *) &storage, &len); - if (err != 0) - return {}; - return format_sockaddr(storage); + int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { + return ::getsockname(this->fd_, addr, addrlen); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { - return ::getsockopt(fd_, level, optname, optval, optlen); + return ::getsockopt(this->fd_, level, optname, optval, optlen); } int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { - return ::setsockopt(fd_, level, optname, optval, optlen); + return ::setsockopt(this->fd_, level, optname, optval, optlen); + } + int listen(int backlog) override { return ::listen(this->fd_, backlog); } + ssize_t read(void *buf, size_t len) override { +#ifdef USE_ESP32 + return ::lwip_read(this->fd_, buf, len); +#else + return ::read(this->fd_, buf, len); +#endif } - int listen(int backlog) override { return ::listen(fd_, backlog); } - ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { #if defined(USE_ESP32) || defined(USE_HOST) return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len); @@ -129,41 +95,58 @@ class BSDSocketImpl : public Socket { } ssize_t readv(const struct iovec *iov, int iovcnt) override { #if defined(USE_ESP32) - return ::lwip_readv(fd_, iov, iovcnt); + return ::lwip_readv(this->fd_, iov, iovcnt); #else - return ::readv(fd_, iov, iovcnt); + return ::readv(this->fd_, iov, iovcnt); #endif } - ssize_t write(const void *buf, size_t len) override { return ::write(fd_, buf, len); } - ssize_t send(void *buf, size_t len, int flags) { return ::send(fd_, buf, len, flags); } + ssize_t write(const void *buf, size_t len) override { +#ifdef USE_ESP32 + return ::lwip_write(this->fd_, buf, len); +#else + return ::write(this->fd_, buf, len); +#endif + } + ssize_t send(void *buf, size_t len, int flags) { return ::send(this->fd_, buf, len, flags); } ssize_t writev(const struct iovec *iov, int iovcnt) override { #if defined(USE_ESP32) - return ::lwip_writev(fd_, iov, iovcnt); + return ::lwip_writev(this->fd_, iov, iovcnt); #else - return ::writev(fd_, iov, iovcnt); + return ::writev(this->fd_, iov, iovcnt); #endif } ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { - return ::sendto(fd_, buf, len, flags, to, tolen); // NOLINT(readability-suspicious-call-argument) + return ::sendto(this->fd_, buf, len, flags, to, tolen); // NOLINT(readability-suspicious-call-argument) } int setblocking(bool blocking) override { - int fl = ::fcntl(fd_, F_GETFL, 0); + int fl = ::fcntl(this->fd_, F_GETFL, 0); if (blocking) { fl &= ~O_NONBLOCK; } else { fl |= O_NONBLOCK; } - ::fcntl(fd_, F_SETFL, fl); + ::fcntl(this->fd_, F_SETFL, fl); return 0; } - int get_fd() const override { return fd_; } + int get_fd() const override { return this->fd_; } + +#ifdef USE_SOCKET_SELECT_SUPPORT + bool ready() const override { + if (!this->loop_monitored_) + return true; + return App.is_socket_ready(this->fd_); + } +#endif protected: int fd_; - bool closed_ = false; + bool closed_{false}; +#ifdef USE_SOCKET_SELECT_SUPPORT + bool loop_monitored_{false}; +#endif }; // Helper to create a socket with optional monitoring @@ -182,7 +165,6 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return create_socket(domain, type, protocol, true); } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif // USE_SOCKET_IMPL_BSD_SOCKETS diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 328df24bdd..429f59ceca 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -18,8 +18,7 @@ #include // For esp_schedule() #endif -namespace esphome { -namespace socket { +namespace esphome::socket { #ifdef USE_ESP8266 // Flag to signal socket activity - checked by socket_delay() to exit early @@ -72,7 +71,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return nullptr; } - int bind(const struct sockaddr *name, socklen_t addrlen) override { + int bind(const struct sockaddr *name, socklen_t addrlen) final { if (pcb_ == nullptr) { errno = EBADF; return -1; @@ -136,7 +135,7 @@ class LWIPRawImpl : public Socket { } return 0; } - int close() override { + int close() final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -153,7 +152,7 @@ class LWIPRawImpl : public Socket { pcb_ = nullptr; return 0; } - int shutdown(int how) override { + int shutdown(int how) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -179,7 +178,7 @@ class LWIPRawImpl : public Socket { return 0; } - int getpeername(struct sockaddr *name, socklen_t *addrlen) override { + int getpeername(struct sockaddr *name, socklen_t *addrlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -190,14 +189,7 @@ class LWIPRawImpl : public Socket { } return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen); } - std::string getpeername() override { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return ""; - } - return this->format_ip_address_(pcb_->remote_ip); - } - int getsockname(struct sockaddr *name, socklen_t *addrlen) override { + int getsockname(struct sockaddr *name, socklen_t *addrlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -208,14 +200,7 @@ class LWIPRawImpl : public Socket { } return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); } - std::string getsockname() override { - if (pcb_ == nullptr) { - errno = ECONNRESET; - return ""; - } - return this->format_ip_address_(pcb_->local_ip); - } - int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { + int getsockopt(int level, int optname, void *optval, socklen_t *optlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -249,7 +234,7 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { + int setsockopt(int level, int optname, const void *optval, socklen_t optlen) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -283,7 +268,7 @@ class LWIPRawImpl : public Socket { errno = EOPNOTSUPP; return -1; } - ssize_t read(void *buf, size_t len) override { + ssize_t read(void *buf, size_t len) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -341,7 +326,7 @@ class LWIPRawImpl : public Socket { return read; } - ssize_t readv(const struct iovec *iov, int iovcnt) override { + ssize_t readv(const struct iovec *iov, int iovcnt) final { ssize_t ret = 0; for (int i = 0; i < iovcnt; i++) { ssize_t err = read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); @@ -359,7 +344,7 @@ class LWIPRawImpl : public Socket { return ret; } - ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) final { errno = ENOTSUP; return -1; } @@ -413,7 +398,7 @@ class LWIPRawImpl : public Socket { } return 0; } - ssize_t write(const void *buf, size_t len) override { + ssize_t write(const void *buf, size_t len) final { ssize_t written = internal_write(buf, len); if (written == -1) return -1; @@ -428,7 +413,7 @@ class LWIPRawImpl : public Socket { } return written; } - ssize_t writev(const struct iovec *iov, int iovcnt) override { + ssize_t writev(const struct iovec *iov, int iovcnt) final { ssize_t written = 0; for (int i = 0; i < iovcnt; i++) { ssize_t err = internal_write(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); @@ -454,12 +439,12 @@ class LWIPRawImpl : public Socket { } return written; } - ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { + ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) final { // return ::sendto(fd_, buf, len, flags, to, tolen); errno = ENOSYS; return -1; } - int setblocking(bool blocking) override { + int setblocking(bool blocking) final { if (pcb_ == nullptr) { errno = ECONNRESET; return -1; @@ -518,19 +503,6 @@ class LWIPRawImpl : public Socket { } protected: - std::string format_ip_address_(const ip_addr_t &ip) { - char buffer[50] = {}; - if (IP_IS_V4_VAL(ip)) { - inet_ntoa_r(ip, buffer, sizeof(buffer)); - } -#if LWIP_IPV6 - else if (IP_IS_V6_VAL(ip)) { - inet6_ntoa_r(ip, buffer, sizeof(buffer)); - } -#endif - return std::string(buffer); - } - int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { if (family_ == AF_INET) { if (*addrlen < sizeof(struct sockaddr_in)) { @@ -585,7 +557,7 @@ class LWIPRawImpl : public Socket { // Listening socket class - only allocates accept queue when needed (for bind+listen sockets) // This saves 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) for regular connected sockets on ESP8266/RP2040 -class LWIPRawListenImpl : public LWIPRawImpl { +class LWIPRawListenImpl final : public LWIPRawImpl { public: LWIPRawListenImpl(sa_family_t family, struct tcp_pcb *pcb) : LWIPRawImpl(family, pcb) {} @@ -711,7 +683,6 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return socket(domain, type, protocol); } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif // USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/components/socket/lwip_sockets_impl.cpp b/esphome/components/socket/lwip_sockets_impl.cpp index d94c1fb2ff..a885f243f3 100644 --- a/esphome/components/socket/lwip_sockets_impl.cpp +++ b/esphome/components/socket/lwip_sockets_impl.cpp @@ -7,41 +7,18 @@ #include #include "esphome/core/application.h" -namespace esphome { -namespace socket { +namespace esphome::socket { -std::string format_sockaddr(const struct sockaddr_storage &storage) { - if (storage.ss_family == AF_INET) { - const struct sockaddr_in *addr = reinterpret_cast(&storage); - char buf[INET_ADDRSTRLEN]; - const char *ret = lwip_inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)); - if (ret == nullptr) - return {}; - return std::string{buf}; - } -#if LWIP_IPV6 - else if (storage.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr = reinterpret_cast(&storage); - char buf[INET6_ADDRSTRLEN]; - const char *ret = lwip_inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)); - if (ret == nullptr) - return {}; - return std::string{buf}; - } -#endif - return {}; -} - -class LwIPSocketImpl : public Socket { +class LwIPSocketImpl final : public Socket { public: LwIPSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) { #ifdef USE_SOCKET_SELECT_SUPPORT // Register new socket with the application for select() if monitoring requested - if (monitor_loop && fd_ >= 0) { + if (monitor_loop && this->fd_ >= 0) { // Only set loop_monitored_ to true if registration succeeds - loop_monitored_ = App.register_socket_fd(fd_); + this->loop_monitored_ = App.register_socket_fd(this->fd_); } else { - loop_monitored_ = false; + this->loop_monitored_ = false; } #else // Without select support, ignore monitor_loop parameter @@ -49,96 +26,94 @@ class LwIPSocketImpl : public Socket { #endif } ~LwIPSocketImpl() override { - if (!closed_) { - close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) + if (!this->closed_) { + this->close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) } } - int connect(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_connect(fd_, addr, addrlen); } + int connect(const struct sockaddr *addr, socklen_t addrlen) override { + return lwip_connect(this->fd_, addr, addrlen); + } std::unique_ptr accept(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, false); - } - std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { - return accept_impl_(addr, addrlen, true); - } - - private: - std::unique_ptr accept_impl_(struct sockaddr *addr, socklen_t *addrlen, bool loop_monitored) { - int fd = lwip_accept(fd_, addr, addrlen); + int fd = lwip_accept(this->fd_, addr, addrlen); if (fd == -1) return {}; - return make_unique(fd, loop_monitored); + return make_unique(fd, false); + } + std::unique_ptr accept_loop_monitored(struct sockaddr *addr, socklen_t *addrlen) override { + int fd = lwip_accept(this->fd_, addr, addrlen); + if (fd == -1) + return {}; + return make_unique(fd, true); } - public: - int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(fd_, addr, addrlen); } + int bind(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_bind(this->fd_, addr, addrlen); } int close() override { - if (!closed_) { + if (!this->closed_) { #ifdef USE_SOCKET_SELECT_SUPPORT // Unregister from select() before closing if monitored - if (loop_monitored_) { - App.unregister_socket_fd(fd_); + if (this->loop_monitored_) { + App.unregister_socket_fd(this->fd_); } #endif - int ret = lwip_close(fd_); - closed_ = true; + int ret = lwip_close(this->fd_); + this->closed_ = true; return ret; } return 0; } - int shutdown(int how) override { return lwip_shutdown(fd_, how); } + int shutdown(int how) override { return lwip_shutdown(this->fd_, how); } - int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getpeername(fd_, addr, addrlen); } - std::string getpeername() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - int err = this->getpeername((struct sockaddr *) &storage, &len); - if (err != 0) - return {}; - return format_sockaddr(storage); + int getpeername(struct sockaddr *addr, socklen_t *addrlen) override { + return lwip_getpeername(this->fd_, addr, addrlen); } - int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { return lwip_getsockname(fd_, addr, addrlen); } - std::string getsockname() override { - struct sockaddr_storage storage; - socklen_t len = sizeof(storage); - int err = this->getsockname((struct sockaddr *) &storage, &len); - if (err != 0) - return {}; - return format_sockaddr(storage); + int getsockname(struct sockaddr *addr, socklen_t *addrlen) override { + return lwip_getsockname(this->fd_, addr, addrlen); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { - return lwip_getsockopt(fd_, level, optname, optval, optlen); + return lwip_getsockopt(this->fd_, level, optname, optval, optlen); } int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override { - return lwip_setsockopt(fd_, level, optname, optval, optlen); + return lwip_setsockopt(this->fd_, level, optname, optval, optlen); } - int listen(int backlog) override { return lwip_listen(fd_, backlog); } - ssize_t read(void *buf, size_t len) override { return lwip_read(fd_, buf, len); } + int listen(int backlog) override { return lwip_listen(this->fd_, backlog); } + ssize_t read(void *buf, size_t len) override { return lwip_read(this->fd_, buf, len); } ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { - return lwip_recvfrom(fd_, buf, len, 0, addr, addr_len); + return lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); } - ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(fd_, iov, iovcnt); } - ssize_t write(const void *buf, size_t len) override { return lwip_write(fd_, buf, len); } - ssize_t send(void *buf, size_t len, int flags) { return lwip_send(fd_, buf, len, flags); } - ssize_t writev(const struct iovec *iov, int iovcnt) override { return lwip_writev(fd_, iov, iovcnt); } + ssize_t readv(const struct iovec *iov, int iovcnt) override { return lwip_readv(this->fd_, iov, iovcnt); } + ssize_t write(const void *buf, size_t len) override { return lwip_write(this->fd_, buf, len); } + ssize_t send(void *buf, size_t len, int flags) { return lwip_send(this->fd_, buf, len, flags); } + ssize_t writev(const struct iovec *iov, int iovcnt) override { return lwip_writev(this->fd_, iov, iovcnt); } ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { - return lwip_sendto(fd_, buf, len, flags, to, tolen); + return lwip_sendto(this->fd_, buf, len, flags, to, tolen); } int setblocking(bool blocking) override { - int fl = lwip_fcntl(fd_, F_GETFL, 0); + int fl = lwip_fcntl(this->fd_, F_GETFL, 0); if (blocking) { fl &= ~O_NONBLOCK; } else { fl |= O_NONBLOCK; } - lwip_fcntl(fd_, F_SETFL, fl); + lwip_fcntl(this->fd_, F_SETFL, fl); return 0; } - int get_fd() const override { return fd_; } + int get_fd() const override { return this->fd_; } + +#ifdef USE_SOCKET_SELECT_SUPPORT + bool ready() const override { + if (!this->loop_monitored_) + return true; + return App.is_socket_ready(this->fd_); + } +#endif protected: int fd_; - bool closed_ = false; + bool closed_{false}; +#ifdef USE_SOCKET_SELECT_SUPPORT + bool loop_monitored_{false}; +#endif }; // Helper to create a socket with optional monitoring @@ -157,7 +132,6 @@ std::unique_ptr socket_loop_monitored(int domain, int type, int protocol return create_socket(domain, type, protocol, true); } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif // USE_SOCKET_IMPL_LWIP_SOCKETS diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp index cc9232d21a..c92e33393b 100644 --- a/esphome/components/socket/socket.cpp +++ b/esphome/components/socket/socket.cpp @@ -6,31 +6,89 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace socket { +namespace esphome::socket { Socket::~Socket() {} -bool Socket::ready() const { -#ifdef USE_SOCKET_SELECT_SUPPORT - if (!loop_monitored_) { - // Non-monitored sockets always return true (assume data may be available) - return true; - } - - // For loop-monitored sockets, check with the Application's select() results - int fd = this->get_fd(); - if (fd < 0) { - // No valid file descriptor, assume ready (fallback behavior) - return true; - } - - return App.is_socket_ready(fd); -#else - // Without select() support, we can't monitor sockets in the loop - // Always return true (assume data may be available) - return true; +// Platform-specific inet_ntop wrappers +#if defined(USE_SOCKET_IMPL_LWIP_TCP) +// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + inet_ntoa_r(*reinterpret_cast(addr), buf, size); + return buf; +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + inet6_ntoa_r(*reinterpret_cast(addr), buf, size); + return buf; +} #endif +#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS) +// LWIP sockets (LibreTiny, ESP32 Arduino) +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + return lwip_inet_ntop(AF_INET, addr, buf, size); +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + return lwip_inet_ntop(AF_INET6, addr, buf, size); +} +#endif +#else +// BSD sockets (host, ESP32-IDF) +static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) { + return inet_ntop(AF_INET, addr, buf, size); +} +#if USE_NETWORK_IPV6 +static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) { + return inet_ntop(AF_INET6, addr, buf, size); +} +#endif +#endif + +// Format sockaddr into caller-provided buffer, returns length written (excluding null) +static size_t format_sockaddr_to(const struct sockaddr_storage &storage, std::span buf) { + if (storage.ss_family == AF_INET) { + const auto *addr = reinterpret_cast(&storage); + if (esphome_inet_ntop4(&addr->sin_addr, buf.data(), buf.size()) != nullptr) + return strlen(buf.data()); + } +#if USE_NETWORK_IPV6 + else if (storage.ss_family == AF_INET6) { + const auto *addr = reinterpret_cast(&storage); +#ifndef USE_SOCKET_IMPL_LWIP_TCP + // Format IPv4-mapped IPv6 addresses as regular IPv4 (not supported on ESP8266 raw TCP) + if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 && + addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) && + esphome_inet_ntop4(&addr->sin6_addr.un.u32_addr[3], buf.data(), buf.size()) != nullptr) { + return strlen(buf.data()); + } +#endif + if (esphome_inet_ntop6(&addr->sin6_addr, buf.data(), buf.size()) != nullptr) + return strlen(buf.data()); + } +#endif + buf[0] = '\0'; + return 0; +} + +size_t Socket::getpeername_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getpeername(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(storage, buf); +} + +size_t Socket::getsockname_to(std::span buf) { + struct sockaddr_storage storage; + socklen_t len = sizeof(storage); + if (this->getsockname(reinterpret_cast(&storage), &len) != 0) { + buf[0] = '\0'; + return 0; + } + return format_sockaddr_to(storage, buf); } std::unique_ptr socket_ip(int type, int protocol) { @@ -113,6 +171,5 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po return sizeof(sockaddr_in); #endif /* USE_NETWORK_IPV6 */ } -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 8936b2cd10..9f9f61de85 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -1,13 +1,22 @@ #pragma once #include +#include #include #include "esphome/core/optional.h" #include "headers.h" #if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) -namespace esphome { -namespace socket { +namespace esphome::socket { + +// Maximum length for formatted socket address string (IP address without port) +// IPv4: "255.255.255.255" = 15 chars + null = 16 +// IPv6: full address = 45 chars + null = 46 +#if USE_NETWORK_IPV6 +static constexpr size_t SOCKADDR_STR_LEN = 46; // INET6_ADDRSTRLEN +#else +static constexpr size_t SOCKADDR_STR_LEN = 16; // INET_ADDRSTRLEN +#endif class Socket { public: @@ -32,9 +41,15 @@ class Socket { virtual int shutdown(int how) = 0; virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; - virtual std::string getpeername() = 0; virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0; - virtual std::string getsockname() = 0; + + /// Format peer address into a fixed-size buffer (no heap allocation) + /// Non-virtual wrapper around getpeername() - can be optimized away if unused + /// Returns number of characters written (excluding null terminator), or 0 on error + size_t getpeername_to(std::span buf); + /// Format local address into a fixed-size buffer (no heap allocation) + /// Non-virtual wrapper around getsockname() - can be optimized away if unused + size_t getsockname_to(std::span buf); virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0; virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; @@ -54,12 +69,7 @@ class Socket { /// Check if socket has data ready to read /// For loop-monitored sockets, checks with the Application's select() results /// For non-monitored sockets, always returns true (assumes data may be available) - bool ready() const; - - protected: -#ifdef USE_SOCKET_SELECT_SUPPORT - bool loop_monitored_{false}; ///< Whether this socket is monitored by the event loop -#endif + virtual bool ready() const { return true; } }; /// Create a socket of the given domain, type and protocol. @@ -91,6 +101,5 @@ void socket_delay(uint32_t ms); void socket_wake(); #endif -} // namespace socket -} // namespace esphome +} // namespace esphome::socket #endif diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp index cd09f31dd7..7b99086546 100644 --- a/esphome/components/sonoff_d1/sonoff_d1.cpp +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -42,12 +42,17 @@ * M 6C - CRC over bytes 2 to F (Addition) \*********************************************************************************************/ #include "sonoff_d1.h" +#include "esphome/core/helpers.h" namespace esphome { namespace sonoff_d1 { static const char *const TAG = "sonoff_d1"; +// Protocol constants +static constexpr size_t SONOFF_D1_ACK_SIZE = 7; +static constexpr size_t SONOFF_D1_MAX_CMD_SIZE = 17; + uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) { uint8_t crc = 0; for (size_t i = 2; i < len - 1; i++) { @@ -86,8 +91,13 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) { // Read a minimal packet if (this->read_array(cmd, 6)) { - ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_); - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(6)]; + ESP_LOGV(TAG, + "[%04d] Reading from dimmer:\n" + "[%04d] %s", + this->write_count_, this->write_count_, format_hex_pretty_to(hex_buf, cmd, 6)); +#endif if (cmd[0] != 0xAA || cmd[1] != 0x55) { ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]); @@ -101,7 +111,10 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) { return false; } if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) { - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf2[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)]; + ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf2, &cmd[6], cmd[5] + 1)); +#endif // Check the checksum uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7); @@ -145,9 +158,10 @@ bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) { ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_); return true; } else { + char hex_buf[format_hex_pretty_size(SONOFF_D1_ACK_SIZE)]; ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:", this->write_count_); - ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str()); + ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, ref_buffer, sizeof(ref_buffer))); } return false; } @@ -174,8 +188,13 @@ bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_a // 2. UART command initiated by this component can clash with a command initiated by RF uint32_t retries = 10; do { - ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_); - ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)]; + ESP_LOGV(TAG, + "[%04d] Writing to the dimmer:\n" + "[%04d] %s", + this->write_count_, this->write_count_, format_hex_pretty_to(hex_buf, cmd, len)); +#endif this->write_array(cmd, len); this->write_count_++; if (!needs_ack) diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 062bff92f8..370b4576a7 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -6,7 +6,7 @@ from pathlib import Path from esphome import automation, external_files import esphome.codegen as cg -from esphome.components import audio, esp32, media_player, network, psram, speaker +from esphome.components import audio, esp32, media_player, network, ota, psram, speaker import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, @@ -315,7 +315,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_VOLUME): automation.validate_automation(single=True), } ), - cv.only_with_esp_idf, + cv.only_on_esp32, _validate_repeated_speaker, _request_high_performance_networking, ) @@ -342,7 +342,7 @@ async def to_code(config): var = await media_player.new_media_player(config) await cg.register_component(var, config) - cg.add_define("USE_OTA_STATE_CALLBACK") + ota.request_ota_state_listeners() cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index dc8572ae43..8be37d740a 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -1,6 +1,6 @@ #include "audio_pipeline.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/defines.h" #include "esphome/core/hal.h" diff --git a/esphome/components/speaker/media_player/audio_pipeline.h b/esphome/components/speaker/media_player/audio_pipeline.h index 98f43fda6e..6fffde6c20 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.h +++ b/esphome/components/speaker/media_player/audio_pipeline.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio_reader.h" diff --git a/esphome/components/speaker/media_player/automation.h b/esphome/components/speaker/media_player/automation.h index fdf3db07f9..6270da7bd4 100644 --- a/esphome/components/speaker/media_player/automation.h +++ b/esphome/components/speaker/media_player/automation.h @@ -2,7 +2,7 @@ #include "speaker_media_player.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/audio/audio.h" #include "esphome/core/automation.h" diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index b45a78010a..9a3a47bac8 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -1,6 +1,6 @@ #include "speaker_media_player.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/log.h" @@ -66,25 +66,8 @@ void SpeakerMediaPlayer::setup() { this->set_mute_state_(false); } -#ifdef USE_OTA - ota::get_global_ota_callback()->add_on_state_callback( - [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { - if (state == ota::OTA_STARTED) { - if (this->media_pipeline_ != nullptr) { - this->media_pipeline_->suspend_tasks(); - } - if (this->announcement_pipeline_ != nullptr) { - this->announcement_pipeline_->suspend_tasks(); - } - } else if (state == ota::OTA_ERROR) { - if (this->media_pipeline_ != nullptr) { - this->media_pipeline_->resume_tasks(); - } - if (this->announcement_pipeline_ != nullptr) { - this->announcement_pipeline_->resume_tasks(); - } - } - }); +#ifdef USE_OTA_STATE_LISTENER + ota::get_global_ota_callback()->add_global_state_listener(this); #endif this->announcement_pipeline_ = @@ -300,6 +283,27 @@ void SpeakerMediaPlayer::watch_media_commands_() { } } +#ifdef USE_OTA_STATE_LISTENER +void SpeakerMediaPlayer::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, + ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { + if (this->media_pipeline_ != nullptr) { + this->media_pipeline_->suspend_tasks(); + } + if (this->announcement_pipeline_ != nullptr) { + this->announcement_pipeline_->suspend_tasks(); + } + } else if (state == ota::OTA_ERROR) { + if (this->media_pipeline_ != nullptr) { + this->media_pipeline_->resume_tasks(); + } + if (this->announcement_pipeline_ != nullptr) { + this->announcement_pipeline_->resume_tasks(); + } + } +} +#endif + void SpeakerMediaPlayer::loop() { this->watch_media_commands_(); diff --git a/esphome/components/speaker/media_player/speaker_media_player.h b/esphome/components/speaker/media_player/speaker_media_player.h index 967772d1a5..065926d0cf 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.h +++ b/esphome/components/speaker/media_player/speaker_media_player.h @@ -1,18 +1,22 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "audio_pipeline.h" #include "esphome/components/audio/audio.h" - #include "esphome/components/media_player/media_player.h" #include "esphome/components/speaker/speaker.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/preferences.h" +#ifdef USE_OTA_STATE_LISTENER +#include "esphome/components/ota/ota_backend.h" +#endif + #include #include #include @@ -39,12 +43,22 @@ struct VolumeRestoreState { bool is_muted; }; -class SpeakerMediaPlayer : public Component, public media_player::MediaPlayer { +class SpeakerMediaPlayer : public Component, + public media_player::MediaPlayer +#ifdef USE_OTA_STATE_LISTENER + , + public ota::OTAGlobalStateListener +#endif +{ public: float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } void setup() override; void loop() override; +#ifdef USE_OTA_STATE_LISTENER + void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; +#endif + // MediaPlayer implementations media_player::MediaPlayerTraits get_traits() override; bool is_muted() const override { return this->is_muted_; } diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 801593c2ac..af98e3a51f 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -29,7 +29,7 @@ void SpeedFan::control(const fan::FanCall &call) { this->oscillating = *call.get_oscillating(); if (call.get_direction().has_value()) this->direction = *call.get_direction(); - this->set_preset_mode_(call.get_preset_mode()); + this->apply_preset_mode_(call); this->write_state_(); this->publish_state(); diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 88bb3406e1..e890567abf 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -49,21 +49,60 @@ SPIDevice = spi_ns.class_("SPIDevice") SPIDataRate = spi_ns.enum("SPIDataRate") SPIMode = spi_ns.enum("SPIMode") -SPI_DATA_RATE_OPTIONS = { - 80e6: SPIDataRate.DATA_RATE_80MHZ, - 40e6: SPIDataRate.DATA_RATE_40MHZ, - 20e6: SPIDataRate.DATA_RATE_20MHZ, - 10e6: SPIDataRate.DATA_RATE_10MHZ, - 8e6: SPIDataRate.DATA_RATE_8MHZ, - 5e6: SPIDataRate.DATA_RATE_5MHZ, - 4e6: SPIDataRate.DATA_RATE_4MHZ, - 2e6: SPIDataRate.DATA_RATE_2MHZ, - 1e6: SPIDataRate.DATA_RATE_1MHZ, - 2e5: SPIDataRate.DATA_RATE_200KHZ, - 75e3: SPIDataRate.DATA_RATE_75KHZ, - 1e3: SPIDataRate.DATA_RATE_1KHZ, +PLATFORM_SPI_CLOCKS = { + PLATFORM_ESP8266: 40e6, + PLATFORM_ESP32: 80e6, + PLATFORM_RP2040: 62.5e6, } -SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS)) + +MAX_DATA_RATE_ERROR = 0.05 # Max allowable actual data rate difference from requested + + +def _render_hz(value: float) -> str: + """Render a frequency in Hz as a human-readable string using Hz, KHz or MHz. + + Examples: + 500 -> "500 Hz" + 1500 -> "1.5 kHz" + 2000000 -> "2 MHz" + """ + if value >= 1e6: + unit = "MHz" + num = value / 1e6 + elif value >= 1e3: + unit = "kHz" + num = value / 1e3 + else: + unit = "Hz" + num = value + + # Format with up to 2 decimal places, then strip unnecessary trailing zeros and dot + formatted = f"{int(num)}" if unit == "Hz" else f"{num:.2f}".rstrip("0").rstrip(".") + return formatted + unit + + +def _frequency_validator(value): + platform = get_target_platform() + frequency = PLATFORM_SPI_CLOCKS[platform] + value = cv.frequency(value) + if value > frequency: + raise cv.Invalid( + f"The configured SPI data rate ({_render_hz(value)}) exceeds the maximum for this platform ({_render_hz(frequency)})" + ) + if value < 1000: + raise cv.Invalid("The configured SPI data rate must be at least 1000Hz") + divisor = round(frequency / value) + actual = frequency / divisor + error = abs(actual - value) / value + if error > MAX_DATA_RATE_ERROR: + raise cv.Invalid( + f"The configured SPI data rate ({_render_hz(value)}) is not available for this chip - closest is {_render_hz(actual)}" + ) + return value + + +SPI_DATA_RATE_SCHEMA = _frequency_validator + SPI_MODE_OPTIONS = { "MODE0": SPIMode.MODE0, @@ -272,10 +311,11 @@ def validate_spi_config(config): # Given an SPI index, convert to a string that represents the C++ object for it. def get_spi_interface(index): - if CORE.using_esp_idf: + platform = get_target_platform() + if platform == PLATFORM_ESP32: + # ESP32 uses ESP-IDF SPI driver for both Arduino and IDF frameworks return ["SPI2_HOST", "SPI3_HOST"][index] # Arduino code follows - platform = get_target_platform() if platform == PLATFORM_RP2040: return ["&SPI", "&SPI1"][index] if index == 0: @@ -310,7 +350,7 @@ def spi_mode_schema(mode): if mode == TYPE_SINGLE: return SPI_SINGLE_SCHEMA pin_count = 4 if mode == TYPE_QUAD else 8 - onlys = [cv.only_on([PLATFORM_ESP32]), cv.only_with_esp_idf] + onlys = [cv.only_on([PLATFORM_ESP32])] if pin_count == 8: onlys.append( only_on_variant( @@ -356,7 +396,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(configs): cg.add_define("USE_SPI") cg.add_global(spi_ns.using) - if CORE.using_arduino: + if CORE.using_arduino and not CORE.is_esp32: cg.add_library("SPI", None) for spi in configs: var = cg.new_Pvariable(spi[CONF_ID]) @@ -392,19 +432,20 @@ def spi_device_schema( :param mode Choose single, quad or octal mode. :return: The SPI device schema, `extend` this in your config schema. """ - schema = { - cv.GenerateID(CONF_SPI_ID): cv.use_id(TYPE_CLASS[mode]), - cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA, - cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( - SPI_MODE_OPTIONS, upper=True - ), - cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_with_esp_idf), - } - if cs_pin_required: - schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema - else: - schema[cv.Optional(CONF_CS_PIN)] = pins.gpio_output_pin_schema - return cv.Schema(schema) + cs_pin_option = cv.Required if cs_pin_required else cv.Optional + return cv.Schema( + { + cv.GenerateID(CONF_SPI_ID): cv.use_id(TYPE_CLASS[mode]), + cv.Optional( + CONF_DATA_RATE, default=default_data_rate + ): SPI_DATA_RATE_SCHEMA, + cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( + SPI_MODE_OPTIONS, upper=True + ), + cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32), + cs_pin_option(CONF_CS_PIN): pins.gpio_output_pin_schema, + } + ) async def register_spi_device(var, config): @@ -447,13 +488,15 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: FILTER_SOURCE_FILES = filter_source_files_from_platform( { "spi_arduino.cpp": { - PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP8266_ARDUINO, PlatformFramework.RP2040_ARDUINO, PlatformFramework.BK72XX_ARDUINO, PlatformFramework.RTL87XX_ARDUINO, PlatformFramework.LN882X_ARDUINO, }, - "spi_esp_idf.cpp": {PlatformFramework.ESP32_IDF}, + "spi_esp_idf.cpp": { + PlatformFramework.ESP32_ARDUINO, + PlatformFramework.ESP32_IDF, + }, } ) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 00e9845a03..36344a6d38 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -2,8 +2,7 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace spi { +namespace esphome::spi { const char *const TAG = "spi"; @@ -65,9 +64,9 @@ void SPIComponent::setup() { void SPIComponent::dump_config() { ESP_LOGCONFIG(TAG, "SPI bus:"); - LOG_PIN(" CLK Pin: ", this->clk_pin_) - LOG_PIN(" SDI Pin: ", this->sdi_pin_) - LOG_PIN(" SDO Pin: ", this->sdo_pin_) + LOG_PIN(" CLK Pin: ", this->clk_pin_); + LOG_PIN(" SDI Pin: ", this->sdi_pin_); + LOG_PIN(" SDO Pin: ", this->sdo_pin_); for (size_t i = 0; i != this->data_pins_.size(); i++) { ESP_LOGCONFIG(TAG, " Data pin %u: GPIO%d", i, this->data_pins_[i]); } @@ -119,5 +118,4 @@ uint16_t SPIDelegateBitBash::transfer_(uint16_t data, size_t num_bits) { return out_data; } -} // namespace spi -} // namespace esphome +} // namespace esphome::spi diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 5bc80350da..e237cf44f4 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -7,7 +7,13 @@ #include #include -#ifdef USE_ARDUINO +#ifdef USE_ESP32 + +#include "driver/spi_master.h" + +using SPIInterface = spi_host_device_t; + +#elif defined(USE_ARDUINO) #include @@ -17,26 +23,16 @@ using SPIInterface = SPIClassRP2040 *; using SPIInterface = SPIClass *; #endif -#endif +#elif defined(CLANG_TIDY) -#ifdef USE_ESP_IDF +using SPIInterface = void *; // Stub for platforms without SPI (e.g., Zephyr) -#include "driver/spi_master.h" - -using SPIInterface = spi_host_device_t; - -#endif // USE_ESP_IDF - -#ifdef USE_ZEPHYR -// TODO supprse clang-tidy. Remove after SPI driver for nrf52 is added. -using SPIInterface = void *; -#endif +#endif // USE_ESP32 / USE_ARDUINO /** * Implementation of SPI Controller mode. */ -namespace esphome { -namespace spi { +namespace esphome::spi { /// The bit-order for SPI devices. This defines how the data read from and written to the device is interpreted. enum SPIBitOrder { @@ -124,7 +120,11 @@ class NullPin : public GPIOPin { void digital_write(bool value) override {} - std::string dump_summary() const override { return std::string(); } + size_t dump_summary(char *buffer, size_t len) const override { + if (len > 0) + buffer[0] = '\0'; + return 0; + } protected: static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -509,5 +509,4 @@ class SPIDevice : public SPIClient { template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } }; -} // namespace spi -} // namespace esphome +} // namespace esphome::spi diff --git a/esphome/components/spi/spi_arduino.cpp b/esphome/components/spi/spi_arduino.cpp index a34e3c3c82..4267fe63ce 100644 --- a/esphome/components/spi/spi_arduino.cpp +++ b/esphome/components/spi/spi_arduino.cpp @@ -1,9 +1,8 @@ #include "spi.h" #include -namespace esphome { -namespace spi { -#ifdef USE_ARDUINO +namespace esphome::spi { +#if defined(USE_ARDUINO) && !defined(USE_ESP32) static const char *const TAG = "spi-esp-arduino"; class SPIDelegateHw : public SPIDelegate { @@ -101,6 +100,5 @@ SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo return new SPIBusHw(clk, sdo, sdi, interface); } -#endif // USE_ARDUINO -} // namespace spi -} // namespace esphome +#endif // USE_ARDUINO && !USE_ESP32 +} // namespace esphome::spi diff --git a/esphome/components/spi/spi_esp_idf.cpp b/esphome/components/spi/spi_esp_idf.cpp index 549f516eb1..a1837fa58d 100644 --- a/esphome/components/spi/spi_esp_idf.cpp +++ b/esphome/components/spi/spi_esp_idf.cpp @@ -1,10 +1,9 @@ #include "spi.h" #include -namespace esphome { -namespace spi { +namespace esphome::spi { -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 static const char *const TAG = "spi-esp-idf"; static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API. @@ -266,6 +265,5 @@ SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo return new SPIBusHw(clk, sdo, sdi, interface, data_pins); } -#endif -} // namespace spi -} // namespace esphome +#endif // USE_ESP32 +} // namespace esphome::spi diff --git a/esphome/components/spi_device/spi_device.cpp b/esphome/components/spi_device/spi_device.cpp index dbfbc9eccb..4cc7286ba9 100644 --- a/esphome/components/spi_device/spi_device.cpp +++ b/esphome/components/spi_device/spi_device.cpp @@ -11,9 +11,11 @@ static const char *const TAG = "spi_device"; void SPIDeviceComponent::setup() { this->spi_setup(); } void SPIDeviceComponent::dump_config() { - ESP_LOGCONFIG(TAG, "SPIDevice"); + ESP_LOGCONFIG(TAG, + "SPIDevice\n" + " Mode: %d", + this->mode_); LOG_PIN(" CS pin: ", this->cs_); - ESP_LOGCONFIG(TAG, " Mode: %d", this->mode_); if (this->data_rate_ < 1000000) { ESP_LOGCONFIG(TAG, " Data rate: %" PRId32 "kHz", this->data_rate_ / 1000); } else { diff --git a/esphome/components/spi_led_strip/spi_led_strip.cpp b/esphome/components/spi_led_strip/spi_led_strip.cpp index 85c10ee87d..afb51afe3a 100644 --- a/esphome/components/spi_led_strip/spi_led_strip.cpp +++ b/esphome/components/spi_led_strip/spi_led_strip.cpp @@ -34,8 +34,10 @@ light::LightTraits SpiLedStrip::get_traits() { return traits; } void SpiLedStrip::dump_config() { - esph_log_config(TAG, "SPI LED Strip:"); - esph_log_config(TAG, " LEDs: %d", this->num_leds_); + esph_log_config(TAG, + "SPI LED Strip:\n" + " LEDs: %d", + this->num_leds_); if (this->data_rate_ >= spi::DATA_RATE_1MHZ) { esph_log_config(TAG, " Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000)); } else { diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index 2dccb6896a..50c69f9496 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( UNIT_MINUTE, UNIT_SECOND, ) +from esphome.helpers import docs_url AUTO_LOAD = ["number", "switch"] CODEOWNERS = ["@kbx81"] @@ -162,55 +163,9 @@ def validate_sprinkler(config): raise cv.Invalid( f"{CONF_RUN_DURATION} must be greater than {CONF_VALVE_OPEN_DELAY}" ) - if ( - CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID not in valve - ) or ( - CONF_PUMP_ON_SWITCH_ID in valve and CONF_PUMP_OFF_SWITCH_ID not in valve - ): + if CONF_VALVE_SWITCH_ID not in valve: raise cv.Invalid( - f"Both {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID} must be specified for latching pump configuration" - ) - if CONF_PUMP_SWITCH_ID in valve and ( - CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"Do not specify {CONF_PUMP_OFF_SWITCH_ID} or {CONF_PUMP_ON_SWITCH_ID} when using {CONF_PUMP_SWITCH_ID}" - ) - if CONF_PUMP_PULSE_DURATION not in sprinkler_controller and ( - CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"{CONF_PUMP_PULSE_DURATION} must be specified when using {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID}" - ) - if ( - CONF_VALVE_OFF_SWITCH_ID in valve - and CONF_VALVE_ON_SWITCH_ID not in valve - ) or ( - CONF_VALVE_ON_SWITCH_ID in valve - and CONF_VALVE_OFF_SWITCH_ID not in valve - ): - raise cv.Invalid( - f"Both {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified for latching valve configuration" - ) - if CONF_VALVE_SWITCH_ID in valve and ( - CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"Do not specify {CONF_VALVE_OFF_SWITCH_ID} or {CONF_VALVE_ON_SWITCH_ID} when using {CONF_VALVE_SWITCH_ID}" - ) - if CONF_VALVE_PULSE_DURATION not in sprinkler_controller and ( - CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve - ): - raise cv.Invalid( - f"{CONF_VALVE_PULSE_DURATION} must be specified when using {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID}" - ) - if ( - CONF_VALVE_SWITCH_ID not in valve - and CONF_VALVE_OFF_SWITCH_ID not in valve - and CONF_VALVE_ON_SWITCH_ID not in valve - ): - raise cv.Invalid( - f"Either {CONF_VALVE_SWITCH_ID} or {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified in valve configuration" + f"{CONF_VALVE_SWITCH_ID} must be specified in valve configuration" ) if CONF_RUN_DURATION not in valve and CONF_RUN_DURATION_NUMBER not in valve: raise cv.Invalid( @@ -290,8 +245,15 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( ), key=CONF_NAME, ), - cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.use_id(switch.Switch), - cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.use_id(switch.Switch), + # Removed latching pump keys - accepted for validation error reporting + cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), + cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), cv.Optional(CONF_PUMP_SWITCH_ID): cv.use_id(switch.Switch), cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_seconds, cv.Optional(CONF_RUN_DURATION_NUMBER): cv.maybe_simple_value( @@ -321,8 +283,15 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( switch.switch_schema(SprinklerControllerSwitch), key=CONF_NAME, ), - cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.use_id(switch.Switch), - cv.Optional(CONF_VALVE_ON_SWITCH_ID): cv.use_id(switch.Switch), + # Removed latching valve keys - accepted for validation error reporting + cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), + cv.Optional(CONF_VALVE_ON_SWITCH_ID): cv.invalid( + f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), cv.Optional(CONF_VALVE_SWITCH_ID): cv.use_id(switch.Switch), } ) @@ -410,8 +379,15 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( validate_min_max, key=CONF_NAME, ), - cv.Optional(CONF_PUMP_PULSE_DURATION): cv.positive_time_period_milliseconds, - cv.Optional(CONF_VALVE_PULSE_DURATION): cv.positive_time_period_milliseconds, + # Removed latching valve keys - accepted for validation error reporting + cv.Optional(CONF_PUMP_PULSE_DURATION): cv.invalid( + f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), + cv.Optional(CONF_VALVE_PULSE_DURATION): cv.invalid( + f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. " + f"See {docs_url('components/switch/h_bridge')} for more information" + ), cv.Exclusive( CONF_PUMP_START_PUMP_DELAY, "pump_start_xxxx_delay" ): cv.positive_time_period_seconds, @@ -765,35 +741,10 @@ async def to_code(config): valve_index, valve_switch, valve[CONF_RUN_DURATION] ) ) - elif CONF_VALVE_OFF_SWITCH_ID in valve and CONF_VALVE_ON_SWITCH_ID in valve: - valve_switch_off = await cg.get_variable( - valve[CONF_VALVE_OFF_SWITCH_ID] - ) - valve_switch_on = await cg.get_variable(valve[CONF_VALVE_ON_SWITCH_ID]) - cg.add( - var.configure_valve_switch_pulsed( - valve_index, - valve_switch_off, - valve_switch_on, - sprinkler_controller[CONF_VALVE_PULSE_DURATION], - valve[CONF_RUN_DURATION], - ) - ) if CONF_PUMP_SWITCH_ID in valve: pump = await cg.get_variable(valve[CONF_PUMP_SWITCH_ID]) cg.add(var.configure_valve_pump_switch(valve_index, pump)) - elif CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID in valve: - pump_off = await cg.get_variable(valve[CONF_PUMP_OFF_SWITCH_ID]) - pump_on = await cg.get_variable(valve[CONF_PUMP_ON_SWITCH_ID]) - cg.add( - var.configure_valve_pump_switch_pulsed( - valve_index, - pump_off, - pump_on, - sprinkler_controller[CONF_PUMP_PULSE_DURATION], - ) - ) if CONF_RUN_DURATION_NUMBER in valve: num_rd_var = await number.new_number( diff --git a/esphome/components/sprinkler/automation.h b/esphome/components/sprinkler/automation.h index d6c877ae90..b3f030805d 100644 --- a/esphome/components/sprinkler/automation.h +++ b/esphome/components/sprinkler/automation.h @@ -4,8 +4,7 @@ #include "esphome/core/component.h" #include "esphome/components/sprinkler/sprinkler.h" -namespace esphome { -namespace sprinkler { +namespace esphome::sprinkler { template class SetDividerAction : public Action { public: @@ -181,5 +180,4 @@ template class ResumeOrStartAction : public Action { Sprinkler *sprinkler_; }; -} // namespace sprinkler -} // namespace esphome +} // namespace esphome::sprinkler diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 8edb240a41..ca9f85abd8 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -7,75 +7,10 @@ #include #include -namespace esphome { -namespace sprinkler { +namespace esphome::sprinkler { static const char *const TAG = "sprinkler"; -SprinklerSwitch::SprinklerSwitch() {} -SprinklerSwitch::SprinklerSwitch(switch_::Switch *sprinkler_switch) : on_switch_(sprinkler_switch) {} -SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration) - : pulse_duration_(pulse_duration), off_switch_(off_switch), on_switch_(on_switch) {} - -bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); } - -void SprinklerSwitch::loop() { - if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) { - this->pinned_millis_ = 0; // reset tracker - if (this->off_switch_->state) { - this->off_switch_->turn_off(); - } - if (this->on_switch_->state) { - this->on_switch_->turn_off(); - } - } -} - -void SprinklerSwitch::turn_off() { - if (!this->state()) { // do nothing if we're already in the requested state - return; - } - if (this->off_switch_ != nullptr) { // latching valve, start a pulse - if (!this->off_switch_->state) { - this->off_switch_->turn_on(); - } - this->pinned_millis_ = millis(); - } else if (this->on_switch_ != nullptr) { // non-latching valve - this->on_switch_->turn_off(); - } - this->state_ = false; -} - -void SprinklerSwitch::turn_on() { - if (this->state()) { // do nothing if we're already in the requested state - return; - } - if (this->off_switch_ != nullptr) { // latching valve, start a pulse - if (!this->on_switch_->state) { - this->on_switch_->turn_on(); - } - this->pinned_millis_ = millis(); - } else if (this->on_switch_ != nullptr) { // non-latching valve - this->on_switch_->turn_on(); - } - this->state_ = true; -} - -bool SprinklerSwitch::state() { - if ((this->off_switch_ == nullptr) && (this->on_switch_ != nullptr)) { // latching valve is not configured... - return this->on_switch_->state; // ...so just return the pump switch state - } - return this->state_; -} - -void SprinklerSwitch::sync_valve_state(bool latch_state) { - if (this->is_latching_valve()) { - this->state_ = latch_state; - } else if (this->on_switch_ != nullptr) { - this->state_ = this->on_switch_->state; - } -} - void SprinklerControllerNumber::setup() { float value; if (!this->restore_value_) { @@ -220,8 +155,8 @@ void SprinklerValveOperator::start() { this->state_ = STARTING; // STARTING state requires both a pump and a start_delay_ if (this->start_delay_is_valve_delay_) { this->pump_on_(); - } else if (!this->pump_switch()->state()) { // if the pump is already on, wait to switch on the valve - this->valve_on_(); // to ensure consistent run time + } else if (!this->pump_switch()->state) { // if the pump is already on, wait to switch on the valve + this->valve_on_(); // to ensure consistent run time } } else { this->run_(); // there is no start_delay_, so just start the pump and valve @@ -241,8 +176,8 @@ void SprinklerValveOperator::stop() { } else { this->valve_off_(); } - if (this->pump_switch()->state()) { // if the pump is still on at this point, it may be in use... - this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time + if (this->pump_switch()->state) { // if the pump is still on at this point, it may be in use... + this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time } } else { this->kill_(); // there is no stop_delay_, so just stop the pump and valve @@ -275,7 +210,7 @@ uint32_t SprinklerValveOperator::time_remaining() { SprinklerState SprinklerValveOperator::state() { return this->state_; } -SprinklerSwitch *SprinklerValveOperator::pump_switch() { +switch_::Switch *SprinklerValveOperator::pump_switch() { if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) { return nullptr; } @@ -286,48 +221,50 @@ SprinklerSwitch *SprinklerValveOperator::pump_switch() { } void SprinklerValveOperator::pump_off_() { - if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first! + auto *pump = this->pump_switch(); + if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first! return; } if (this->controller_ == nullptr) { // safety first! - this->pump_switch()->turn_off(); // if no controller was set, just switch off the pump + pump->turn_off(); // if no controller was set, just switch off the pump } else { // ...otherwise, do it "safely" auto state = this->state_; // this is silly, but... this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does - this->controller_->set_pump_state(this->pump_switch(), false); + this->controller_->set_pump_state(pump, false); this->state_ = state; } } void SprinklerValveOperator::pump_on_() { - if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first! + auto *pump = this->pump_switch(); + if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first! return; } if (this->controller_ == nullptr) { // safety first! - this->pump_switch()->turn_on(); // if no controller was set, just switch on the pump + pump->turn_on(); // if no controller was set, just switch on the pump } else { // ...otherwise, do it "safely" auto state = this->state_; // this is silly, but... this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does - this->controller_->set_pump_state(this->pump_switch(), true); + this->controller_->set_pump_state(pump, true); this->state_ = state; } } void SprinklerValveOperator::valve_off_() { - if (this->valve_ == nullptr) { // safety first! + if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first! return; } - if (this->valve_->valve_switch.state()) { - this->valve_->valve_switch.turn_off(); + if (this->valve_->valve_switch->state) { + this->valve_->valve_switch->turn_off(); } } void SprinklerValveOperator::valve_on_() { - if (this->valve_ == nullptr) { // safety first! + if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first! return; } - if (!this->valve_->valve_switch.state()) { - this->valve_->valve_switch.turn_on(); + if (!this->valve_->valve_switch->state) { + this->valve_->valve_switch->turn_on(); } } @@ -402,16 +339,11 @@ Sprinkler::Sprinkler(const std::string &name) { void Sprinkler::setup() { this->all_valves_off_(true); } void Sprinkler::loop() { - for (auto &p : this->pump_) { - p.loop(); - } - for (auto &v : this->valve_) { - v.valve_switch.loop(); - } for (auto &vo : this->valve_op_) { vo.loop(); } - if (this->prev_req_.has_request() && this->prev_req_.valve_operator()->state() == IDLE) { + if (this->prev_req_.has_request() && this->prev_req_.has_valve_operator() && + this->prev_req_.valve_operator()->state() == IDLE) { this->prev_req_.reset(); } } @@ -423,10 +355,15 @@ void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControll new_valve->controller_switch = valve_sw; new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional { - if (this->valve_pump_switch(new_valve_number) != nullptr) { - return this->valve_switch(new_valve_number)->state() && this->valve_pump_switch(new_valve_number)->state(); + auto *valve = this->valve_switch(new_valve_number); + auto *pump = this->valve_pump_switch(new_valve_number); + if (valve == nullptr) { + return false; } - return this->valve_switch(new_valve_number)->state(); + if (pump != nullptr) { + return valve->state && pump->state; + } + return valve->state; }); new_valve->valve_turn_off_automation = @@ -496,18 +433,7 @@ void Sprinkler::set_controller_repeat_number(SprinklerControllerNumber *repeat_n void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) { if (this->is_a_valid_valve(valve_number)) { - this->valve_[valve_number].valve_switch.set_on_switch(valve_switch); - this->valve_[valve_number].run_duration = run_duration; - } -} - -void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off, - switch_::Switch *valve_switch_on, uint32_t pulse_duration, - uint32_t run_duration) { - if (this->is_a_valid_valve(valve_number)) { - this->valve_[valve_number].valve_switch.set_off_switch(valve_switch_off); - this->valve_[valve_number].valve_switch.set_on_switch(valve_switch_on); - this->valve_[valve_number].valve_switch.set_pulse_duration(pulse_duration); + this->valve_[valve_number].valve_switch = valve_switch; this->valve_[valve_number].run_duration = run_duration; } } @@ -515,31 +441,12 @@ void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Swit void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) { if (this->is_a_valid_valve(valve_number)) { for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump - if (this->pump_[i].on_switch() == pump_switch) { // if the "new" pump matches one we already have... - this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_... + if (this->pump_[i] == pump_switch) { // if the "new" pump matches one we already have... + this->valve_[valve_number].pump_switch_index = i; // ...save its index in the pump vector... return; // ...and we are done } - } // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it - this->pump_.resize(this->pump_.size() + 1); - this->pump_.back().set_on_switch(pump_switch); - this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump - } -} - -void Sprinkler::configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off, - switch_::Switch *pump_switch_on, uint32_t pulse_duration) { - if (this->is_a_valid_valve(valve_number)) { - for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump - if ((this->pump_[i].off_switch() == pump_switch_off) && - (this->pump_[i].on_switch() == pump_switch_on)) { // if the "new" pump matches one we already have... - this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_... - return; // ...and we are done - } - } // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it - this->pump_.resize(this->pump_.size() + 1); - this->pump_.back().set_off_switch(pump_switch_off); - this->pump_.back().set_on_switch(pump_switch_on); - this->pump_.back().set_pulse_duration(pulse_duration); + } // if we end up here, no pumps matched, so add a new one + this->pump_.push_back(pump_switch); this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump } } @@ -808,11 +715,11 @@ bool Sprinkler::standby() { void Sprinkler::start_from_queue() { if (this->standby()) { - ESP_LOGD(TAG, "start_from_queue called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("start_from_queue")); return; } if (this->multiplier() == 0) { - ESP_LOGD(TAG, "start_from_queue called but multiplier is set to zero; no action taken"); + this->log_multiplier_zero_warning_(LOG_STR("start_from_queue")); return; } if (this->queued_valves_.empty()) { @@ -832,11 +739,11 @@ void Sprinkler::start_from_queue() { void Sprinkler::start_full_cycle() { if (this->standby()) { - ESP_LOGD(TAG, "start_full_cycle called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("start_full_cycle")); return; } if (this->multiplier() == 0) { - ESP_LOGD(TAG, "start_full_cycle called but multiplier is set to zero; no action taken"); + this->log_multiplier_zero_warning_(LOG_STR("start_full_cycle")); return; } if (this->auto_advance() && this->active_valve().has_value()) { @@ -855,11 +762,11 @@ void Sprinkler::start_full_cycle() { void Sprinkler::start_single_valve(const optional valve_number, optional run_duration) { if (this->standby()) { - ESP_LOGD(TAG, "start_single_valve called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("start_single_valve")); return; } if (this->multiplier() == 0) { - ESP_LOGD(TAG, "start_single_valve called but multiplier is set to zero; no action taken"); + this->log_multiplier_zero_warning_(LOG_STR("start_single_valve")); return; } if (!valve_number.has_value() || (valve_number == this->active_valve())) { @@ -891,6 +798,11 @@ void Sprinkler::clear_queued_valves() { } void Sprinkler::next_valve() { + if (this->standby()) { + this->log_standby_warning_(LOG_STR("next_valve")); + return; + } + if (this->state_ == IDLE) { this->reset_cycle_states_(); // just in case auto-advance is switched on later } @@ -914,6 +826,11 @@ void Sprinkler::next_valve() { } void Sprinkler::previous_valve() { + if (this->standby()) { + this->log_standby_warning_(LOG_STR("previous_valve")); + return; + } + if (this->state_ == IDLE) { this->reset_cycle_states_(); // just in case auto-advance is switched on later } @@ -964,7 +881,7 @@ void Sprinkler::pause() { void Sprinkler::resume() { if (this->standby()) { - ESP_LOGD(TAG, "resume called but standby is enabled; no action taken"); + this->log_standby_warning_(LOG_STR("resume")); return; } @@ -1009,7 +926,7 @@ optional Sprinkler::active_valve_request_is_from } optional Sprinkler::active_valve() { - if (!this->valve_overlap_ && this->prev_req_.has_request() && + if (!this->valve_overlap_ && this->prev_req_.has_request() && this->prev_req_.has_valve_operator() && (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) { return this->prev_req_.valve_as_opt(); } @@ -1029,11 +946,9 @@ optional Sprinkler::manual_valve() { return this->manual_valve_; } size_t Sprinkler::number_of_valves() { return this->valve_.size(); } -bool Sprinkler::is_a_valid_valve(const size_t valve_number) { - return ((valve_number >= 0) && (valve_number < this->number_of_valves())); -} +bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); } -bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { +bool Sprinkler::pump_in_use(switch_::Switch *pump_switch) { if (pump_switch == nullptr) { return false; // we can't do anything if there's nothing to check } @@ -1046,8 +961,7 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) { // the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest - if ((vo.pump_switch()->off_switch() == pump_switch->off_switch()) && - (vo.pump_switch()->on_switch() == pump_switch->on_switch())) { + if (vo.pump_switch() == pump_switch) { // now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or // is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now if ((vo.state() == ACTIVE) || @@ -1062,13 +976,16 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) { this->active_req_.has_request() && (this->state_ != STOPPING)) { // ...the controller is configured to keep the pump on during a valve open delay, so just return // whether or not the next valve shares the same pump - return (pump_switch->off_switch() == this->valve_pump_switch(this->active_req_.valve())->off_switch()) && - (pump_switch->on_switch() == this->valve_pump_switch(this->active_req_.valve())->on_switch()); + auto *valve_pump = this->valve_pump_switch(this->active_req_.valve()); + if (valve_pump == nullptr) { + return false; // valve has no pump, so this pump isn't in use by it + } + return pump_switch == valve_pump; } return false; } -void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) { +void Sprinkler::set_pump_state(switch_::Switch *pump_switch, bool state) { if (pump_switch == nullptr) { return; // we can't do anything if there's nothing to check } @@ -1079,15 +996,10 @@ void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) { if (controller != this) { // dummy check if (controller->pump_in_use(pump_switch)) { hold_pump_on = true; // if another controller says it's using this pump, keep it on - // at this point we know if there exists another SprinklerSwitch that is "on" with its - // off_switch_ and on_switch_ pointers pointing to the same pair of switch objects } } } if (hold_pump_on) { - // at this point we know if there exists another SprinklerSwitch that is "on" with its - // off_switch_ and on_switch_ pointers pointing to the same pair of switch objects... - pump_switch->sync_valve_state(true); // ...so ensure our state is consistent ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it"); } @@ -1095,8 +1007,6 @@ void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) { pump_switch->turn_on(); } else if (!hold_pump_on && !this->pump_in_use(pump_switch)) { pump_switch->turn_off(); - } else if (hold_pump_on) { // we must assume the other controller will switch off the pump when done... - pump_switch->sync_valve_state(false); // ...this only impacts latching valves } } @@ -1262,23 +1172,23 @@ SprinklerControllerSwitch *Sprinkler::enable_switch(size_t valve_number) { return nullptr; } -SprinklerSwitch *Sprinkler::valve_switch(const size_t valve_number) { +switch_::Switch *Sprinkler::valve_switch(const size_t valve_number) { if (this->is_a_valid_valve(valve_number)) { - return &this->valve_[valve_number].valve_switch; + return this->valve_[valve_number].valve_switch; } return nullptr; } -SprinklerSwitch *Sprinkler::valve_pump_switch(const size_t valve_number) { +switch_::Switch *Sprinkler::valve_pump_switch(const size_t valve_number) { if (this->is_a_valid_valve(valve_number) && this->valve_[valve_number].pump_switch_index.has_value()) { - return &this->pump_[this->valve_[valve_number].pump_switch_index.value()]; + return this->pump_[this->valve_[valve_number].pump_switch_index.value()]; } return nullptr; } -SprinklerSwitch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) { +switch_::Switch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) { if (pump_index < this->pump_.size()) { - return &this->pump_[pump_index]; + return this->pump_[pump_index]; } return nullptr; } @@ -1426,8 +1336,8 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) { if (vo.state() == IDLE) { auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve()); ESP_LOGD(TAG, "%s is starting valve %zu for %" PRIu32 " seconds, cycle %" PRIu32 " of %" PRIu32, - this->req_as_str_(req->request_is_from()).c_str(), req->valve(), run_duration, this->repeat_count_ + 1, - this->repeat().value_or(0) + 1); + LOG_STR_ARG(this->req_as_str_(req->request_is_from())), req->valve(), run_duration, + this->repeat_count_ + 1, this->repeat().value_or(0) + 1); req->set_valve_operator(&vo); vo.set_controller(this); vo.set_valve(&this->valve_[req->valve()]); @@ -1442,8 +1352,9 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) { void Sprinkler::all_valves_off_(const bool include_pump) { for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) { - if (this->valve_[valve_index].valve_switch.state()) { - this->valve_[valve_index].valve_switch.turn_off(); + auto *valve_sw = this->valve_[valve_index].valve_switch; + if ((valve_sw != nullptr) && valve_sw->state) { + valve_sw->turn_off(); } if (include_pump) { this->set_pump_state(this->valve_pump_switch(valve_index), false); @@ -1488,7 +1399,7 @@ void Sprinkler::fsm_kick_() { } void Sprinkler::fsm_transition_() { - ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", this->state_as_str_(this->state_).c_str()); + ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", LOG_STR_ARG(this->state_as_str_(this->state_))); switch (this->state_) { case IDLE: // the system was off -> start it up // advances to ACTIVE @@ -1502,8 +1413,11 @@ void Sprinkler::fsm_transition_() { case STARTING: { // follows valve open delay interval - this->set_timer_duration_(sprinkler::TIMER_SM, - this->active_req_.run_duration() - this->switching_delay_.value_or(0)); + uint32_t timer_duration = this->active_req_.run_duration(); + if (timer_duration > this->switching_delay_.value_or(0)) { + timer_duration -= this->switching_delay_.value_or(0); + } + this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration); this->start_timer_(sprinkler::TIMER_SM); this->start_valve_(&this->active_req_); this->state_ = ACTIVE; @@ -1531,7 +1445,7 @@ void Sprinkler::fsm_transition_() { this->set_timer_duration_(sprinkler::TIMER_SM, this->manual_selection_delay_.value_or(1)); this->start_timer_(sprinkler::TIMER_SM); } - ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", this->state_as_str_(this->state_).c_str()); + ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", LOG_STR_ARG(this->state_as_str_(this->state_))); } void Sprinkler::fsm_transition_from_shutdown_() { @@ -1543,8 +1457,11 @@ void Sprinkler::fsm_transition_from_shutdown_() { this->active_req_.set_run_duration(this->next_req_.run_duration()); this->next_req_.reset(); - this->set_timer_duration_(sprinkler::TIMER_SM, - this->active_req_.run_duration() - this->switching_delay_.value_or(0)); + uint32_t timer_duration = this->active_req_.run_duration(); + if (timer_duration > this->switching_delay_.value_or(0)) { + timer_duration -= this->switching_delay_.value_or(0); + } + this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration); this->start_timer_(sprinkler::TIMER_SM); this->start_valve_(&this->active_req_); this->state_ = ACTIVE; @@ -1571,8 +1488,9 @@ void Sprinkler::fsm_transition_from_valve_run_() { this->load_next_valve_run_request_(this->active_req_.valve()); if (this->next_req_.has_request()) { // there is another valve to run... - bool same_pump = - this->valve_pump_switch(this->active_req_.valve()) == this->valve_pump_switch(this->next_req_.valve()); + auto *active_pump = this->valve_pump_switch(this->active_req_.valve()); + auto *next_pump = this->valve_pump_switch(this->next_req_.valve()); + bool same_pump = (active_pump != nullptr) && (next_pump != nullptr) && (active_pump == next_pump); this->active_req_.set_valve(this->next_req_.valve()); this->active_req_.set_request_from(this->next_req_.request_is_from()); @@ -1581,8 +1499,11 @@ void Sprinkler::fsm_transition_from_valve_run_() { // this->state_ = ACTIVE; // state isn't changing if (this->valve_overlap_ || !this->switching_delay_.has_value()) { - this->set_timer_duration_(sprinkler::TIMER_SM, - this->active_req_.run_duration() - this->switching_delay_.value_or(0)); + uint32_t timer_duration = this->active_req_.run_duration(); + if (timer_duration > this->switching_delay_.value_or(0)) { + timer_duration -= this->switching_delay_.value_or(0); + } + this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration); this->start_timer_(sprinkler::TIMER_SM); this->start_valve_(&this->active_req_); } else { @@ -1605,41 +1526,49 @@ void Sprinkler::fsm_transition_to_shutdown_() { this->start_timer_(sprinkler::TIMER_SM); } -std::string Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) { +void Sprinkler::log_standby_warning_(const LogString *method_name) { + ESP_LOGW(TAG, "%s called but standby is enabled; no action taken", LOG_STR_ARG(method_name)); +} + +void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) { + ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name)); +} + +const LogString *Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) { switch (origin) { case USER: - return "USER"; + return LOG_STR("USER"); case CYCLE: - return "CYCLE"; + return LOG_STR("CYCLE"); case QUEUE: - return "QUEUE"; + return LOG_STR("QUEUE"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } -std::string Sprinkler::state_as_str_(SprinklerState state) { +const LogString *Sprinkler::state_as_str_(SprinklerState state) { switch (state) { case IDLE: - return "IDLE"; + return LOG_STR("IDLE"); case STARTING: - return "STARTING"; + return LOG_STR("STARTING"); case ACTIVE: - return "ACTIVE"; + return LOG_STR("ACTIVE"); case STOPPING: - return "STOPPING"; + return LOG_STR("STOPPING"); case BYPASS: - return "BYPASS"; + return LOG_STR("BYPASS"); default: - return "UNKNOWN"; + return LOG_STR("UNKNOWN"); } } @@ -1724,10 +1653,6 @@ void Sprinkler::dump_config() { " Name: %s\n" " Run Duration: %" PRIu32 " seconds", valve_number, this->valve_name(valve_number), this->valve_run_duration(valve_number)); - if (this->valve_[valve_number].valve_switch.pulse_duration()) { - ESP_LOGCONFIG(TAG, " Pulse Duration: %" PRIu32 " milliseconds", - this->valve_[valve_number].valve_switch.pulse_duration()); - } } if (!this->pump_.empty()) { ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size()); @@ -1737,5 +1662,4 @@ void Sprinkler::dump_config() { } } -} // namespace sprinkler -} // namespace esphome +} // namespace esphome::sprinkler diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index c4a8b8aeb8..25e2d42446 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -8,8 +8,7 @@ #include -namespace esphome { -namespace sprinkler { +namespace esphome::sprinkler { const std::string MIN_STR = "min"; @@ -36,7 +35,6 @@ enum SprinklerValveRunRequestOrigin : uint8_t { class Sprinkler; // this component class SprinklerControllerNumber; // number components that appear in the front end; based on number core class SprinklerControllerSwitch; // switches that appear in the front end; based on switch core -class SprinklerSwitch; // switches representing any valve or pump; provides abstraction for latching valves class SprinklerValveOperator; // manages all switching on/off of valves and associated pumps class SprinklerValveRunRequest; // tells the sprinkler controller what valve to run and for how long as well as what // SprinklerValveOperator is handling it @@ -44,34 +42,6 @@ template class StartSingleValveAction; template class ShutdownAction; template class ResumeOrStartAction; -class SprinklerSwitch { - public: - SprinklerSwitch(); - SprinklerSwitch(switch_::Switch *sprinkler_switch); - SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration); - - bool is_latching_valve(); // returns true if configured as a latching valve - void loop(); // called as a part of loop(), used for latching valve pulses - uint32_t pulse_duration() { return this->pulse_duration_; } - bool state(); // returns the switch's current state - void set_off_switch(switch_::Switch *off_switch) { this->off_switch_ = off_switch; } - void set_on_switch(switch_::Switch *on_switch) { this->on_switch_ = on_switch; } - void set_pulse_duration(uint32_t pulse_duration) { this->pulse_duration_ = pulse_duration; } - void sync_valve_state( - bool latch_state); // syncs internal state to switch; if latching valve, sets state to latch_state - void turn_off(); // sets internal flag and actuates the switch - void turn_on(); // sets internal flag and actuates the switch - switch_::Switch *off_switch() { return this->off_switch_; } - switch_::Switch *on_switch() { return this->on_switch_; } - - protected: - bool state_{false}; - uint32_t pulse_duration_{0}; - uint64_t pinned_millis_{0}; - switch_::Switch *off_switch_{nullptr}; // only used for latching valves - switch_::Switch *on_switch_{nullptr}; // used for both latching and non-latching valves -}; - struct SprinklerQueueItem { size_t valve_number; uint32_t run_duration; @@ -89,7 +59,7 @@ struct SprinklerValve { SprinklerControllerNumber *run_duration_number; SprinklerControllerSwitch *controller_switch; SprinklerControllerSwitch *enable_switch; - SprinklerSwitch valve_switch; + switch_::Switch *valve_switch; uint32_t run_duration; optional pump_switch_index; bool valve_cycle_complete; @@ -156,7 +126,7 @@ class SprinklerValveOperator { uint32_t run_duration(); // returns the desired run duration in seconds uint32_t time_remaining(); // returns seconds remaining (does not include stop_delay_) SprinklerState state(); // returns the valve's state/status - SprinklerSwitch *pump_switch(); // returns this SprinklerValveOperator's pump's SprinklerSwitch + switch_::Switch *pump_switch(); // returns this SprinklerValveOperator's pump switch protected: void pump_off_(); @@ -229,13 +199,9 @@ class Sprinkler : public Component { /// configure a valve's switch object and run duration. run_duration is time in seconds. void configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration); - void configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off, - switch_::Switch *valve_switch_on, uint32_t pulse_duration, uint32_t run_duration); /// configure a valve's associated pump switch object void configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch); - void configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off, - switch_::Switch *pump_switch_on, uint32_t pulse_duration); /// configure a valve's run duration number component void configure_valve_run_duration_number(size_t valve_number, SprinklerControllerNumber *run_duration_number); @@ -384,10 +350,10 @@ class Sprinkler : public Component { bool is_a_valid_valve(size_t valve_number); /// returns true if the pump the pointer points to is in use - bool pump_in_use(SprinklerSwitch *pump_switch); + bool pump_in_use(switch_::Switch *pump_switch); /// switches on/off a pump "safely" by checking that the new state will not conflict with another controller - void set_pump_state(SprinklerSwitch *pump_switch, bool state); + void set_pump_state(switch_::Switch *pump_switch, bool state); /// returns the amount of time in seconds required for all valves uint32_t total_cycle_time_all_valves(); @@ -420,13 +386,13 @@ class Sprinkler : public Component { SprinklerControllerSwitch *enable_switch(size_t valve_number); /// returns a pointer to a valve's switch object - SprinklerSwitch *valve_switch(size_t valve_number); + switch_::Switch *valve_switch(size_t valve_number); /// returns a pointer to a valve's pump switch object - SprinklerSwitch *valve_pump_switch(size_t valve_number); + switch_::Switch *valve_pump_switch(size_t valve_number); /// returns a pointer to a valve's pump switch object - SprinklerSwitch *valve_pump_switch_by_pump_index(size_t pump_index); + switch_::Switch *valve_pump_switch_by_pump_index(size_t pump_index); protected: /// returns true if valve number is enabled @@ -490,11 +456,17 @@ class Sprinkler : public Component { /// starts up the system from IDLE state void fsm_transition_to_shutdown_(); + /// log error message when a method is called but standby is enabled + void log_standby_warning_(const LogString *method_name); + + /// log error message when a method is called but multiplier is zero + void log_multiplier_zero_warning_(const LogString *method_name); + /// return the specified SprinklerValveRunRequestOrigin as a string - std::string req_as_str_(SprinklerValveRunRequestOrigin origin); + const LogString *req_as_str_(SprinklerValveRunRequestOrigin origin); /// return the specified SprinklerState state as a string - std::string state_as_str_(SprinklerState state); + const LogString *state_as_str_(SprinklerState state); /// Start/cancel/get status of valve timers void start_timer_(SprinklerTimerIndex timer_index); @@ -572,8 +544,8 @@ class Sprinkler : public Component { /// Queue of valves to activate next, regardless of auto-advance std::vector queued_valves_; - /// Sprinkler valve pump objects - std::vector pump_; + /// Sprinkler valve pump switches + std::vector pump_; /// Sprinkler valve objects std::vector valve_; @@ -607,5 +579,4 @@ class Sprinkler : public Component { std::unique_ptr> sprinkler_standby_turn_on_automation_; }; -} // namespace sprinkler -} // namespace esphome +} // namespace esphome::sprinkler diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 00425b853f..e0e7f94ce0 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -32,6 +32,8 @@ static const uint8_t SSD1306_COMMAND_PAGE_ADDRESS = 0x22; static const uint8_t SSD1306_COMMAND_NORMAL_DISPLAY = 0xA6; static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7; +static const uint8_t SSD1306B_COMMAND_SELECT_IREF = 0xAD; + static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82; static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8; @@ -95,6 +97,12 @@ void SSD1306::setup() { this->command(0x8B); } } else { + if (this->is_ssd1306b_()) { + // Select external or internal Iref (0xAD) + this->command(SSD1306B_COMMAND_SELECT_IREF); + // Enable internal Iref and change from 19ua (POR) to 30uA + this->command(0x20 | 0x10); + } // Enable charge pump (0x8D) this->command(SSD1306_COMMAND_CHARGE_PUMP); if (this->external_vcc_) { @@ -226,6 +234,8 @@ bool SSD1306::is_sh1107_() const { return this->model_ == SH1107_MODEL_128_64 || bool SSD1306::is_ssd1305_() const { return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_32; } +bool SSD1306::is_ssd1306b_() const { return this->model_ == SSD1306_MODEL_72_40; } + void SSD1306::update() { this->do_update_(); this->display(); @@ -329,6 +339,12 @@ void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, Color color) { } } void SSD1306::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + uint8_t fill = color.is_on() ? 0xFF : 0x00; for (uint32_t i = 0; i < this->get_buffer_length_(); i++) this->buffer_[i] = fill; diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 14ec309ae0..a573437386 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -63,6 +63,7 @@ class SSD1306 : public display::DisplayBuffer { bool is_sh1106_() const; bool is_sh1107_() const; bool is_ssd1305_() const; + bool is_ssd1306b_() const; void draw_absolute_pixel_internal(int x, int y, Color color) override; diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index 8e490834bc..47a21a8ff4 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -20,18 +20,18 @@ void I2CSSD1306::setup() { } void I2CSSD1306::dump_config() { LOG_DISPLAY("", "I2C SSD1306", this); - LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + " Model: %s\n" " External VCC: %s\n" " Flip X: %s\n" " Flip Y: %s\n" " Offset X: %d\n" " Offset Y: %d\n" " Inverted Color: %s", - YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), this->offset_x_, - this->offset_y_, YESNO(this->invert_)); + this->model_str_(), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), + this->offset_x_, this->offset_y_, YESNO(this->invert_)); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_UPDATE_INTERVAL(this); if (this->error_code_ == COMMUNICATION_FAILED) { @@ -62,9 +62,9 @@ void HOT I2CSSD1306::write_display_data() { } } else { size_t block_size = 16; - if ((this->get_buffer_length_() & 8) == 8) { - // use smaller block size for e.g. 72x40 displays where buffer size is multiple of 8, not 16 - block_size = 8; + if ((this->get_buffer_length_() % 24) == 0) { + // use 24 byte block size for e.g. 72x40 displays where buffer size is multiple of 24, not 16 + block_size = 24; } for (uint32_t i = 0; i < this->get_buffer_length_();) { diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index d93742c0e5..db28dfc564 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -16,19 +16,19 @@ void SPISSD1306::setup() { } void SPISSD1306::dump_config() { LOG_DISPLAY("", "SPI SSD1306", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - LOG_PIN(" CS Pin: ", this->cs_); - LOG_PIN(" DC Pin: ", this->dc_pin_); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + " Model: %s\n" " External VCC: %s\n" " Flip X: %s\n" " Flip Y: %s\n" " Offset X: %d\n" " Offset Y: %d\n" " Inverted Color: %s", - YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), this->offset_x_, - this->offset_y_, YESNO(this->invert_)); + this->model_str_(), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_), + this->offset_x_, this->offset_y_, YESNO(this->invert_)); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_UPDATE_INTERVAL(this); } void SPISSD1306::command(uint8_t value) { diff --git a/esphome/components/ssd1322_base/ssd1322_base.cpp b/esphome/components/ssd1322_base/ssd1322_base.cpp index eb8d87998f..23576e7b2c 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.cpp +++ b/esphome/components/ssd1322_base/ssd1322_base.cpp @@ -174,6 +174,12 @@ void HOT SSD1322::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] |= color4; } void SSD1322::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); uint8_t fill = (color4 & SSD1322_COLORMASK) | ((color4 & SSD1322_COLORMASK) << SSD1322_COLORSHIFT); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) diff --git a/esphome/components/ssd1322_spi/ssd1322_spi.cpp b/esphome/components/ssd1322_spi/ssd1322_spi.cpp index 6a8918353b..bc7d298922 100644 --- a/esphome/components/ssd1322_spi/ssd1322_spi.cpp +++ b/esphome/components/ssd1322_spi/ssd1322_spi.cpp @@ -19,11 +19,13 @@ void SPISSD1322::setup() { } void SPISSD1322::dump_config() { LOG_DISPLAY("", "SPI SSD1322", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f", + this->model_str_(), this->brightness_); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); LOG_UPDATE_INTERVAL(this); } void SPISSD1322::command(uint8_t value) { diff --git a/esphome/components/ssd1325_spi/ssd1325_spi.cpp b/esphome/components/ssd1325_spi/ssd1325_spi.cpp index 3c9dfd3324..07a5119d8f 100644 --- a/esphome/components/ssd1325_spi/ssd1325_spi.cpp +++ b/esphome/components/ssd1325_spi/ssd1325_spi.cpp @@ -19,12 +19,14 @@ void SPISSD1325::setup() { } void SPISSD1325::dump_config() { LOG_DISPLAY("", "SPI SSD1325", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f\n" + " External VCC: %s", + this->model_str_(), this->brightness_, YESNO(this->external_vcc_)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); - ESP_LOGCONFIG(TAG, " External VCC: %s", YESNO(this->external_vcc_)); LOG_UPDATE_INTERVAL(this); } void SPISSD1325::command(uint8_t value) { diff --git a/esphome/components/ssd1327_base/ssd1327_base.cpp b/esphome/components/ssd1327_base/ssd1327_base.cpp index 6b83ec5f9d..2498bfcd67 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.cpp +++ b/esphome/components/ssd1327_base/ssd1327_base.cpp @@ -150,6 +150,12 @@ void HOT SSD1327::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] |= color4; } void SSD1327::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color); uint8_t fill = (color4 & SSD1327_COLORMASK) | ((color4 & SSD1327_COLORMASK) << SSD1327_COLORSHIFT); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) diff --git a/esphome/components/ssd1327_spi/ssd1327_spi.cpp b/esphome/components/ssd1327_spi/ssd1327_spi.cpp index c26238ae19..54d1a51100 100644 --- a/esphome/components/ssd1327_spi/ssd1327_spi.cpp +++ b/esphome/components/ssd1327_spi/ssd1327_spi.cpp @@ -19,11 +19,13 @@ void SPISSD1327::setup() { } void SPISSD1327::dump_config() { LOG_DISPLAY("", "SPI SSD1327", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f", + this->model_str_(), this->brightness_); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); LOG_UPDATE_INTERVAL(this); } void SPISSD1327::command(uint8_t value) { diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index 8ee12387e4..a2993edef3 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -128,6 +128,12 @@ void HOT SSD1331::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] = color565 & 0xff; } void SSD1331::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color565 = display::ColorUtil::color_to_565(color); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index 09530e8a27..69bf67f476 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -160,6 +160,12 @@ void HOT SSD1351::draw_absolute_pixel_internal(int x, int y, Color color) { this->buffer_[pos] = color565 & 0xff; } void SSD1351::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + const uint32_t color565 = display::ColorUtil::color_to_565(color); for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { diff --git a/esphome/components/ssd1351_spi/ssd1351_spi.cpp b/esphome/components/ssd1351_spi/ssd1351_spi.cpp index ffac07b82b..b046f0adcb 100644 --- a/esphome/components/ssd1351_spi/ssd1351_spi.cpp +++ b/esphome/components/ssd1351_spi/ssd1351_spi.cpp @@ -19,11 +19,13 @@ void SPISSD1351::setup() { } void SPISSD1351::dump_config() { LOG_DISPLAY("", "SPI SSD1351", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Initial Brightness: %.2f", + this->model_str_(), this->brightness_); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Initial Brightness: %.2f", this->brightness_); LOG_UPDATE_INTERVAL(this); } void SPISSD1351::command(uint8_t value) { diff --git a/esphome/components/st7567_base/st7567_base.cpp b/esphome/components/st7567_base/st7567_base.cpp index 0afd2a70ba..8c47094b26 100644 --- a/esphome/components/st7567_base/st7567_base.cpp +++ b/esphome/components/st7567_base/st7567_base.cpp @@ -131,7 +131,16 @@ void HOT ST7567::draw_absolute_pixel_internal(int x, int y, Color color) { } } -void ST7567::fill(Color color) { memset(buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } +void ST7567::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + + uint8_t fill = color.is_on() ? 0xFF : 0x00; + memset(buffer_, fill, this->get_buffer_length_()); +} void ST7567::init_reset_() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/st7567_i2c/st7567_i2c.cpp b/esphome/components/st7567_i2c/st7567_i2c.cpp index 14c21d5148..3214339571 100644 --- a/esphome/components/st7567_i2c/st7567_i2c.cpp +++ b/esphome/components/st7567_i2c/st7567_i2c.cpp @@ -20,14 +20,14 @@ void I2CST7567::setup() { void I2CST7567::dump_config() { LOG_DISPLAY("", "I2CST7567", this); - LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); - LOG_PIN(" Reset Pin: ", this->reset_pin_); ESP_LOGCONFIG(TAG, + " Model: %s\n" " Mirror X: %s\n" " Mirror Y: %s\n" " Invert Colors: %s", - YESNO(this->mirror_x_), YESNO(this->mirror_y_), YESNO(this->invert_colors_)); + this->model_str_(), YESNO(this->mirror_x_), YESNO(this->mirror_y_), YESNO(this->invert_colors_)); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_UPDATE_INTERVAL(this); if (this->error_code_ == COMMUNICATION_FAILED) { diff --git a/esphome/components/st7567_spi/st7567_spi.cpp b/esphome/components/st7567_spi/st7567_spi.cpp index 813afcf682..7476fd7c8d 100644 --- a/esphome/components/st7567_spi/st7567_spi.cpp +++ b/esphome/components/st7567_spi/st7567_spi.cpp @@ -18,13 +18,15 @@ void SPIST7567::setup() { void SPIST7567::dump_config() { LOG_DISPLAY("", "SPI ST7567", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Mirror X: %s\n" + " Mirror Y: %s\n" + " Invert Colors: %s", + this->model_str_(), YESNO(this->mirror_x_), YESNO(this->mirror_y_), YESNO(this->invert_colors_)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGCONFIG(TAG, " Mirror X: %s", YESNO(this->mirror_x_)); - ESP_LOGCONFIG(TAG, " Mirror Y: %s", YESNO(this->mirror_y_)); - ESP_LOGCONFIG(TAG, " Invert Colors: %s", YESNO(this->invert_colors_)); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py index 6e4bff6431..3078158d25 100644 --- a/esphome/components/st7701s/display.py +++ b/esphome/components/st7701s/display.py @@ -161,8 +161,8 @@ CONFIG_SCHEMA = cv.All( } ).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6)) ), + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, ) FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 160ba151f7..1a74b5ce1e 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -373,15 +373,18 @@ void ST7735::display_init_(const uint8_t *addr) { void ST7735::dump_config() { LOG_DISPLAY("", "ST7735", this); - ESP_LOGCONFIG(TAG, " Model: %s", this->model_str_()); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); - ESP_LOGD(TAG, " Buffer Size: %zu", this->get_buffer_length()); - ESP_LOGD(TAG, " Height: %d", this->height_); - ESP_LOGD(TAG, " Width: %d", this->width_); - ESP_LOGD(TAG, " ColStart: %d", this->colstart_); - ESP_LOGD(TAG, " RowStart: %d", this->rowstart_); + ESP_LOGCONFIG(TAG, + " Model: %s\n" + " Buffer Size: %zu\n" + " Height: %d\n" + " Width: %d\n" + " ColStart: %d\n" + " RowStart: %d", + this->model_str_(), this->get_buffer_length(), this->height_, this->width_, this->colstart_, + this->rowstart_); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index ade9c1126f..cd0b6cabc3 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -127,15 +127,15 @@ void ST7789V::dump_config() { " Width: %u\n" " Height Offset: %u\n" " Width Offset: %u\n" - " 8-bit color mode: %s", + " 8-bit color mode: %s\n" + " Data rate: %dMHz", this->model_str_, this->height_, this->width_, this->offset_height_, this->offset_width_, - YESNO(this->eightbitcolor_)); + YESNO(this->eightbitcolor_), (unsigned) (this->data_rate_ / 1000000)); LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" B/L Pin: ", this->backlight_pin_); LOG_UPDATE_INTERVAL(this); - ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); #ifdef USE_POWER_SUPPLY ESP_LOGCONFIG(TAG, " Power Supply Configured: yes"); #endif diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index c7ce7140e3..afd7cd61bd 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -89,7 +89,16 @@ void HOT ST7920::write_display_data() { } } -void ST7920::fill(Color color) { memset(this->buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); } +void ST7920::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + + uint8_t fill = color.is_on() ? 0xFF : 0x00; + memset(this->buffer_, fill, this->get_buffer_length_()); +} void ST7920::dump_config() { LOG_DISPLAY("", "ST7920", this); diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index df7030461b..e8fc4e44d1 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -172,21 +172,25 @@ struct SunAtTime { void debug() const { // debug output like in example 25.a, p. 165 - ESP_LOGV(TAG, "jde: %f", jde); - ESP_LOGV(TAG, "T: %f", t); - ESP_LOGV(TAG, "L_0: %f", mean_longitude()); - ESP_LOGV(TAG, "M: %f", mean_anomaly()); - ESP_LOGV(TAG, "e: %f", eccentricity()); - ESP_LOGV(TAG, "C: %f", equation_of_center()); - ESP_LOGV(TAG, "Odot: %f", true_longitude()); - ESP_LOGV(TAG, "Omega: %f", omega()); - ESP_LOGV(TAG, "lambda: %f", apparent_longitude()); - ESP_LOGV(TAG, "epsilon_0: %f", mean_obliquity()); - ESP_LOGV(TAG, "epsilon: %f", true_obliquity()); - ESP_LOGV(TAG, "v: %f", true_anomaly()); auto eq = equatorial_coordinate(); - ESP_LOGV(TAG, "right_ascension: %f", eq.right_ascension); - ESP_LOGV(TAG, "declination: %f", eq.declination); + ESP_LOGV(TAG, + "jde: %f\n" + "T: %f\n" + "L_0: %f\n" + "M: %f\n" + "e: %f\n" + "C: %f\n" + "Odot: %f\n" + "Omega: %f\n" + "lambda: %f\n" + "epsilon_0: %f\n" + "epsilon: %f\n" + "v: %f\n" + "right_ascension: %f\n" + "declination: %f", + jde, t, mean_longitude(), mean_anomaly(), eccentricity(), equation_of_center(), true_longitude(), omega(), + apparent_longitude(), mean_obliquity(), true_obliquity(), true_anomaly(), eq.right_ascension, + eq.declination); } }; diff --git a/esphome/components/sun/text_sensor/sun_text_sensor.h b/esphome/components/sun/text_sensor/sun_text_sensor.h index ce7d21fb86..9345a32223 100644 --- a/esphome/components/sun/text_sensor/sun_text_sensor.h +++ b/esphome/components/sun/text_sensor/sun_text_sensor.h @@ -28,7 +28,9 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent { return; } - this->publish_state(res->strftime(this->format_)); + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + size_t len = res->strftime_to(buf, this->format_.c_str()); + this->publish_state(buf, len); } void dump_config() override; diff --git a/esphome/components/sun_gtil2/sun_gtil2.cpp b/esphome/components/sun_gtil2/sun_gtil2.cpp index 46b4902654..d416d9a636 100644 --- a/esphome/components/sun_gtil2/sun_gtil2.cpp +++ b/esphome/components/sun_gtil2/sun_gtil2.cpp @@ -47,14 +47,15 @@ void SunGTIL2::loop() { } } -std::string SunGTIL2::state_to_string_(uint8_t state) { +const char *SunGTIL2::state_to_string_(uint8_t state, std::span buffer) { switch (state) { case 0x02: return "Starting voltage too low"; case 0x07: return "Working"; default: - return str_sprintf("Unknown (0x%02x)", state); + snprintf(buffer.data(), buffer.size(), "Unknown (0x%02x)", state); + return buffer.data(); } } @@ -106,12 +107,11 @@ void SunGTIL2::handle_char_(uint8_t c) { #endif #ifdef USE_TEXT_SENSOR if (this->state_ != nullptr) { - this->state_->publish_state(this->state_to_string_(msg.state)); + char state_buffer[STATE_BUFFER_SIZE]; + this->state_->publish_state(this->state_to_string_(msg.state, state_buffer)); } if (this->serial_number_ != nullptr) { - std::string serial_number; - serial_number.assign(msg.serial_number, 10); - this->serial_number_->publish_state(serial_number); + this->serial_number_->publish_state(msg.serial_number, 10); } #endif } diff --git a/esphome/components/sun_gtil2/sun_gtil2.h b/esphome/components/sun_gtil2/sun_gtil2.h index 0c29ae695d..3e28527cf7 100644 --- a/esphome/components/sun_gtil2/sun_gtil2.h +++ b/esphome/components/sun_gtil2/sun_gtil2.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "esphome/core/defines.h" @@ -34,8 +36,10 @@ class SunGTIL2 : public Component, public uart::UARTDevice { void set_serial_number(text_sensor::TextSensor *text_sensor) { serial_number_ = text_sensor; } #endif + static constexpr size_t STATE_BUFFER_SIZE = 32; + protected: - std::string state_to_string_(uint8_t state); + const char *state_to_string_(uint8_t state, std::span buffer); #ifdef USE_SENSOR sensor::Sensor *ac_voltage_{nullptr}; sensor::Sensor *dc_voltage_{nullptr}; diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index e9473012cf..7424d7c92f 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -1,7 +1,7 @@ from esphome import automation from esphome.automation import Condition, maybe_simple_id import esphome.codegen as cg -from esphome.components import mqtt, web_server +from esphome.components import mqtt, web_server, zigbee import esphome.config_validation as cv from esphome.const import ( CONF_DEVICE_CLASS, @@ -74,6 +74,7 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True) _SWITCH_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend(zigbee.SWITCH_SCHEMA) .extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), @@ -103,6 +104,7 @@ _SWITCH_SCHEMA = ( _SWITCH_SCHEMA.add_extra(entity_duplicate_validator("switch")) +_SWITCH_SCHEMA.add_extra(zigbee.validate_switch) def switch_schema( @@ -165,6 +167,7 @@ async def setup_switch_core_(var, config): cg.add(var.set_device_class(device_class)) cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) + await zigbee.setup_switch(var, config) async def register_switch(var, config): diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 6371e35292..9319adf9ed 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -134,8 +134,8 @@ class Switch : public EntityBase, public EntityBase_DeviceClass { // Pointer first (4 bytes) ESPPreferenceObject rtc_; - // CallbackManager (12 bytes on 32-bit - contains vector) - CallbackManager state_callback_{}; + // LazyCallbackManager (4 bytes on 32-bit - nullptr when empty) + LazyCallbackManager state_callback_{}; // Small types grouped together Deduplicator publish_dedup_; // 2 bytes (bool has_value_ + bool last_value_) diff --git a/esphome/components/sx126x/sx126x.cpp b/esphome/components/sx126x/sx126x.cpp index bb59f26b79..707d6f1fbf 100644 --- a/esphome/components/sx126x/sx126x.cpp +++ b/esphome/components/sx126x/sx126x.cpp @@ -527,7 +527,9 @@ void SX126x::dump_config() { this->spreading_factor_, cr, this->preamble_size_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->is_failed()) { ESP_LOGE(TAG, "Configuring SX126x failed"); diff --git a/esphome/components/sx127x/sx127x.cpp b/esphome/components/sx127x/sx127x.cpp index 8e6db5dc9e..3185574b1a 100644 --- a/esphome/components/sx127x/sx127x.cpp +++ b/esphome/components/sx127x/sx127x.cpp @@ -476,7 +476,9 @@ void SX127x::dump_config() { ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/sx1509/output/sx1509_float_output.cpp b/esphome/components/sx1509/output/sx1509_float_output.cpp index 1d2541bb46..4a24d78478 100644 --- a/esphome/components/sx1509/output/sx1509_float_output.cpp +++ b/esphome/components/sx1509/output/sx1509_float_output.cpp @@ -22,8 +22,10 @@ void SX1509FloatOutputChannel::setup() { } void SX1509FloatOutputChannel::dump_config() { - ESP_LOGCONFIG(TAG, "SX1509 PWM:"); - ESP_LOGCONFIG(TAG, " sx1509 pin: %d", this->pin_); + ESP_LOGCONFIG(TAG, + "SX1509 PWM:\n" + " sx1509 pin: %d", + this->pin_); LOG_FLOAT_OUTPUT(this); } diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index a74c8b60b8..41a99eba4b 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -12,10 +12,8 @@ void SX1509GPIOPin::setup() { pin_mode(flags_); } void SX1509GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string SX1509GPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via sx1509", this->pin_); - return buffer; +size_t SX1509GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via sx1509", this->pin_); } } // namespace sx1509 diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h index eb9207e882..5903af9d12 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.h +++ b/esphome/components/sx1509/sx1509_gpio_pin.h @@ -13,7 +13,7 @@ class SX1509GPIOPin : public GPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_parent(SX1509Component *parent) { this->parent_ = parent; } void set_pin(uint8_t pin) { this->pin_ = pin; } diff --git a/esphome/components/syslog/__init__.py b/esphome/components/syslog/__init__.py index 80b79d2040..08626404f7 100644 --- a/esphome/components/syslog/__init__.py +++ b/esphome/components/syslog/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg from esphome.components import udp -from esphome.components.logger import LOG_LEVELS, is_log_level +from esphome.components.logger import LOG_LEVELS, is_log_level, request_log_listener from esphome.components.time import RealTimeClock from esphome.components.udp import CONF_UDP_ID import esphome.config_validation as cv @@ -36,6 +36,7 @@ async def to_code(config): level = LOG_LEVELS[config[CONF_LEVEL]] var = cg.new_Pvariable(config[CONF_ID], level, time) await cg.register_component(var, config) + request_log_listener() # Request a log listener slot for syslog await cg.register_parented(var, parent) cg.add(var.set_strip(config[CONF_STRIP])) cg.add(var.set_facility(config[CONF_FACILITY])) diff --git a/esphome/components/syslog/esphome_syslog.cpp b/esphome/components/syslog/esphome_syslog.cpp index 851fb30c22..83ad6b2720 100644 --- a/esphome/components/syslog/esphome_syslog.cpp +++ b/esphome/components/syslog/esphome_syslog.cpp @@ -4,8 +4,7 @@ #include "esphome/core/application.h" #include "esphome/core/time.h" -namespace esphome { -namespace syslog { +namespace esphome::syslog { // Map log levels to syslog severity using an array, indexed by ESPHome log level (1-7) constexpr int LOG_LEVEL_TO_SYSLOG_SEVERITY[] = { @@ -34,15 +33,7 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level]; } int pri = this->facility_ * 8 + severity; - auto now = this->time_->now(); - std::string timestamp; - if (now.is_valid()) { - timestamp = now.strftime("%b %e %H:%M:%S"); - } else { - // RFC 5424: A syslog application MUST use the NILVALUE as TIMESTAMP if the syslog application is incapable of - // obtaining system time. - timestamp = "-"; - } + size_t len = message_len; // remove color formatting if (this->strip_ && message[0] == 0x1B && len > 11) { @@ -50,9 +41,40 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t len -= 11; } - auto data = str_sprintf("<%d>%s %s %s: %.*s", pri, timestamp.c_str(), App.get_name().c_str(), tag, len, message); - this->parent_->send_packet((const uint8_t *) data.data(), data.size()); + // Build syslog packet on stack (508 bytes chosen as practical limit for syslog over UDP) + char packet[508]; + size_t offset = 0; + size_t remaining = sizeof(packet); + + // Write PRI - abort if this fails as packet would be malformed + int ret = snprintf(packet, remaining, "<%d>", pri); + if (ret <= 0 || static_cast(ret) >= remaining) { + return; + } + offset = ret; + remaining -= ret; + + // Write timestamp directly into packet (RFC 5424: use "-" if time not valid or strftime fails) + auto now = this->time_->now(); + size_t ts_written = now.is_valid() ? now.strftime(packet + offset, remaining, "%b %e %H:%M:%S") : 0; + if (ts_written > 0) { + offset += ts_written; + remaining -= ts_written; + } else if (remaining > 0) { + packet[offset++] = '-'; + remaining--; + } + + // Write hostname, tag, and message + ret = snprintf(packet + offset, remaining, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len, message); + if (ret > 0) { + // snprintf returns chars that would be written; clamp to actual buffer space + offset += std::min(static_cast(ret), remaining > 0 ? remaining - 1 : 0); + } + + if (offset > 0) { + this->parent_->send_packet(reinterpret_cast(packet), offset); + } } -} // namespace syslog -} // namespace esphome +} // namespace esphome::syslog diff --git a/esphome/components/syslog/esphome_syslog.h b/esphome/components/syslog/esphome_syslog.h index 1010993265..bde6ab5ed4 100644 --- a/esphome/components/syslog/esphome_syslog.h +++ b/esphome/components/syslog/esphome_syslog.h @@ -7,8 +7,7 @@ #include "esphome/components/time/real_time_clock.h" #ifdef USE_NETWORK -namespace esphome { -namespace syslog { +namespace esphome::syslog { class Syslog : public Component, public Parented, public logger::LogListener { public: Syslog(int level, time::RealTimeClock *time) : log_level_(level), time_(time) {} @@ -24,6 +23,5 @@ class Syslog : public Component, public Parented, public logg bool strip_{true}; int facility_{16}; }; -} // namespace syslog -} // namespace esphome +} // namespace esphome::syslog #endif diff --git a/esphome/components/tca9548a/__init__.py b/esphome/components/tca9548a/__init__.py index cef779de2e..72973a54ad 100644 --- a/esphome/components/tca9548a/__init__.py +++ b/esphome/components/tca9548a/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv -from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID, CONF_SCAN +from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID CODEOWNERS = ["@andreashergert1984"] @@ -18,7 +18,6 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(TCA9548AComponent), - cv.Optional(CONF_SCAN): cv.invalid("This option has been removed"), cv.Optional(CONF_CHANNELS, default=[]): cv.ensure_list( { cv.Required(CONF_BUS_ID): cv.declare_id(TCA9548AChannel), diff --git a/esphome/components/tca9555/tca9555.cpp b/esphome/components/tca9555/tca9555.cpp index c3449ce254..376de6a370 100644 --- a/esphome/components/tca9555/tca9555.cpp +++ b/esphome/components/tca9555/tca9555.cpp @@ -138,7 +138,9 @@ void TCA9555GPIOPin::setup() { this->pin_mode(this->flags_); } void TCA9555GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool TCA9555GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void TCA9555GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -std::string TCA9555GPIOPin::dump_summary() const { return str_sprintf("%u via TCA9555", this->pin_); } +size_t TCA9555GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via TCA9555", this->pin_); +} } // namespace tca9555 } // namespace esphome diff --git a/esphome/components/tca9555/tca9555.h b/esphome/components/tca9555/tca9555.h index 0c236ae4e3..9f7273b1e7 100644 --- a/esphome/components/tca9555/tca9555.h +++ b/esphome/components/tca9555/tca9555.h @@ -48,7 +48,7 @@ class TCA9555GPIOPin : public GPIOPin, public Parented { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void set_pin(uint8_t pin) { this->pin_ = pin; } void set_inverted(bool inverted) { this->inverted_ = inverted; } diff --git a/esphome/components/tee501/tee501.cpp b/esphome/components/tee501/tee501.cpp index d6513dbbe0..06481b628b 100644 --- a/esphome/components/tee501/tee501.cpp +++ b/esphome/components/tee501/tee501.cpp @@ -7,6 +7,8 @@ namespace tee501 { static const char *const TAG = "tee501"; +static constexpr size_t TEE501_SERIAL_NUMBER_SIZE = 7; + void TEE501Component::setup() { uint8_t address[] = {0x70, 0x29}; uint8_t identification[9]; @@ -17,7 +19,10 @@ void TEE501Component::setup() { this->mark_failed(); return; } - ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(identification + 0, 7).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char serial_hex[format_hex_size(TEE501_SERIAL_NUMBER_SIZE)]; +#endif + ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, identification, TEE501_SERIAL_NUMBER_SIZE)); } void TEE501Component::dump_config() { diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp index 384e6b0ca1..0e1920a984 100644 --- a/esphome/components/template/fan/template_fan.cpp +++ b/esphome/components/template/fan/template_fan.cpp @@ -28,7 +28,7 @@ void TemplateFan::control(const fan::FanCall &call) { this->oscillating = *call.get_oscillating(); if (call.get_direction().has_value() && this->has_direction_) this->direction = *call.get_direction(); - this->set_preset_mode_(call.get_preset_mode()); + this->apply_preset_mode_(call); this->publish_state(); } diff --git a/esphome/components/template/switch/__init__.py b/esphome/components/template/switch/__init__.py index e86657510f..8ae5a07dc3 100644 --- a/esphome/components/template/switch/__init__.py +++ b/esphome/components/template/switch/__init__.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC, - CONF_RESTORE_STATE, CONF_STATE, CONF_TURN_OFF_ACTION, CONF_TURN_ON_ACTION, @@ -44,9 +43,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation( single=True ), - cv.Optional(CONF_RESTORE_STATE): cv.invalid( - "The restore_state option has been removed in 2023.7.0. Use the restore_mode option instead" - ), } ) .extend(cv.COMPONENT_SCHEMA), diff --git a/esphome/components/template/water_heater/__init__.py b/esphome/components/template/water_heater/__init__.py new file mode 100644 index 0000000000..716289035a --- /dev/null +++ b/esphome/components/template/water_heater/__init__.py @@ -0,0 +1,123 @@ +from esphome import automation +import esphome.codegen as cg +from esphome.components import water_heater +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_MODE, + CONF_OPTIMISTIC, + CONF_RESTORE_MODE, + CONF_SET_ACTION, + CONF_SUPPORTED_MODES, + CONF_TARGET_TEMPERATURE, +) +from esphome.core import ID +from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.types import ConfigType + +from .. import template_ns + +CONF_CURRENT_TEMPERATURE = "current_temperature" + +TemplateWaterHeater = template_ns.class_( + "TemplateWaterHeater", water_heater.WaterHeater +) + +TemplateWaterHeaterPublishAction = template_ns.class_( + "TemplateWaterHeaterPublishAction", + automation.Action, + cg.Parented.template(TemplateWaterHeater), +) + +TemplateWaterHeaterRestoreMode = template_ns.enum("TemplateWaterHeaterRestoreMode") +RESTORE_MODES = { + "NO_RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_NO_RESTORE, + "RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE, + "RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL, +} + +CONFIG_SCHEMA = water_heater.water_heater_schema(TemplateWaterHeater).extend( + { + cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean, + cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum( + RESTORE_MODES, upper=True + ), + cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda, + cv.Optional(CONF_MODE): cv.returning_lambda, + cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list( + water_heater.validate_water_heater_mode + ), + } +) + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await water_heater.register_water_heater(var, config) + + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + + if CONF_SET_ACTION in config: + await automation.build_automation( + var.get_set_trigger(), [], config[CONF_SET_ACTION] + ) + + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) + + if CONF_CURRENT_TEMPERATURE in config: + template_ = await cg.process_lambda( + config[CONF_CURRENT_TEMPERATURE], + [], + return_type=cg.optional.template(cg.float_), + ) + cg.add(var.set_current_temperature_lambda(template_)) + + if CONF_MODE in config: + template_ = await cg.process_lambda( + config[CONF_MODE], + [], + return_type=cg.optional.template(water_heater.WaterHeaterMode), + ) + cg.add(var.set_mode_lambda(template_)) + + if CONF_SUPPORTED_MODES in config: + cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) + + +@automation.register_action( + "water_heater.template.publish", + TemplateWaterHeaterPublishAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(TemplateWaterHeater), + cv.Optional(CONF_CURRENT_TEMPERATURE): cv.templatable(cv.temperature), + cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), + cv.Optional(CONF_MODE): cv.templatable( + water_heater.validate_water_heater_mode + ), + } + ), +) +async def water_heater_template_publish_to_code( + config: ConfigType, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, +) -> MockObj: + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + if current_temp := config.get(CONF_CURRENT_TEMPERATURE): + template_ = await cg.templatable(current_temp, args, float) + cg.add(var.set_current_temperature(template_)) + + if target_temp := config.get(CONF_TARGET_TEMPERATURE): + template_ = await cg.templatable(target_temp, args, float) + cg.add(var.set_target_temperature(template_)) + + if mode := config.get(CONF_MODE): + template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode) + cg.add(var.set_mode(template_)) + + return var diff --git a/esphome/components/template/water_heater/automation.h b/esphome/components/template/water_heater/automation.h new file mode 100644 index 0000000000..3dad2b85ae --- /dev/null +++ b/esphome/components/template/water_heater/automation.h @@ -0,0 +1,35 @@ +#pragma once + +#include "template_water_heater.h" +#include "esphome/core/automation.h" + +namespace esphome::template_ { + +template +class TemplateWaterHeaterPublishAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(float, current_temperature) + TEMPLATABLE_VALUE(float, target_temperature) + TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode) + + void play(const Ts &...x) override { + if (this->current_temperature_.has_value()) { + this->parent_->set_current_temperature(this->current_temperature_.value(x...)); + } + bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value(); + if (needs_call) { + auto call = this->parent_->make_call(); + if (this->target_temperature_.has_value()) { + call.set_target_temperature(this->target_temperature_.value(x...)); + } + if (this->mode_.has_value()) { + call.set_mode(this->mode_.value(x...)); + } + call.perform(); + } else { + this->parent_->publish_state(); + } + } +}; + +} // namespace esphome::template_ diff --git a/esphome/components/template/water_heater/template_water_heater.cpp b/esphome/components/template/water_heater/template_water_heater.cpp new file mode 100644 index 0000000000..5ae5c30f36 --- /dev/null +++ b/esphome/components/template/water_heater/template_water_heater.cpp @@ -0,0 +1,88 @@ +#include "template_water_heater.h" +#include "esphome/core/log.h" + +namespace esphome::template_ { + +static const char *const TAG = "template.water_heater"; + +TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {} + +void TemplateWaterHeater::setup() { + if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE || + this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE_AND_CALL) { + auto restore = this->restore_state(); + + if (restore.has_value()) { + restore->perform(); + } + } + if (!this->current_temperature_f_.has_value() && !this->mode_f_.has_value()) + this->disable_loop(); +} + +water_heater::WaterHeaterTraits TemplateWaterHeater::traits() { + water_heater::WaterHeaterTraits traits; + + if (!this->supported_modes_.empty()) { + traits.set_supported_modes(this->supported_modes_); + } + + traits.set_supports_current_temperature(true); + return traits; +} + +void TemplateWaterHeater::loop() { + bool changed = false; + + auto curr_temp = this->current_temperature_f_.call(); + if (curr_temp.has_value()) { + if (*curr_temp != this->current_temperature_) { + this->current_temperature_ = *curr_temp; + changed = true; + } + } + + auto new_mode = this->mode_f_.call(); + if (new_mode.has_value()) { + if (*new_mode != this->mode_) { + this->mode_ = *new_mode; + changed = true; + } + } + + if (changed) { + this->publish_state(); + } +} + +void TemplateWaterHeater::dump_config() { + LOG_WATER_HEATER("", "Template Water Heater", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); +} + +float TemplateWaterHeater::get_setup_priority() const { return setup_priority::HARDWARE; } + +water_heater::WaterHeaterCallInternal TemplateWaterHeater::make_call() { + return water_heater::WaterHeaterCallInternal(this); +} + +void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) { + if (call.get_mode().has_value()) { + if (this->optimistic_) { + this->mode_ = *call.get_mode(); + } + } + if (!std::isnan(call.get_target_temperature())) { + if (this->optimistic_) { + this->target_temperature_ = call.get_target_temperature(); + } + } + + this->set_trigger_->trigger(); + + if (this->optimistic_) { + this->publish_state(); + } +} + +} // namespace esphome::template_ diff --git a/esphome/components/template/water_heater/template_water_heater.h b/esphome/components/template/water_heater/template_water_heater.h new file mode 100644 index 0000000000..e5f51b72dc --- /dev/null +++ b/esphome/components/template/water_heater/template_water_heater.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/core/template_lambda.h" +#include "esphome/components/water_heater/water_heater.h" + +namespace esphome::template_ { + +enum TemplateWaterHeaterRestoreMode { + WATER_HEATER_NO_RESTORE, + WATER_HEATER_RESTORE, + WATER_HEATER_RESTORE_AND_CALL, +}; + +class TemplateWaterHeater : public water_heater::WaterHeater { + public: + TemplateWaterHeater(); + + template void set_current_temperature_lambda(F &&f) { + this->current_temperature_f_.set(std::forward(f)); + } + template void set_mode_lambda(F &&f) { this->mode_f_.set(std::forward(f)); } + + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + void set_restore_mode(TemplateWaterHeaterRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } + void set_supported_modes(const std::initializer_list &modes) { + this->supported_modes_ = modes; + } + + Trigger<> *get_set_trigger() const { return this->set_trigger_; } + + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override; + + water_heater::WaterHeaterCallInternal make_call() override; + + protected: + void control(const water_heater::WaterHeaterCall &call) override; + water_heater::WaterHeaterTraits traits() override; + + // Ordered to minimize padding on 32-bit: 4-byte members first, then smaller + Trigger<> *set_trigger_; + TemplateLambda current_temperature_f_; + TemplateLambda mode_f_; + TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE}; + water_heater::WaterHeaterModeMask supported_modes_; + bool optimistic_{true}; +}; + +} // namespace esphome::template_ diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index 933d82c85c..c2ade56f69 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -2,28 +2,35 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text { static const char *const TAG = "text"; -void Text::publish_state(const std::string &state) { - this->set_has_state(true); - this->state = state; - if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { - ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str()); +void Text::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } - } else { - ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); +void Text::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void Text::publish_state(const char *state, size_t len) { + this->set_has_state(true); + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); } - this->state_callback_.call(state); + if (this->traits.get_mode() == TEXT_MODE_PASSWORD) { + ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), this->state.c_str()); + } else { + ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), this->state.c_str()); + } + this->state_callback_.call(this->state); #if defined(USE_TEXT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_update(this); #endif } -void Text::add_on_state_callback(std::function &&callback) { +void Text::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index 74d08eda8a..e4ad64334b 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -27,11 +27,13 @@ class Text : public EntityBase { TextTraits traits; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Instantiate a TextCall object to modify this text component's state. TextCall make_call() { return TextCall(this); } - void add_on_state_callback(std::function &&callback); + void add_on_state_callback(std::function &&callback); protected: friend class TextCall; @@ -44,7 +46,7 @@ class Text : public EntityBase { */ virtual void control(const std::string &value) = 0; - CallbackManager state_callback_; + LazyCallbackManager state_callback_; }; } // namespace text diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 51923ebd96..66301564a4 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include namespace esphome { namespace text_sensor { @@ -24,22 +25,32 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text } } -void TextSensor::publish_state(const std::string &state) { -// Suppress deprecation warning - we need to populate raw_state for backwards compatibility +void TextSensor::publish_state(const std::string &state) { this->publish_state(state.data(), state.size()); } + +void TextSensor::publish_state(const char *state) { this->publish_state(state, strlen(state)); } + +void TextSensor::publish_state(const char *state, size_t len) { + if (this->filter_list_ == nullptr) { + // No filters: raw_state == state, store once and use for both callbacks + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } + this->raw_callback_.call(this->state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->state.c_str()); + this->notify_frontend_(); + } else { + // Has filters: need separate raw storage #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - this->raw_state = state; + // Only assign if changed to avoid heap allocation + if (len != this->raw_state.size() || memcmp(state, this->raw_state.data(), len) != 0) { + this->raw_state.assign(state, len); + } + this->raw_callback_.call(this->raw_state); + ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->raw_state.c_str()); + this->filter_list_->input(this->raw_state); #pragma GCC diagnostic pop - if (this->raw_callback_) { - this->raw_callback_->call(state); - } - - ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str()); - - if (this->filter_list_ == nullptr) { - this->internal_send_state_to_frontend(state); - } else { - this->filter_list_->input(state); } } @@ -73,18 +84,18 @@ void TextSensor::clear_filters() { this->filter_list_ = nullptr; } -void TextSensor::add_on_state_callback(std::function callback) { +void TextSensor::add_on_state_callback(std::function callback) { this->callback_.add(std::move(callback)); } -void TextSensor::add_on_raw_state_callback(std::function callback) { - if (!this->raw_callback_) { - this->raw_callback_ = make_unique>(); - } - this->raw_callback_->add(std::move(callback)); +void TextSensor::add_on_raw_state_callback(std::function callback) { + this->raw_callback_.add(std::move(callback)); } -std::string TextSensor::get_state() const { return this->state; } -std::string TextSensor::get_raw_state() const { +const std::string &TextSensor::get_state() const { return this->state; } +const std::string &TextSensor::get_raw_state() const { + if (this->filter_list_ == nullptr) { + return this->state; // No filters, raw == filtered + } // Suppress deprecation warning - get_raw_state() is the replacement API #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -92,10 +103,21 @@ std::string TextSensor::get_raw_state() const { #pragma GCC diagnostic pop } void TextSensor::internal_send_state_to_frontend(const std::string &state) { - this->state = state; + this->internal_send_state_to_frontend(state.data(), state.size()); +} + +void TextSensor::internal_send_state_to_frontend(const char *state, size_t len) { + // Only assign if changed to avoid heap allocation + if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) { + this->state.assign(state, len); + } + this->notify_frontend_(); +} + +void TextSensor::notify_frontend_() { this->set_has_state(true); - ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); - this->callback_.call(state); + ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), this->state.c_str()); + this->callback_.call(this->state); #if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_text_sensor_update(this); #endif diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index e411f57d67..1352a8c1e4 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -37,11 +37,13 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { #pragma GCC diagnostic pop /// Getter-syntax for .state. - std::string get_state() const; + const std::string &get_state() const; /// Getter-syntax for .raw_state - std::string get_raw_state() const; + const std::string &get_raw_state() const; void publish_state(const std::string &state); + void publish_state(const char *state); + void publish_state(const char *state, size_t len); /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -55,19 +57,21 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { /// Clear the entire filter chain. void clear_filters(); - void add_on_state_callback(std::function callback); + void add_on_state_callback(std::function callback); /// Add a callback that will be called every time the sensor sends a raw value. - void add_on_raw_state_callback(std::function callback); + void add_on_raw_state_callback(std::function callback); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) void internal_send_state_to_frontend(const std::string &state); + void internal_send_state_to_frontend(const char *state, size_t len); protected: - std::unique_ptr> - raw_callback_; ///< Storage for raw state callbacks (lazy allocated). - CallbackManager callback_; ///< Storage for filtered state callbacks. + /// Notify frontend that state has changed (assumes this->state is already set) + void notify_frontend_(); + LazyCallbackManager raw_callback_; ///< Storage for raw state callbacks. + LazyCallbackManager callback_; ///< Storage for filtered state callbacks. Filter *filter_list_{nullptr}; ///< Store all active filters. }; diff --git a/esphome/components/thermopro_ble/thermopro_ble.cpp b/esphome/components/thermopro_ble/thermopro_ble.cpp index 4b43c9b39e..2c90ee23f8 100644 --- a/esphome/components/thermopro_ble/thermopro_ble.cpp +++ b/esphome/components/thermopro_ble/thermopro_ble.cpp @@ -47,7 +47,8 @@ bool ThermoProBLE::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); // publish signal strength float signal_strength = float(device.get_rssi()); diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index a3c155aac0..f7c1298d68 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -153,6 +153,19 @@ def generate_comparable_preset(config, name): return comparable_preset +def validate_heat_cool_mode(value) -> list: + """Validate heat_cool_mode - accepts either True or an automation.""" + if value is True: + # Convert True to empty automation list + return [] + if value is False: + raise cv.Invalid( + "heat_cool_mode cannot be 'false'. Specify 'true' to enable the mode or provide an automation" + ) + # Otherwise validate as automation + return automation.validate_automation(single=True)(value) + + def validate_thermostat(config): # verify corresponding action(s) exist(s) for any defined climate mode or action requirements = { @@ -554,9 +567,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FAN_ONLY_MODE): automation.validate_automation( single=True ), - cv.Optional(CONF_HEAT_COOL_MODE): automation.validate_automation( - single=True - ), + cv.Optional(CONF_HEAT_COOL_MODE): validate_heat_cool_mode, cv.Optional(CONF_HEAT_MODE): automation.validate_automation(single=True), cv.Optional(CONF_OFF_MODE): automation.validate_automation(single=True), cv.Optional(CONF_FAN_MODE_ON_ACTION): automation.validate_automation( @@ -828,9 +839,11 @@ async def to_code(config): ) cg.add(var.set_supports_heat(True)) if CONF_HEAT_COOL_MODE in config: - await automation.build_automation( - var.get_heat_cool_mode_trigger(), [], config[CONF_HEAT_COOL_MODE] - ) + # Build automation only if user provided actions (not just `true`) + if config[CONF_HEAT_COOL_MODE]: + await automation.build_automation( + var.get_heat_cool_mode_trigger(), [], config[CONF_HEAT_COOL_MODE] + ) cg.add(var.set_supports_heat_cool(True)) if CONF_OFF_MODE in config: await automation.build_automation( diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index e79eed4055..0416438dcd 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -3,8 +3,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace thermostat { +namespace esphome::thermostat { static const char *const TAG = "thermostat.climate"; @@ -66,10 +65,12 @@ void ThermostatClimate::setup() { } void ThermostatClimate::loop() { - for (auto &timer : this->timer_) { - if (timer.active && (timer.started + timer.time < App.get_loop_component_start_time())) { + uint32_t now = App.get_loop_component_start_time(); + for (uint8_t i = 0; i < THERMOSTAT_TIMER_COUNT; i++) { + auto &timer = this->timer_[i]; + if (timer.active && (now - timer.started >= timer.time)) { timer.active = false; - timer.func(); + this->call_timer_callback_(static_cast(i)); } } } @@ -916,8 +917,42 @@ uint32_t ThermostatClimate::timer_duration_(ThermostatClimateTimerIndex timer_in return this->timer_[timer_index].time; } -std::function ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex timer_index) { - return this->timer_[timer_index].func; +void ThermostatClimate::call_timer_callback_(ThermostatClimateTimerIndex timer_index) { + switch (timer_index) { + case THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME: + this->cooling_max_run_time_timer_callback_(); + break; + case THERMOSTAT_TIMER_COOLING_OFF: + this->cooling_off_timer_callback_(); + break; + case THERMOSTAT_TIMER_COOLING_ON: + this->cooling_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_FAN_MODE: + this->fan_mode_timer_callback_(); + break; + case THERMOSTAT_TIMER_FANNING_OFF: + this->fanning_off_timer_callback_(); + break; + case THERMOSTAT_TIMER_FANNING_ON: + this->fanning_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME: + this->heating_max_run_time_timer_callback_(); + break; + case THERMOSTAT_TIMER_HEATING_OFF: + this->heating_off_timer_callback_(); + break; + case THERMOSTAT_TIMER_HEATING_ON: + this->heating_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_IDLE_ON: + this->idle_on_timer_callback_(); + break; + case THERMOSTAT_TIMER_COUNT: + default: + break; + } } void ThermostatClimate::cooling_max_run_time_timer_callback_() { @@ -1183,11 +1218,12 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { } } -void ThermostatClimate::change_custom_preset_(const char *custom_preset) { +void ThermostatClimate::change_custom_preset_(const char *custom_preset, size_t len) { // Linear search through custom preset configurations const ThermostatClimateTargetTempConfig *config = nullptr; for (const auto &entry : this->custom_preset_config_) { - if (strcmp(entry.name, custom_preset) == 0) { + // Compare first len chars, then verify entry.name ends there (same length) + if (strncmp(entry.name, custom_preset, len) == 0 && entry.name[len] == '\0') { config = &entry.config; break; } @@ -1196,7 +1232,7 @@ void ThermostatClimate::change_custom_preset_(const char *custom_preset) { if (config != nullptr) { ESP_LOGV(TAG, "Custom preset %s requested", custom_preset); if (this->change_preset_internal_(*config) || !this->has_custom_preset() || - strcmp(this->get_custom_preset(), custom_preset) != 0) { + this->get_custom_preset() != custom_preset) { // Fire any preset changed trigger if defined Trigger<> *trig = this->preset_change_trigger_; // Use the base class method which handles pointer lookup and preset reset internally @@ -1330,45 +1366,64 @@ void ThermostatClimate::set_heat_deadband(float deadband) { this->heating_deadba void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_ = overrun; } void ThermostatClimate::set_supplemental_cool_delta(float delta) { this->supplemental_cool_delta_ = delta; } void ThermostatClimate::set_supplemental_heat_delta(float delta) { this->supplemental_heat_delta_ = delta; } + +void ThermostatClimate::set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time) { + uint32_t new_duration_ms = 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + + if (this->timer_[timer_index].active) { + // Timer is running, calculate elapsed time and adjust if needed + uint32_t current_time = App.get_loop_component_start_time(); + uint32_t elapsed = current_time - this->timer_[timer_index].started; + + if (elapsed >= new_duration_ms) { + // Timer should complete immediately (including when new_duration_ms is 0) + ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %d >= new %d)", timer_index, elapsed, new_duration_ms); + this->timer_[timer_index].active = false; + // Trigger the timer callback immediately + this->call_timer_callback_(timer_index); + return; + } else { + // Adjust timer to run for remaining time - keep original start time + ESP_LOGVV(TAG, "timer %d adjusted: elapsed %d, new total %d, remaining %d", timer_index, elapsed, new_duration_ms, + new_duration_ms - elapsed); + this->timer_[timer_index].time = new_duration_ms; + return; + } + } + + // Original logic for non-running timers + this->timer_[timer_index].time = new_duration_ms; +} + void ThermostatClimate::set_cooling_maximum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME, time); } void ThermostatClimate::set_cooling_minimum_off_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_OFF].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_OFF, time); } void ThermostatClimate::set_cooling_minimum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_ON, time); } void ThermostatClimate::set_fan_mode_minimum_switching_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_FAN_MODE].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FAN_MODE, time); } void ThermostatClimate::set_fanning_minimum_off_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_OFF].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FANNING_OFF, time); } void ThermostatClimate::set_fanning_minimum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FANNING_ON, time); } void ThermostatClimate::set_heating_maximum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME, time); } void ThermostatClimate::set_heating_minimum_off_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_OFF].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_OFF, time); } void ThermostatClimate::set_heating_minimum_run_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_ON, time); } void ThermostatClimate::set_idle_minimum_time_in_sec(uint32_t time) { - this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time = - 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time); + this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_IDLE_ON, time); } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void ThermostatClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { @@ -1653,5 +1708,4 @@ ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float defau float default_temperature_high) : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {} -} // namespace thermostat -} // namespace esphome +} // namespace esphome::thermostat diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 69d2307b1c..d37c9a68a6 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -10,8 +10,7 @@ #include #include -namespace esphome { -namespace thermostat { +namespace esphome::thermostat { enum HumidificationAction : uint8_t { THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF = 0, @@ -41,13 +40,11 @@ enum OnBootRestoreFrom : uint8_t { struct ThermostatClimateTimer { ThermostatClimateTimer() = default; - ThermostatClimateTimer(bool active, uint32_t time, uint32_t started, std::function func) - : active(active), time(time), started(started), func(std::move(func)) {} + ThermostatClimateTimer(bool active, uint32_t time, uint32_t started) : active(active), time(time), started(started) {} bool active; uint32_t time; uint32_t started; - std::function func; }; struct ThermostatClimateTargetTempConfig { @@ -217,7 +214,13 @@ class ThermostatClimate : public climate::Climate, public Component { /// Change to a provided preset setting; will reset temperature, mode, fan, and swing modes accordingly void change_preset_(climate::ClimatePreset preset); /// Change to a provided custom preset setting; will reset temperature, mode, fan, and swing modes accordingly - void change_custom_preset_(const char *custom_preset); + void change_custom_preset_(const char *custom_preset) { + this->change_custom_preset_(custom_preset, strlen(custom_preset)); + } + void change_custom_preset_(const char *custom_preset, size_t len); + void change_custom_preset_(StringRef custom_preset) { + this->change_custom_preset_(custom_preset.c_str(), custom_preset.size()); + } /// Applies the temperature, mode, fan, and swing modes of the provided config. /// This is agnostic of custom vs built in preset @@ -266,7 +269,10 @@ class ThermostatClimate : public climate::Climate, public Component { bool cancel_timer_(ThermostatClimateTimerIndex timer_index); bool timer_active_(ThermostatClimateTimerIndex timer_index); uint32_t timer_duration_(ThermostatClimateTimerIndex timer_index); - std::function timer_cbf_(ThermostatClimateTimerIndex timer_index); + /// Call the appropriate timer callback based on timer index + void call_timer_callback_(ThermostatClimateTimerIndex timer_index); + /// Enhanced timer duration setter with running timer adjustment + void set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time); /// set_timeout() callbacks for various actions (see above) void cooling_max_run_time_timer_callback_(); @@ -532,27 +538,16 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_humidity_control_trigger_{nullptr}; /// Climate action timers - std::array timer_{ - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)), - ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)), - }; + std::array timer_{}; /// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc) FixedVector preset_config_{}; /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") FixedVector custom_preset_config_{}; - /// Default custom preset to use on start up (pointer to entry in custom_preset_config_) + private: + /// Default custom preset to use on start up (pointer to entry in custom_preset_config_) const char *default_custom_preset_{nullptr}; }; -} // namespace thermostat -} // namespace esphome +} // namespace esphome::thermostat diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index f7c1916ffe..8bc87878d1 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -4,8 +4,7 @@ #include -namespace esphome { -namespace time { +namespace esphome::time { static const char *const TAG = "automation"; static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider @@ -92,5 +91,4 @@ SyncTrigger::SyncTrigger(RealTimeClock *rtc) : rtc_(rtc) { rtc->add_on_time_sync_callback([this]() { this->trigger(); }); } -} // namespace time -} // namespace esphome +} // namespace esphome::time diff --git a/esphome/components/time/automation.h b/esphome/components/time/automation.h index b5c8291533..4ccfc641d6 100644 --- a/esphome/components/time/automation.h +++ b/esphome/components/time/automation.h @@ -8,8 +8,7 @@ #include -namespace esphome { -namespace time { +namespace esphome::time { class CronTrigger : public Trigger<>, public Component { public: @@ -48,5 +47,4 @@ class SyncTrigger : public Trigger<>, public Component { protected: RealTimeClock *rtc_; }; -} // namespace time -} // namespace esphome +} // namespace esphome::time diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 175cee0c1f..639af4457f 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -17,8 +17,7 @@ #include -namespace esphome { -namespace time { +namespace esphome::time { static const char *const TAG = "time"; @@ -78,5 +77,4 @@ void RealTimeClock::apply_timezone_() { } #endif -} // namespace time -} // namespace esphome +} // namespace esphome::time diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 2f17bd86d6..70469e11b0 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -7,8 +7,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/time.h" -namespace esphome { -namespace time { +namespace esphome::time { /// The RealTimeClock class exposes common timekeeping functions via the device's local real-time clock. /// @@ -75,5 +74,4 @@ template class TimeHasTimeCondition : public Condition { RealTimeClock *parent_; }; -} // namespace time -} // namespace esphome +} // namespace esphome::time diff --git a/esphome/components/tlc5947/tlc5947.cpp b/esphome/components/tlc5947/tlc5947.cpp index 6d4e099f7a..0a278bbaf6 100644 --- a/esphome/components/tlc5947/tlc5947.cpp +++ b/esphome/components/tlc5947/tlc5947.cpp @@ -21,12 +21,14 @@ void TLC5947::setup() { this->pwm_amounts_.resize(this->num_chips_ * N_CHANNELS_PER_CHIP, 0); } void TLC5947::dump_config() { - ESP_LOGCONFIG(TAG, "TLC5947:"); + ESP_LOGCONFIG(TAG, + "TLC5947:\n" + " Number of chips: %u", + this->num_chips_); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); LOG_PIN(" LAT Pin: ", this->lat_pin_); LOG_PIN(" OE Pin: ", this->outenable_pin_); - ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); } void TLC5947::loop() { diff --git a/esphome/components/tlc5971/tlc5971.cpp b/esphome/components/tlc5971/tlc5971.cpp index 719ab7c2b3..be17780f8c 100644 --- a/esphome/components/tlc5971/tlc5971.cpp +++ b/esphome/components/tlc5971/tlc5971.cpp @@ -15,10 +15,12 @@ void TLC5971::setup() { this->pwm_amounts_.resize(this->num_chips_ * N_CHANNELS_PER_CHIP, 0); } void TLC5971::dump_config() { - ESP_LOGCONFIG(TAG, "TLC5971:"); + ESP_LOGCONFIG(TAG, + "TLC5971:\n" + " Number of chips: %u", + this->num_chips_); LOG_PIN(" Data Pin: ", this->data_pin_); LOG_PIN(" Clock Pin: ", this->clock_pin_); - ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_); } void TLC5971::loop() { diff --git a/esphome/components/tmp1075/tmp1075.cpp b/esphome/components/tmp1075/tmp1075.cpp index 1d9b384c66..9eb1e86c75 100644 --- a/esphome/components/tmp1075/tmp1075.cpp +++ b/esphome/components/tmp1075/tmp1075.cpp @@ -73,12 +73,15 @@ void TMP1075Sensor::set_fault_count(const int faults) { } void TMP1075Sensor::log_config_() { - ESP_LOGV(TAG, " oneshot : %d", config_.fields.oneshot); - ESP_LOGV(TAG, " rate : %d", config_.fields.rate); - ESP_LOGV(TAG, " faults : %d", config_.fields.faults); - ESP_LOGV(TAG, " polarity : %d", config_.fields.polarity); - ESP_LOGV(TAG, " alert_mode: %d", config_.fields.alert_mode); - ESP_LOGV(TAG, " shutdown : %d", config_.fields.shutdown); + ESP_LOGV(TAG, + " oneshot : %d\n" + " rate : %d\n" + " faults : %d\n" + " polarity : %d\n" + " alert_mode: %d\n" + " shutdown : %d", + config_.fields.oneshot, config_.fields.rate, config_.fields.faults, config_.fields.polarity, + config_.fields.alert_mode, config_.fields.shutdown); } void TMP1075Sensor::write_config() { diff --git a/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp b/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp index edfbb2ac60..a63e9c8318 100644 --- a/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp +++ b/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp @@ -14,8 +14,10 @@ void TuyaBinarySensor::setup() { } void TuyaBinarySensor::dump_config() { - ESP_LOGCONFIG(TAG, "Tuya Binary Sensor:"); - ESP_LOGCONFIG(TAG, " Binary Sensor has datapoint ID %u", this->sensor_id_); + ESP_LOGCONFIG(TAG, + "Tuya Binary Sensor:\n" + " Binary Sensor has datapoint ID %u", + this->sensor_id_); } } // namespace tuya diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 1d2286e3c7..bf2d3daf98 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -26,6 +26,7 @@ CONF_RGB_DATAPOINT = "rgb_datapoint" CONF_HSV_DATAPOINT = "hsv_datapoint" CONF_COLOR_DATAPOINT = "color_datapoint" CONF_COLOR_TYPE = "color_type" +CONF_COLOR_TYPE_LOWERCASE = "color_type_lowercase" TuyaColorType = tuya_ns.enum("TuyaColorType") @@ -37,10 +38,6 @@ COLOR_TYPES = { TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component) -COLOR_CONFIG_ERROR = ( - "This option has been removed, use color_datapoint and color_type instead." -) - CONFIG_SCHEMA = cv.All( light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( { @@ -49,10 +46,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, - cv.Optional(CONF_RGB_DATAPOINT): cv.invalid(COLOR_CONFIG_ERROR), - cv.Optional(CONF_HSV_DATAPOINT): cv.invalid(COLOR_CONFIG_ERROR), cv.Inclusive(CONF_COLOR_DATAPOINT, "color"): cv.uint8_t, cv.Inclusive(CONF_COLOR_TYPE, "color"): cv.enum(COLOR_TYPES, upper=True), + cv.Optional(CONF_COLOR_TYPE_LOWERCASE, default=False): cv.boolean, cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, cv.Inclusive( CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature" @@ -97,6 +93,7 @@ async def to_code(config): if CONF_COLOR_DATAPOINT in config: cg.add(var.set_color_id(config[CONF_COLOR_DATAPOINT])) cg.add(var.set_color_type(config[CONF_COLOR_TYPE])) + cg.add(var.set_color_type_lowercase(config[CONF_COLOR_TYPE_LOWERCASE])) if CONF_COLOR_TEMPERATURE_DATAPOINT in config: cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT])) cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT])) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 815a089d9f..c487f9f50b 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -190,7 +190,8 @@ void TuyaLight::write_state(light::LightState *state) { switch (*this->color_type_) { case TuyaColorType::RGB: { char buffer[7]; - sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255)); + const char *format_str = this->color_type_lowercase_ ? "%02x%02x%02x" : "%02X%02X%02X"; + sprintf(buffer, format_str, int(red * 255), int(green * 255), int(blue * 255)); color_value = buffer; break; } @@ -199,7 +200,8 @@ void TuyaLight::write_state(light::LightState *state) { float saturation, value; rgb_to_hsv(red, green, blue, hue, saturation, value); char buffer[13]; - sprintf(buffer, "%04X%04X%04X", hue, int(saturation * 1000), int(value * 1000)); + const char *format_str = this->color_type_lowercase_ ? "%04x%04x%04x" : "%04X%04X%04X"; + sprintf(buffer, format_str, hue, int(saturation * 1000), int(value * 1000)); color_value = buffer; break; } @@ -208,8 +210,9 @@ void TuyaLight::write_state(light::LightState *state) { float saturation, value; rgb_to_hsv(red, green, blue, hue, saturation, value); char buffer[15]; - sprintf(buffer, "%02X%02X%02X%04X%02X%02X", int(red * 255), int(green * 255), int(blue * 255), hue, - int(saturation * 255), int(value * 255)); + const char *format_str = this->color_type_lowercase_ ? "%02x%02x%02x%04x%02x%02x" : "%02X%02X%02X%04X%02X%02X"; + sprintf(buffer, format_str, int(red * 255), int(green * 255), int(blue * 255), hue, int(saturation * 255), + int(value * 255)); color_value = buffer; break; } diff --git a/esphome/components/tuya/light/tuya_light.h b/esphome/components/tuya/light/tuya_light.h index bd9920f18f..ded94f390a 100644 --- a/esphome/components/tuya/light/tuya_light.h +++ b/esphome/components/tuya/light/tuya_light.h @@ -7,11 +7,7 @@ namespace esphome { namespace tuya { -enum TuyaColorType { - RGB, - HSV, - RGBHSV, -}; +enum TuyaColorType { RGB, HSV, RGBHSV }; class TuyaLight : public Component, public light::LightOutput { public: @@ -28,6 +24,7 @@ class TuyaLight : public Component, public light::LightOutput { void set_color_temperature_invert(bool color_temperature_invert) { this->color_temperature_invert_ = color_temperature_invert; } + void set_color_type_lowercase(bool color_type_lowercase) { this->color_type_lowercase_ = color_type_lowercase; } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } void set_min_value(uint32_t min_value) { min_value_ = min_value; } void set_max_value(uint32_t max_value) { max_value_ = max_value; } @@ -63,6 +60,7 @@ class TuyaLight : public Component, public light::LightOutput { float cold_white_temperature_; float warm_white_temperature_; bool color_temperature_invert_{false}; + bool color_type_lowercase_{false}; bool color_interlock_{false}; light::LightState *state_{nullptr}; }; diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index fbe511811f..36b6d630ae 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -1,4 +1,5 @@ #include "tuya_text_sensor.h" +#include "esphome/core/entity_base.h" #include "esphome/core/log.h" namespace esphome { @@ -14,15 +15,18 @@ void TuyaTextSensor::setup() { this->publish_state(datapoint.value_string); break; case TuyaDatapointType::RAW: { - std::string data = format_hex_pretty(datapoint.value_raw); - ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); - this->publish_state(data); + char hex_buf[MAX_STATE_LEN + 1]; + const char *formatted = + format_hex_pretty_to(hex_buf, sizeof(hex_buf), datapoint.value_raw.data(), datapoint.value_raw.size()); + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, formatted); + this->publish_state(formatted); break; } case TuyaDatapointType::ENUM: { - std::string data = to_string(datapoint.value_enum); - ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); - this->publish_state(data); + char buf[4]; // uint8_t max is 3 digits + null + snprintf(buf, sizeof(buf), "%u", datapoint.value_enum); + ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, buf); + this->publish_state(buf); break; } default: @@ -33,8 +37,10 @@ void TuyaTextSensor::setup() { } void TuyaTextSensor::dump_config() { - ESP_LOGCONFIG(TAG, "Tuya Text Sensor:"); - ESP_LOGCONFIG(TAG, " Text Sensor has datapoint ID %u", this->sensor_id_); + ESP_LOGCONFIG(TAG, + "Tuya Text Sensor:\n" + " Text Sensor has datapoint ID %u", + this->sensor_id_); } } // namespace tuya diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 12b14be9ff..2812fb6ad6 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -20,6 +20,8 @@ static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; static const int MAX_RETRIES = 5; +// Max bytes to log for datapoint values (larger values are truncated) +static constexpr size_t MAX_DATAPOINT_LOG_BYTES = 16; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); @@ -51,7 +53,9 @@ void Tuya::dump_config() { } for (auto &info : this->datapoints_) { if (info.type == TuyaDatapointType::RAW) { - ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str()); + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, + format_hex_pretty_to(hex_buf, info.value_raw.data(), info.value_raw.size())); } else if (info.type == TuyaDatapointType::BOOLEAN) { ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool)); } else if (info.type == TuyaDatapointType::INTEGER) { @@ -122,8 +126,11 @@ bool Tuya::validate_message_() { // valid message const uint8_t *message_data = data + 6; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, - format_hex_pretty(message_data, length).c_str(), static_cast(this->init_state_)); + format_hex_pretty_to(hex_buf, message_data, length), static_cast(this->init_state_)); +#endif this->handle_command_(command, version, message_data, length); // return false to reset rx buffer @@ -349,7 +356,11 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { switch (datapoint.type) { case TuyaDatapointType::RAW: datapoint.value_raw = std::vector(data, data + data_size); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); + { + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, + format_hex_pretty_to(hex_buf, datapoint.value_raw.data(), datapoint.value_raw.size())); + } break; case TuyaDatapointType::BOOLEAN: if (data_size != 1) { @@ -460,8 +471,12 @@ void Tuya::send_raw_command_(TuyaCommand command) { break; } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), - version, format_hex_pretty(command.payload).c_str(), static_cast(this->init_state_)); + version, format_hex_pretty_to(hex_buf, command.payload.data(), command.payload.size()), + static_cast(this->init_state_)); +#endif this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); if (!command.payload.empty()) @@ -675,7 +690,8 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str()); + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty_to(hex_buf, value.data(), value.size())); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 6494aaa286..31e37a06e0 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -19,7 +19,6 @@ from esphome.const import ( CONF_DUMMY_RECEIVER_ID, CONF_FLOW_CONTROL_PIN, CONF_ID, - CONF_INVERT, CONF_LAMBDA, CONF_NUMBER, CONF_PORT, @@ -304,9 +303,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PARITY, default="NONE"): cv.enum( UART_PARITY_OPTIONS, upper=True ), - cv.Optional(CONF_INVERT): cv.invalid( - "This option has been removed. Please instead use invert in the tx/rx pin schemas." - ), cv.Optional(CONF_DEBUG): maybe_empty_debug, } ).extend(cv.COMPONENT_SCHEMA), @@ -382,6 +378,28 @@ async def to_code(config): if CONF_DEBUG in config: await debug_to_code(config[CONF_DEBUG], var) + # ESP8266: Enable the Arduino Serial objects that might be used based on pin config + # The C++ code selects hardware serial at runtime based on these pin combinations: + # - Serial (UART0): TX=1 or null, RX=3 or null + # - Serial (UART0 swap): TX=15 or null, RX=13 or null + # - Serial1: TX=2 or null, RX=8 or null + if CORE.is_esp8266: + from esphome.components.esp8266.const import enable_serial, enable_serial1 + + tx_num = config[CONF_TX_PIN][CONF_NUMBER] if CONF_TX_PIN in config else None + rx_num = config[CONF_RX_PIN][CONF_NUMBER] if CONF_RX_PIN in config else None + + # Check if this config could use Serial (UART0 regular or swap) + if (tx_num is None or tx_num in (1, 15)) and ( + rx_num is None or rx_num in (3, 13) + ): + enable_serial() + cg.add_define("USE_ESP8266_UART_SERIAL") + # Check if this config could use Serial1 + if (tx_num is None or tx_num == 2) and (rx_num is None or rx_num == 8): + enable_serial1() + cg.add_define("USE_ESP8266_UART_SERIAL1") + CORE.add_job(final_step) @@ -486,7 +504,7 @@ def final_validate_device_schema( async def register_uart_device(var, config): """Register a UART device, setting up all the internal values. - This is a coroutine, you need to await it with a 'yield' expression! + This is a coroutine, you need to await it with an 'await' expression! """ parent = await cg.get_variable(config[CONF_UART_ID]) cg.add(var.set_uart_parent(parent)) diff --git a/esphome/components/uart/event/__init__.py b/esphome/components/uart/event/__init__.py new file mode 100644 index 0000000000..64af318a11 --- /dev/null +++ b/esphome/components/uart/event/__init__.py @@ -0,0 +1,90 @@ +import esphome.codegen as cg +from esphome.components import event, uart +import esphome.config_validation as cv +from esphome.const import CONF_EVENT_TYPES, CONF_ID +from esphome.core import ID +from esphome.types import ConfigType + +from .. import uart_ns + +CODEOWNERS = ["@eoasmxd"] + +DEPENDENCIES = ["uart"] + +UARTEvent = uart_ns.class_("UARTEvent", event.Event, uart.UARTDevice, cg.Component) + + +def validate_event_types(value) -> list[tuple[str, str | list[int]]]: + if not isinstance(value, list): + raise cv.Invalid("Event type must be a list of key-value mappings.") + + processed: list[tuple[str, str | list[int]]] = [] + for item in value: + if not isinstance(item, dict): + raise cv.Invalid(f"Event type item must be a mapping (dictionary): {item}") + if len(item) != 1: + raise cv.Invalid( + f"Event type item must be a single key-value mapping: {item}" + ) + + # Get the single key-value pair + event_name, match_data = next(iter(item.items())) + + if not isinstance(event_name, str): + raise cv.Invalid(f"Event name (key) must be a string: {event_name}") + + try: + # Try to validate as list of hex bytes + match_data_bin = cv.ensure_list(cv.hex_uint8_t)(match_data) + processed.append((event_name, match_data_bin)) + continue + except cv.Invalid: + pass # Not binary, try string + + try: + # Try to validate as string + match_data_str = cv.string_strict(match_data) + processed.append((event_name, match_data_str)) + continue + except cv.Invalid: + pass # Not string either + + # If neither validation passed + raise cv.Invalid( + f"Event match data for '{event_name}' must be a string or a list of hex bytes. Invalid data: {match_data}" + ) + + if not processed: + raise cv.Invalid("event_types must contain at least one event mapping.") + + return processed + + +CONFIG_SCHEMA = ( + event.event_schema(UARTEvent) + .extend( + { + cv.Required(CONF_EVENT_TYPES): validate_event_types, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config: ConfigType) -> None: + event_names = [item[0] for item in config[CONF_EVENT_TYPES]] + var = await event.new_event(config, event_types=event_names) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + for i, (event_name, match_data) in enumerate(config[CONF_EVENT_TYPES]): + if isinstance(match_data, str): + match_data = [ord(c) for c in match_data] + + match_data_var_id = ID( + f"match_data_{config[CONF_ID]}_{i}", is_declaration=True, type=cg.uint8 + ) + match_data_var = cg.static_const_array( + match_data_var_id, cg.ArrayInitializer(*match_data) + ) + cg.add(var.add_event_matcher(event_name, match_data_var, len(match_data))) diff --git a/esphome/components/uart/event/uart_event.cpp b/esphome/components/uart/event/uart_event.cpp new file mode 100644 index 0000000000..02c5f2e631 --- /dev/null +++ b/esphome/components/uart/event/uart_event.cpp @@ -0,0 +1,48 @@ +#include "uart_event.h" +#include "esphome/core/log.h" +#include + +namespace esphome::uart { + +static const char *const TAG = "uart.event"; + +void UARTEvent::setup() {} + +void UARTEvent::dump_config() { LOG_EVENT("", "UART Event", this); } + +void UARTEvent::loop() { this->read_data_(); } + +void UARTEvent::add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len) { + this->matchers_.push_back({event_name, match_data, match_data_len}); + if (match_data_len > this->max_matcher_len_) { + this->max_matcher_len_ = match_data_len; + } +} + +void UARTEvent::read_data_() { + while (this->available()) { + uint8_t data; + this->read_byte(&data); + this->buffer_.push_back(data); + + bool match_found = false; + for (const auto &matcher : this->matchers_) { + if (this->buffer_.size() < matcher.data_len) { + continue; + } + + if (std::equal(matcher.data, matcher.data + matcher.data_len, this->buffer_.end() - matcher.data_len)) { + this->trigger(matcher.event_name); + this->buffer_.clear(); + match_found = true; + break; + } + } + + if (!match_found && this->max_matcher_len_ > 0 && this->buffer_.size() > this->max_matcher_len_) { + this->buffer_.erase(this->buffer_.begin()); + } + } +} + +} // namespace esphome::uart diff --git a/esphome/components/uart/event/uart_event.h b/esphome/components/uart/event/uart_event.h new file mode 100644 index 0000000000..8a00b5894b --- /dev/null +++ b/esphome/components/uart/event/uart_event.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/event/event.h" +#include "esphome/components/uart/uart.h" +#include + +namespace esphome::uart { + +class UARTEvent : public event::Event, public UARTDevice, public Component { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len); + + protected: + struct EventMatcher { + const char *event_name; + const uint8_t *data; + size_t data_len; + }; + + void read_data_(); + std::vector matchers_; + std::vector buffer_; + size_t max_matcher_len_ = 0; +}; + +} // namespace esphome::uart diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index fd528e228f..ea6e1562f4 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -189,10 +189,10 @@ class UARTComponent { size_t rx_buffer_size_; size_t rx_full_threshold_{1}; size_t rx_timeout_{0}; - uint32_t baud_rate_; - uint8_t stop_bits_; - uint8_t data_bits_; - UARTParityOptions parity_; + uint32_t baud_rate_{0}; + uint8_t stop_bits_{0}; + uint8_t data_bits_{0}; + UARTParityOptions parity_{UART_CONFIG_PARITY_NONE}; #ifdef USE_UART_DEBUGGER CallbackManager debug_callback_{}; #endif diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index c78daa7462..504d494e2e 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -75,6 +75,7 @@ void ESP8266UartComponent::setup() { // is 1 we still want to use Serial. SerialConfig config = static_cast(get_config()); +#ifdef USE_ESP8266_UART_SERIAL if (!ESP8266UartComponent::serial0_in_use && (tx_pin_ == nullptr || tx_pin_->get_pin() == 1) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 3) #ifdef USE_LOGGER @@ -100,11 +101,16 @@ void ESP8266UartComponent::setup() { this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); this->hw_serial_->swap(); ESP8266UartComponent::serial0_in_use = true; - } else if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { + } else +#endif // USE_ESP8266_UART_SERIAL +#ifdef USE_ESP8266_UART_SERIAL1 + if ((tx_pin_ == nullptr || tx_pin_->get_pin() == 2) && (rx_pin_ == nullptr || rx_pin_->get_pin() == 8)) { this->hw_serial_ = &Serial1; this->hw_serial_->begin(this->baud_rate_, config); this->hw_serial_->setRxBufferSize(this->rx_buffer_size_); - } else { + } else +#endif // USE_ESP8266_UART_SERIAL1 + { this->sw_serial_ = new ESP8266SoftwareSerial(); // NOLINT this->sw_serial_->setup(tx_pin_, rx_pin_, this->baud_rate_, this->stop_bits_, this->data_bits_, this->parity_, this->rx_buffer_size_); diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index b4f6eedf91..90997787aa 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -398,14 +398,18 @@ void IDFUARTComponent::rx_event_task_func(void *param) { case UART_DATA: // Data available in UART RX buffer - wake the main loop ESP_LOGVV(TAG, "Data event: %d bytes", event.size); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); +#endif break; case UART_FIFO_OVF: case UART_BUFFER_FULL: ESP_LOGW(TAG, "FIFO overflow or ring buffer full - clearing"); uart_flush_input(self->uart_num_); +#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); +#endif break; default: diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 01c7063fe8..863732c88d 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -120,8 +120,10 @@ void LibreTinyUARTComponent::setup() { void LibreTinyUARTComponent::dump_config() { bool is_software = this->hardware_idx_ == -1; - ESP_LOGCONFIG(TAG, "UART Bus:"); - ESP_LOGCONFIG(TAG, " Type: %s", UART_TYPE[is_software]); + ESP_LOGCONFIG(TAG, + "UART Bus:\n" + " Type: %s", + UART_TYPE[is_software]); if (!is_software) { ESP_LOGCONFIG(TAG, " Port number: %d", this->hardware_idx_); } diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index 9105ced21e..4474efeb77 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -65,11 +65,14 @@ void UDPComponent::setup() { server.sin_port = htons(this->listen_port_); if (this->listen_address_.has_value()) { + // Only 16 bytes needed for IPv4, but use standard size for consistency + char addr_buf[network::IP_ADDRESS_BUFFER_SIZE]; + this->listen_address_.value().str_to(addr_buf); struct ip_mreq imreq = {}; imreq.imr_interface.s_addr = ESPHOME_INADDR_ANY; - inet_aton(this->listen_address_.value().str().c_str(), &imreq.imr_multiaddr); + inet_aton(addr_buf, &imreq.imr_multiaddr); server.sin_addr.s_addr = imreq.imr_multiaddr.s_addr; - ESP_LOGD(TAG, "Join multicast %s", this->listen_address_.value().str().c_str()); + ESP_LOGD(TAG, "Join multicast %s", addr_buf); err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq)); if (err < 0) { ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno); @@ -130,7 +133,8 @@ void UDPComponent::dump_config() { for (const auto &address : this->addresses_) ESP_LOGCONFIG(TAG, " Address: %s", address.c_str()); if (this->listen_address_.has_value()) { - ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str().c_str()); + char addr_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str_to(addr_buf)); } ESP_LOGCONFIG(TAG, " Broadcasting: %s\n" diff --git a/esphome/components/ufire_ec/ufire_ec.cpp b/esphome/components/ufire_ec/ufire_ec.cpp index 0a57ecc67b..3868dc92b7 100644 --- a/esphome/components/ufire_ec/ufire_ec.cpp +++ b/esphome/components/ufire_ec/ufire_ec.cpp @@ -102,16 +102,16 @@ void UFireECComponent::write_data_(uint8_t reg, float data) { } void UFireECComponent::dump_config() { - ESP_LOGCONFIG(TAG, "uFire-EC"); + ESP_LOGCONFIG(TAG, + "uFire-EC:\n" + " Temperature Compensation: %f\n" + " Temperature Coefficient: %f", + this->temperature_compensation_, this->temperature_coefficient_); LOG_I2C_DEVICE(this) LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_); LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_); - ESP_LOGCONFIG(TAG, - " Temperature Compensation: %f\n" - " Temperature Coefficient: %f", - this->temperature_compensation_, this->temperature_coefficient_); } } // namespace ufire_ec diff --git a/esphome/components/uln2003/uln2003.cpp b/esphome/components/uln2003/uln2003.cpp index 991fe53487..11e1c3d4c0 100644 --- a/esphome/components/uln2003/uln2003.cpp +++ b/esphome/components/uln2003/uln2003.cpp @@ -34,12 +34,14 @@ void ULN2003::loop() { this->write_step_(this->current_uln_pos_); } void ULN2003::dump_config() { - ESP_LOGCONFIG(TAG, "ULN2003:"); + ESP_LOGCONFIG(TAG, + "ULN2003:\n" + " Sleep when done: %s", + YESNO(this->sleep_when_done_)); LOG_PIN(" Pin A: ", this->pin_a_); LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin C: ", this->pin_c_); LOG_PIN(" Pin D: ", this->pin_d_); - ESP_LOGCONFIG(TAG, " Sleep when done: %s", YESNO(this->sleep_when_done_)); const char *step_mode_s; switch (this->step_mode_) { case ULN2003_STEP_MODE_FULL_STEP: diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index 937d9a5261..4b04ee7578 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -1,3 +1,5 @@ +import logging + from esphome import pins import esphome.codegen as cg from esphome.components import sensor @@ -11,6 +13,8 @@ from esphome.const import ( UNIT_METER, ) +_LOGGER = logging.getLogger(__name__) + CONF_PULSE_TIME = "pulse_time" ultrasonic_ns = cg.esphome_ns.namespace("ultrasonic") @@ -28,9 +32,9 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_TRIGGER_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, - cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance, + cv.Optional(CONF_TIMEOUT): cv.distance, cv.Optional( CONF_PULSE_TIME, default="10us" ): cv.positive_time_period_microseconds, @@ -49,5 +53,11 @@ async def to_code(config): echo = await cg.gpio_pin_expression(config[CONF_ECHO_PIN]) cg.add(var.set_echo_pin(echo)) - cg.add(var.set_timeout_us(config[CONF_TIMEOUT] / (0.000343 / 2))) + # Remove before 2026.8.0 + if CONF_TIMEOUT in config: + _LOGGER.warning( + "'timeout' option is deprecated and will be removed in 2026.8.0. " + "The option has no effect and can be safely removed." + ) + cg.add(var.set_pulse_time_us(config[CONF_PULSE_TIME])) diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index e864ea6419..369a10edbd 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -1,64 +1,89 @@ #include "ultrasonic_sensor.h" -#include "esphome/core/log.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" -namespace esphome { -namespace ultrasonic { +namespace esphome::ultrasonic { static const char *const TAG = "ultrasonic.sensor"; +static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering) +static constexpr uint32_t MEASUREMENT_TIMEOUT_US = 80000; // Maximum time to wait for measurement completion + +void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) { + uint32_t now = micros(); + if (!arg->echo_start || (now - arg->echo_start_us) <= DEBOUNCE_US) { + arg->echo_start_us = now; + arg->echo_start = true; + } else { + arg->echo_end_us = now; + arg->echo_end = true; + } +} + +void IRAM_ATTR UltrasonicSensorComponent::send_trigger_pulse_() { + InterruptLock lock; + this->store_.echo_start_us = 0; + this->store_.echo_end_us = 0; + this->store_.echo_start = false; + this->store_.echo_end = false; + this->trigger_pin_isr_.digital_write(true); + delayMicroseconds(this->pulse_time_us_); + this->trigger_pin_isr_.digital_write(false); + this->measurement_pending_ = true; + this->measurement_start_us_ = micros(); +} + void UltrasonicSensorComponent::setup() { this->trigger_pin_->setup(); this->trigger_pin_->digital_write(false); + this->trigger_pin_isr_ = this->trigger_pin_->to_isr(); this->echo_pin_->setup(); - // isr is faster to access - echo_isr_ = echo_pin_->to_isr(); + this->echo_pin_->attach_interrupt(UltrasonicSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); } + void UltrasonicSensorComponent::update() { - this->trigger_pin_->digital_write(true); - delayMicroseconds(this->pulse_time_us_); - this->trigger_pin_->digital_write(false); + if (this->measurement_pending_) { + return; + } + this->send_trigger_pulse_(); +} - const uint32_t start = micros(); - while (micros() - start < timeout_us_ && echo_isr_.digital_read()) - ; - while (micros() - start < timeout_us_ && !echo_isr_.digital_read()) - ; - const uint32_t pulse_start = micros(); - while (micros() - start < timeout_us_ && echo_isr_.digital_read()) - ; - const uint32_t pulse_end = micros(); +void UltrasonicSensorComponent::loop() { + if (!this->measurement_pending_) { + return; + } - ESP_LOGV(TAG, "Echo took %" PRIu32 "µs", pulse_end - pulse_start); - - if (pulse_end - start >= timeout_us_) { - ESP_LOGD(TAG, "'%s' - Distance measurement timed out!", this->name_.c_str()); - this->publish_state(NAN); - } else { - float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start); + if (this->store_.echo_end) { + uint32_t pulse_duration = this->store_.echo_end_us - this->store_.echo_start_us; + ESP_LOGV(TAG, "Echo took %" PRIu32 "us", pulse_duration); + float result = UltrasonicSensorComponent::us_to_m(pulse_duration); ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result); this->publish_state(result); + this->measurement_pending_ = false; + return; + } + + uint32_t elapsed = micros() - this->measurement_start_us_; + if (elapsed >= MEASUREMENT_TIMEOUT_US) { + ESP_LOGD(TAG, "'%s' - Measurement timed out after %" PRIu32 "us", this->name_.c_str(), elapsed); + this->publish_state(NAN); + this->measurement_pending_ = false; } } + void UltrasonicSensorComponent::dump_config() { LOG_SENSOR("", "Ultrasonic Sensor", this); LOG_PIN(" Echo Pin: ", this->echo_pin_); LOG_PIN(" Trigger Pin: ", this->trigger_pin_); - ESP_LOGCONFIG(TAG, - " Pulse time: %" PRIu32 " µs\n" - " Timeout: %" PRIu32 " µs", - this->pulse_time_us_, this->timeout_us_); + ESP_LOGCONFIG(TAG, " Pulse time: %" PRIu32 " us", this->pulse_time_us_); LOG_UPDATE_INTERVAL(this); } + float UltrasonicSensorComponent::us_to_m(uint32_t us) { const float speed_sound_m_per_s = 343.0f; const float time_s = us / 1e6f; const float total_dist = time_s * speed_sound_m_per_s; return total_dist / 2.0f; } -float UltrasonicSensorComponent::get_setup_priority() const { return setup_priority::DATA; } -void UltrasonicSensorComponent::set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; } -void UltrasonicSensorComponent::set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; } -} // namespace ultrasonic -} // namespace esphome +} // namespace esphome::ultrasonic diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.h b/esphome/components/ultrasonic/ultrasonic_sensor.h index 1a255d6122..b0c00e51f0 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.h +++ b/esphome/components/ultrasonic/ultrasonic_sensor.h @@ -6,41 +6,45 @@ #include -namespace esphome { -namespace ultrasonic { +namespace esphome::ultrasonic { + +struct UltrasonicSensorStore { + static void gpio_intr(UltrasonicSensorStore *arg); + + volatile uint32_t echo_start_us{0}; + volatile uint32_t echo_end_us{0}; + volatile bool echo_start{false}; + volatile bool echo_end{false}; +}; class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent { public: - void set_trigger_pin(GPIOPin *trigger_pin) { trigger_pin_ = trigger_pin; } - void set_echo_pin(InternalGPIOPin *echo_pin) { echo_pin_ = echo_pin; } + void set_trigger_pin(InternalGPIOPin *trigger_pin) { this->trigger_pin_ = trigger_pin; } + void set_echo_pin(InternalGPIOPin *echo_pin) { this->echo_pin_ = echo_pin; } - /// Set the timeout for waiting for the echo in µs. - void set_timeout_us(uint32_t timeout_us); - - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Set up pins and register interval. void setup() override; + void loop() override; void dump_config() override; - void update() override; - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::DATA; } /// Set the time in µs the trigger pin should be enabled for in µs, defaults to 10µs (for HC-SR04) - void set_pulse_time_us(uint32_t pulse_time_us); + void set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; } protected: /// Helper function to convert the specified echo duration in µs to meters. static float us_to_m(uint32_t us); - /// Helper function to convert the specified distance in meters to the echo duration in µs. + void send_trigger_pulse_(); - GPIOPin *trigger_pin_; + InternalGPIOPin *trigger_pin_; + ISRInternalGPIOPin trigger_pin_isr_; InternalGPIOPin *echo_pin_; - ISRInternalGPIOPin echo_isr_; - uint32_t timeout_us_{}; /// 2 meters. + UltrasonicSensorStore store_; uint32_t pulse_time_us_{}; + + uint32_t measurement_start_us_{0}; + bool measurement_pending_{false}; }; -} // namespace ultrasonic -} // namespace esphome +} // namespace esphome::ultrasonic diff --git a/esphome/components/update/__init__.py b/esphome/components/update/__init__.py index 7a381c85a8..e146f7e685 100644 --- a/esphome/components/update/__init__.py +++ b/esphome/components/update/__init__.py @@ -29,6 +29,9 @@ UpdateInfo = update_ns.struct("UpdateInfo") PerformAction = update_ns.class_( "PerformAction", automation.Action, cg.Parented.template(UpdateEntity) ) +CheckAction = update_ns.class_( + "CheckAction", automation.Action, cg.Parented.template(UpdateEntity) +) IsAvailableCondition = update_ns.class_( "IsAvailableCondition", automation.Condition, cg.Parented.template(UpdateEntity) ) @@ -143,6 +146,21 @@ async def update_perform_action_to_code(config, action_id, template_arg, args): return var +@automation.register_action( + "update.check", + CheckAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(UpdateEntity), + } + ), +) +async def update_check_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + @automation.register_condition( "update.is_available", IsAvailableCondition, diff --git a/esphome/components/update/automation.h b/esphome/components/update/automation.h index 8563b855fe..af24c838b1 100644 --- a/esphome/components/update/automation.h +++ b/esphome/components/update/automation.h @@ -14,6 +14,11 @@ template class PerformAction : public Action, public Pare void play(const Ts &...x) override { this->parent_->perform(this->force_.value(x...)); } }; +template class CheckAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->check(); } +}; + template class IsAvailableCondition : public Condition, public Parented { public: bool check(const Ts &...x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; } diff --git a/esphome/components/update/update_entity.cpp b/esphome/components/update/update_entity.cpp index 567fc9fc8e..6d13341a8a 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -9,8 +9,10 @@ namespace update { static const char *const TAG = "update"; void UpdateEntity::publish_state() { - ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str()); - ESP_LOGD(TAG, " Current Version: %s", this->update_info_.current_version.c_str()); + ESP_LOGD(TAG, + "'%s' - Publishing:\n" + " Current Version: %s", + this->name_.c_str(), this->update_info_.current_version.c_str()); if (!this->update_info_.md5.empty()) { ESP_LOGD(TAG, " Latest Version: %s", this->update_info_.latest_version.c_str()); diff --git a/esphome/components/update/update_entity.h b/esphome/components/update/update_entity.h index 9424e80b9f..8eba78b44b 100644 --- a/esphome/components/update/update_entity.h +++ b/esphome/components/update/update_entity.h @@ -50,7 +50,7 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass { UpdateState state_{UPDATE_STATE_UNKNOWN}; UpdateInfo update_info_; - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; std::unique_ptr> update_available_trigger_{nullptr}; }; diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.cpp b/esphome/components/uponor_smatrix/uponor_smatrix.cpp index 221f07c80e..4c3a4b05df 100644 --- a/esphome/components/uponor_smatrix/uponor_smatrix.cpp +++ b/esphome/components/uponor_smatrix/uponor_smatrix.cpp @@ -8,6 +8,9 @@ namespace uponor_smatrix { static const char *const TAG = "uponor_smatrix"; +// Maximum bytes to log in verbose hex output +static constexpr size_t UPONOR_MAX_LOG_BYTES = 36; + void UponorSmatrixComponent::setup() { #ifdef USE_TIME if (this->time_id_ != nullptr) { @@ -97,8 +100,11 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) { return false; } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_size(UPONOR_MAX_LOG_BYTES)]; +#endif ESP_LOGV(TAG, "Received packet: addr=%08X, data=%s, crc=%04X", device_address, - format_hex(&packet[4], packet_len - 6).c_str(), crc); + format_hex_to(hex_buf, &packet[4], packet_len - 6), crc); // Handle packet size_t data_len = (packet_len - 6) / 3; diff --git a/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp index 94585379fe..b7b3273f39 100644 --- a/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +++ b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp @@ -9,6 +9,19 @@ namespace uptime { static const char *const TAG = "uptime.sensor"; +// Clamp position to valid buffer range when snprintf indicates truncation +static size_t clamp_buffer_pos(size_t pos, size_t buf_size) { return pos < buf_size ? pos : buf_size - 1; } + +static void append_unit(char *buf, size_t buf_size, size_t &pos, const char *separator, unsigned value, + const char *label) { + if (pos > 0) { + pos += snprintf(buf + pos, buf_size - pos, "%s", separator); + pos = clamp_buffer_pos(pos, buf_size); + } + pos += snprintf(buf + pos, buf_size - pos, "%u%s", value, label); + pos = clamp_buffer_pos(pos, buf_size); +} + void UptimeTextSensor::setup() { this->last_ms_ = millis(); if (this->last_ms_ < 60 * 1000) @@ -16,11 +29,6 @@ void UptimeTextSensor::setup() { this->update(); } -void UptimeTextSensor::insert_buffer_(std::string &buffer, const char *key, unsigned value) const { - buffer.insert(0, this->separator_); - buffer.insert(0, str_sprintf("%u%s", value, key)); -} - void UptimeTextSensor::update() { auto now = millis(); // get whole seconds since last update. Note that even if the millis count has overflowed between updates, @@ -29,36 +37,58 @@ void UptimeTextSensor::update() { this->last_ms_ = now - delta % 1000; // save remainder for next update delta /= 1000; this->uptime_ += delta; - auto uptime = this->uptime_; + uint32_t uptime = this->uptime_; unsigned interval = this->get_update_interval() / 1000; - std::string buffer{}; - // display from the largest unit that corresponds to the update interval, drop larger units that are zero. - while (true) { // enable use of break for early exit - unsigned remainder = uptime % 60; - uptime /= 60; - if (interval < 30) { - this->insert_buffer_(buffer, this->seconds_text_, remainder); - if (!this->expand_ && uptime == 0) - break; + + // Calculate all time units + unsigned seconds = uptime % 60; + uptime /= 60; + unsigned minutes = uptime % 60; + uptime /= 60; + unsigned hours = uptime % 24; + uptime /= 24; + unsigned days = uptime; + + // Determine which units to display based on interval thresholds + bool seconds_enabled = interval < 30; + bool minutes_enabled = interval < 1800; + bool hours_enabled = interval < 12 * 3600; + + // Show from highest non-zero unit (or all in expand mode) down to smallest enabled + bool show_days = this->expand_ || days > 0; + bool show_hours = hours_enabled && (show_days || hours > 0); + bool show_minutes = minutes_enabled && (show_hours || minutes > 0); + bool show_seconds = seconds_enabled && (show_minutes || seconds > 0); + + // If nothing shown, show smallest enabled unit + if (!show_days && !show_hours && !show_minutes && !show_seconds) { + if (seconds_enabled) { + show_seconds = true; + } else if (minutes_enabled) { + show_minutes = true; + } else if (hours_enabled) { + show_hours = true; + } else { + show_days = true; } - remainder = uptime % 60; - uptime /= 60; - if (interval < 1800) { - this->insert_buffer_(buffer, this->minutes_text_, remainder); - if (!this->expand_ && uptime == 0) - break; - } - remainder = uptime % 24; - uptime /= 24; - if (interval < 12 * 3600) { - this->insert_buffer_(buffer, this->hours_text_, remainder); - if (!this->expand_ && uptime == 0) - break; - } - this->insert_buffer_(buffer, this->days_text_, (unsigned) uptime); - break; } - this->publish_state(buffer); + + // Build output string on stack + // Home Assistant max state length is 255 chars + null terminator + char buf[256]; + size_t pos = 0; + buf[0] = '\0'; // Initialize for empty case + + if (show_days) + append_unit(buf, sizeof(buf), pos, this->separator_, days, this->days_text_); + if (show_hours) + append_unit(buf, sizeof(buf), pos, this->separator_, hours, this->hours_text_); + if (show_minutes) + append_unit(buf, sizeof(buf), pos, this->separator_, minutes, this->minutes_text_); + if (show_seconds) + append_unit(buf, sizeof(buf), pos, this->separator_, seconds, this->seconds_text_); + + this->publish_state(buf); } float UptimeTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/uptime/text_sensor/uptime_text_sensor.h b/esphome/components/uptime/text_sensor/uptime_text_sensor.h index 8dd058998c..947d9c91e9 100644 --- a/esphome/components/uptime/text_sensor/uptime_text_sensor.h +++ b/esphome/components/uptime/text_sensor/uptime_text_sensor.h @@ -29,7 +29,6 @@ class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent void set_seconds(const char *seconds_text) { this->seconds_text_ = seconds_text; } protected: - void insert_buffer_(std::string &buffer, const char *key, unsigned value) const; const char *days_text_; const char *hours_text_; const char *minutes_text_; diff --git a/esphome/components/usb_cdc_acm/__init__.py b/esphome/components/usb_cdc_acm/__init__.py index 6693d8e75e..bfe177a4da 100644 --- a/esphome/components/usb_cdc_acm/__init__.py +++ b/esphome/components/usb_cdc_acm/__init__.py @@ -74,3 +74,4 @@ async def to_code(config: ConfigType) -> None: add_idf_sdkconfig_option( "CONFIG_TINYUSB_CDC_TX_BUFSIZE", config[CONF_TX_BUFFER_SIZE] ) + cg.add_define("ESPHOME_MAX_USB_CDC_INSTANCES", num_interfaces) diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp index 1cf614286f..a4c2e6c4a4 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp @@ -1,176 +1,19 @@ #if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_cdc_acm.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" - -#include -#include "freertos/FreeRTOS.h" -#include "freertos/ringbuf.h" -#include "freertos/task.h" -#include "esp_log.h" - -#include "tusb.h" -#include "tusb_cdc_acm.h" - namespace esphome::usb_cdc_acm { -static const char *TAG = "usb_cdc_acm"; - -static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096; -static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192; +static const char *const TAG = "usb_cdc_acm"; // Global component instance for managing USB device -USBCDCACMComponent *global_usb_cdc_component = nullptr; - -static USBCDCACMInstance *get_instance_by_itf(int itf) { - if (global_usb_cdc_component == nullptr) { - return nullptr; - } - return global_usb_cdc_component->get_interface_by_number(itf); -} - -static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) { - USBCDCACMInstance *instance = get_instance_by_itf(itf); - if (instance == nullptr) { - ESP_LOGE(TAG, "RX callback: invalid interface %d", itf); - return; - } - - size_t rx_size = 0; - static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0}; - - // read from USB - esp_err_t ret = - tinyusb_cdcacm_read(static_cast(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); - ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size); - ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty(rx_buf, rx_size).c_str()); - - if (ret == ESP_OK && rx_size > 0) { - RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf(); - if (rx_ringbuf != nullptr) { - BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0); - if (send_res != pdTRUE) { - ESP_LOGE(TAG, "USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size); - } else { - ESP_LOGV(TAG, "USB RX itf=%d: queued %u bytes", itf, rx_size); - } - } - } -} - -static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) { - USBCDCACMInstance *instance = get_instance_by_itf(itf); - if (instance == nullptr) { - ESP_LOGE(TAG, "Line state callback: invalid interface %d", itf); - return; - } - - int dtr = event->line_state_changed_data.dtr; - int rts = event->line_state_changed_data.rts; - ESP_LOGV(TAG, "Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts); - - // Queue event for processing in main loop - instance->queue_line_state_event(dtr != 0, rts != 0); -} - -static void tinyusb_cdc_line_coding_changed_callback(int itf, cdcacm_event_t *event) { - USBCDCACMInstance *instance = get_instance_by_itf(itf); - if (instance == nullptr) { - ESP_LOGE(TAG, "Line coding callback: invalid interface %d", itf); - return; - } - - uint32_t bit_rate = event->line_coding_changed_data.p_line_coding->bit_rate; - uint8_t stop_bits = event->line_coding_changed_data.p_line_coding->stop_bits; - uint8_t parity = event->line_coding_changed_data.p_line_coding->parity; - uint8_t data_bits = event->line_coding_changed_data.p_line_coding->data_bits; - ESP_LOGV(TAG, "Line coding itf=%d: bit_rate=%" PRIu32 " stop_bits=%u parity=%u data_bits=%u", itf, bit_rate, - stop_bits, parity, data_bits); - - // Queue event for processing in main loop - instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits); -} - -static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size, - TickType_t xTicksToWait) { - size_t read_sz; - uint8_t *buf = static_cast(xRingbufferReceiveUpTo(ring_buf, &read_sz, xTicksToWait, out_buf_sz)); - - if (buf == nullptr) { - return ESP_FAIL; - } - - memcpy(out_buf, buf, read_sz); - vRingbufferReturnItem(ring_buf, (void *) buf); - *rx_data_size = read_sz; - - // Buffer's data can be wrapped, in which case we should perform another read - buf = static_cast(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size)); - if (buf != nullptr) { - memcpy(out_buf + *rx_data_size, buf, read_sz); - vRingbufferReturnItem(ring_buf, (void *) buf); - *rx_data_size += read_sz; - } - - return ESP_OK; -} +USBCDCACMComponent *global_usb_cdc_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) //============================================================================== // USBCDCACMInstance Implementation //============================================================================== -void USBCDCACMInstance::setup() { - this->usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF); - if (this->usb_tx_ringbuf_ == nullptr) { - ESP_LOGE(TAG, "USB TX buffer creation error for itf %d", this->itf_); - this->parent_->mark_failed(); - return; - } - - this->usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF); - if (this->usb_rx_ringbuf_ == nullptr) { - ESP_LOGE(TAG, "USB RX buffer creation error for itf %d", this->itf_); - this->parent_->mark_failed(); - return; - } - - // Configure this CDC interface - const tinyusb_config_cdcacm_t acm_cfg = { - .usb_dev = TINYUSB_USBDEV_0, - .cdc_port = this->itf_, - .callback_rx = &tinyusb_cdc_rx_callback, - .callback_rx_wanted_char = NULL, - .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback, - .callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback, - }; - - esp_err_t result = tusb_cdc_acm_init(&acm_cfg); - if (result != ESP_OK) { - ESP_LOGE(TAG, "tusb_cdc_acm_init failed: %d", result); - this->parent_->mark_failed(); - return; - } - - // Use a larger stack size for (very) verbose logging - const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE; - - // Create a simple, unique task name per interface - char task_name[] = "usb_tx_0"; - task_name[sizeof(task_name) - 1] = format_hex_char(static_cast(this->itf_)); - xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_); - - if (this->usb_tx_task_handle_ == nullptr) { - ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_); - this->parent_->mark_failed(); - return; - } -} - -void USBCDCACMInstance::loop() { - // Process events from the lock-free queue - this->process_events_(); -} - void USBCDCACMInstance::queue_line_state_event(bool dtr, bool rts) { // Allocate event from pool CDCEvent *event = this->event_pool_.allocate(); @@ -281,167 +124,6 @@ void USBCDCACMInstance::process_events_() { } } -void USBCDCACMInstance::usb_tx_task_fn(void *arg) { - auto *instance = static_cast(arg); - instance->usb_tx_task(); -} - -void USBCDCACMInstance::usb_tx_task() { - uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0}; - size_t tx_data_size = 0; - - while (1) { - // Wait for a notification from the bridge component - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - - // When we do wake up, we can be sure there is data in the ring buffer - esp_err_t ret = ringbuf_read_bytes(this->usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0); - - if (ret != ESP_OK) { - ESP_LOGE(TAG, "USB TX itf=%d: RingBuf read failed", this->itf_); - continue; - } else if (tx_data_size == 0) { - ESP_LOGD(TAG, "USB TX itf=%d: RingBuf empty, skipping", this->itf_); - continue; - } - - ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size); - ESP_LOGVV(TAG, "data = %s", format_hex_pretty(data, tx_data_size).c_str()); - - // Serial data will be split up into 64 byte chunks to be sent over USB so this - // usually will take multiple iterations - uint8_t *data_head = &data[0]; - - while (tx_data_size > 0) { - size_t queued = tinyusb_cdcacm_write_queue(this->itf_, data_head, tx_data_size); - ESP_LOGV(TAG, "USB TX itf=%d: enqueued: size=%d, queued=%u", this->itf_, tx_data_size, queued); - - tx_data_size -= queued; - data_head += queued; - - ESP_LOGV(TAG, "USB TX itf=%d: waiting 10ms for flush", this->itf_); - esp_err_t flush_ret = tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(10)); - - if (flush_ret != ESP_OK) { - ESP_LOGE(TAG, "USB TX itf=%d: flush failed", this->itf_); - tud_cdc_n_write_clear(this->itf_); - break; - } - } - } -} - -//============================================================================== -// UARTComponent Interface Implementation -//============================================================================== - -void USBCDCACMInstance::write_array(const uint8_t *data, size_t len) { - if (len == 0) { - return; - } - - // Write data to TX ring buffer - BaseType_t send_res = xRingbufferSend(this->usb_tx_ringbuf_, data, len, 0); - if (send_res != pdTRUE) { - ESP_LOGW(TAG, "USB TX itf=%d: buffer full, %u bytes dropped", this->itf_, len); - return; - } - - // Notify TX task that data is available - if (this->usb_tx_task_handle_ != nullptr) { - xTaskNotifyGive(this->usb_tx_task_handle_); - } -} - -bool USBCDCACMInstance::peek_byte(uint8_t *data) { - if (this->has_peek_) { - *data = this->peek_buffer_; - return true; - } - - if (this->read_byte(&this->peek_buffer_)) { - *data = this->peek_buffer_; - this->has_peek_ = true; - return true; - } - - return false; -} - -bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) { - if (len == 0) { - return true; - } - - size_t original_len = len; - size_t bytes_read = 0; - - // First, use the peek buffer if available - if (this->has_peek_) { - data[0] = this->peek_buffer_; - this->has_peek_ = false; - bytes_read = 1; - data++; - if (--len == 0) { // Decrement len first, then check it... - return true; // No more to read - } - } - - // Read remaining bytes from RX ring buffer - size_t rx_size = 0; - uint8_t *buf = static_cast(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len)); - if (buf == nullptr) { - return false; - } - - memcpy(data, buf, rx_size); - vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf); - bytes_read += rx_size; - data += rx_size; - len -= rx_size; - if (len == 0) { - return true; // No more to read - } - - // Buffer's data may wrap around, in which case we should perform another read - buf = static_cast(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len)); - if (buf == nullptr) { - return false; - } - - memcpy(data, buf, rx_size); - vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf); - bytes_read += rx_size; - - return bytes_read == original_len; -} - -int USBCDCACMInstance::available() { - UBaseType_t waiting = 0; - if (this->usb_rx_ringbuf_ != nullptr) { - vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting); - } - return static_cast(waiting) + (this->has_peek_ ? 1 : 0); -} - -void USBCDCACMInstance::flush() { - // Wait for TX ring buffer to be empty - if (this->usb_tx_ringbuf_ == nullptr) { - return; - } - - UBaseType_t waiting = 1; - while (waiting > 0) { - vRingbufferGetInfo(this->usb_tx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting); - if (waiting > 0) { - vTaskDelay(pdMS_TO_TICKS(1)); - } - } - - // Also wait for USB to finish transmitting - tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(100)); -} - //============================================================================== // USBCDCACMComponent Implementation //============================================================================== @@ -450,7 +132,7 @@ USBCDCACMComponent::USBCDCACMComponent() { global_usb_cdc_component = this; } void USBCDCACMComponent::setup() { // Setup all registered interfaces - for (auto interface : this->interfaces_) { + for (auto *interface : this->interfaces_) { if (interface != nullptr) { interface->setup(); } @@ -459,7 +141,7 @@ void USBCDCACMComponent::setup() { void USBCDCACMComponent::loop() { // Call loop() on all registered interfaces to process events - for (auto interface : this->interfaces_) { + for (auto *interface : this->interfaces_) { if (interface != nullptr) { interface->loop(); } @@ -470,21 +152,28 @@ void USBCDCACMComponent::dump_config() { ESP_LOGCONFIG(TAG, "USB CDC-ACM:\n" " Number of Interfaces: %d", - this->interfaces_[MAX_USB_CDC_INSTANCES - 1] != nullptr ? MAX_USB_CDC_INSTANCES : 1); + ESPHOME_MAX_USB_CDC_INSTANCES); + for (uint8_t i = 0; i < ESPHOME_MAX_USB_CDC_INSTANCES; ++i) { + if (this->interfaces_[i] != nullptr) { + this->interfaces_[i]->dump_config(); + } else { + ESP_LOGCONFIG(TAG, " Interface %u is disabled", i); + } + } } void USBCDCACMComponent::add_interface(USBCDCACMInstance *interface) { uint8_t itf_num = static_cast(interface->get_itf()); - if (itf_num < MAX_USB_CDC_INSTANCES) { + if (itf_num < ESPHOME_MAX_USB_CDC_INSTANCES) { this->interfaces_[itf_num] = interface; } else { - ESP_LOGE(TAG, "Interface number must be less than %u", MAX_USB_CDC_INSTANCES); + ESP_LOGE(TAG, "Interface number must be less than %u", ESPHOME_MAX_USB_CDC_INSTANCES); } } USBCDCACMInstance *USBCDCACMComponent::get_interface_by_number(uint8_t itf) { - for (auto interface : this->interfaces_) { - if ((interface != nullptr) && (interface->get_itf() == static_cast(itf))) { + for (auto *interface : this->interfaces_) { + if ((interface != nullptr) && (interface->get_itf() == itf)) { return interface; } } diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.h b/esphome/components/usb_cdc_acm/usb_cdc_acm.h index 8c00f5d52f..065d7282d5 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm.h +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.h @@ -13,7 +13,6 @@ namespace esphome::usb_cdc_acm { static const uint8_t EVENT_QUEUE_SIZE = 12; -static const uint8_t MAX_USB_CDC_INSTANCES = 2; // Callback types for line coding and line state changes using LineCodingCallback = std::function; @@ -53,14 +52,13 @@ class USBCDCACMComponent; /// Represents a single CDC ACM interface instance class USBCDCACMInstance : public uart::UARTComponent, public Parented { public: - void set_interface_number(uint8_t itf) { this->itf_ = static_cast(itf); } - void setup(); void loop(); + void dump_config(); + void set_interface_number(uint8_t itf) { this->itf_ = itf; } // Get the CDC port number for this instance - tinyusb_cdcacm_itf_t get_itf() const { return this->itf_; } - + uint8_t get_itf() const { return this->itf_; } // Ring buffer accessors for bridge components RingbufHandle_t get_tx_ringbuf() const { return this->usb_tx_ringbuf_; } RingbufHandle_t get_rx_ringbuf() const { return this->usb_rx_ringbuf_; } @@ -72,7 +70,7 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parentedline_coding_callback_ = std::move(callback); } void set_line_state_callback(LineStateCallback callback) { this->line_state_callback_ = std::move(callback); } - // Called from TinyUSB task context (SPSC producer) - queues event for processing in main loop + // Called from USB core task context queues event for processing in main loop void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits); void queue_line_state_event(bool dtr, bool rts); @@ -87,17 +85,18 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented event_pool_; LockFreeQueue event_queue_; - - // RX buffer for peek functionality - uint8_t peek_buffer_{0}; - bool has_peek_{false}; }; /// Main USB CDC ACM component that manages the USB device and all CDC interfaces @@ -126,7 +121,7 @@ class USBCDCACMComponent : public Component { USBCDCACMInstance *get_interface_by_number(uint8_t itf); protected: - std::array interfaces_{nullptr, nullptr}; + std::array interfaces_{}; }; extern USBCDCACMComponent *global_usb_cdc_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp new file mode 100644 index 0000000000..5c91150f30 --- /dev/null +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp @@ -0,0 +1,349 @@ +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#include "usb_cdc_acm.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/ringbuf.h" +#include "freertos/task.h" +#include "esp_log.h" + +#include "tusb.h" +#include "tusb_cdc_acm.h" + +namespace esphome::usb_cdc_acm { + +static const char *const TAG = "usb_cdc_acm"; + +// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168; + +static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096; +static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192; + +static USBCDCACMInstance *get_instance_by_itf(int itf) { + if (global_usb_cdc_component == nullptr) { + return nullptr; + } + return global_usb_cdc_component->get_interface_by_number(itf); +} + +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) { + USBCDCACMInstance *instance = get_instance_by_itf(itf); + if (instance == nullptr) { + ESP_LOGE(TAG, "RX callback: invalid interface %d", itf); + return; + } + + size_t rx_size = 0; + static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0}; + + // read from USB + esp_err_t ret = + tinyusb_cdcacm_read(static_cast(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); + ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char rx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty_to(rx_hex_buf, rx_buf, rx_size)); + + if (ret == ESP_OK && rx_size > 0) { + RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf(); + if (rx_ringbuf != nullptr) { + BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0); + if (send_res != pdTRUE) { + ESP_LOGE(TAG, "USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size); + } else { + ESP_LOGV(TAG, "USB RX itf=%d: queued %u bytes", itf, rx_size); + } + } + } +} + +static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) { + USBCDCACMInstance *instance = get_instance_by_itf(itf); + if (instance == nullptr) { + ESP_LOGE(TAG, "Line state callback: invalid interface %d", itf); + return; + } + + int dtr = event->line_state_changed_data.dtr; + int rts = event->line_state_changed_data.rts; + ESP_LOGV(TAG, "Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts); + + // Queue event for processing in main loop + instance->queue_line_state_event(dtr != 0, rts != 0); +} + +static void tinyusb_cdc_line_coding_changed_callback(int itf, cdcacm_event_t *event) { + USBCDCACMInstance *instance = get_instance_by_itf(itf); + if (instance == nullptr) { + ESP_LOGE(TAG, "Line coding callback: invalid interface %d", itf); + return; + } + + uint32_t bit_rate = event->line_coding_changed_data.p_line_coding->bit_rate; + uint8_t stop_bits = event->line_coding_changed_data.p_line_coding->stop_bits; + uint8_t parity = event->line_coding_changed_data.p_line_coding->parity; + uint8_t data_bits = event->line_coding_changed_data.p_line_coding->data_bits; + ESP_LOGV(TAG, "Line coding itf=%d: bit_rate=%" PRIu32 " stop_bits=%u parity=%u data_bits=%u", itf, bit_rate, + stop_bits, parity, data_bits); + + // Queue event for processing in main loop + instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits); +} + +static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size, + TickType_t xTicksToWait) { + size_t read_sz; + uint8_t *buf = static_cast(xRingbufferReceiveUpTo(ring_buf, &read_sz, xTicksToWait, out_buf_sz)); + + if (buf == nullptr) { + return ESP_FAIL; + } + + memcpy(out_buf, buf, read_sz); + vRingbufferReturnItem(ring_buf, (void *) buf); + *rx_data_size = read_sz; + + // Buffer's data can be wrapped, in which case we should perform another read + buf = static_cast(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size)); + if (buf != nullptr) { + memcpy(out_buf + *rx_data_size, buf, read_sz); + vRingbufferReturnItem(ring_buf, (void *) buf); + *rx_data_size += read_sz; + } + + return ESP_OK; +} + +//============================================================================== +// USBCDCACMInstance Implementation +//============================================================================== + +void USBCDCACMInstance::setup() { + this->usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF); + if (this->usb_tx_ringbuf_ == nullptr) { + ESP_LOGE(TAG, "USB TX buffer creation error for itf %d", this->itf_); + this->parent_->mark_failed(); + return; + } + + this->usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF); + if (this->usb_rx_ringbuf_ == nullptr) { + ESP_LOGE(TAG, "USB RX buffer creation error for itf %d", this->itf_); + this->parent_->mark_failed(); + return; + } + + // Configure this CDC interface + const tinyusb_config_cdcacm_t acm_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = static_cast(this->itf_), + .callback_rx = &tinyusb_cdc_rx_callback, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback, + .callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback, + }; + + esp_err_t result = tusb_cdc_acm_init(&acm_cfg); + if (result != ESP_OK) { + ESP_LOGE(TAG, "tusb_cdc_acm_init failed: %d", result); + this->parent_->mark_failed(); + return; + } + + // Use a larger stack size for (very) verbose logging + const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE; + + // Create a simple, unique task name per interface + char task_name[] = "usb_tx_0"; + task_name[sizeof(task_name) - 1] = format_hex_char(static_cast(this->itf_)); + xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_); + + if (this->usb_tx_task_handle_ == nullptr) { + ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_); + this->parent_->mark_failed(); + return; + } +} + +void USBCDCACMInstance::loop() { + // Process events from the lock-free queue + this->process_events_(); +} + +void USBCDCACMInstance::dump_config() {} + +void USBCDCACMInstance::usb_tx_task_fn(void *arg) { + auto *instance = static_cast(arg); + instance->usb_tx_task(); +} + +void USBCDCACMInstance::usb_tx_task() { + uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0}; + size_t tx_data_size = 0; + + while (1) { + // Wait for a notification from the bridge component + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + // When we do wake up, we can be sure there is data in the ring buffer + esp_err_t ret = ringbuf_read_bytes(this->usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB TX itf=%d: RingBuf read failed", this->itf_); + continue; + } else if (tx_data_size == 0) { + ESP_LOGD(TAG, "USB TX itf=%d: RingBuf empty, skipping", this->itf_); + continue; + } + + ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char tx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "data = %s", format_hex_pretty_to(tx_hex_buf, data, tx_data_size)); + + // Serial data will be split up into 64 byte chunks to be sent over USB so this + // usually will take multiple iterations + uint8_t *data_head = &data[0]; + + while (tx_data_size > 0) { + size_t queued = + tinyusb_cdcacm_write_queue(static_cast(this->itf_), data_head, tx_data_size); + ESP_LOGV(TAG, "USB TX itf=%d: enqueued: size=%d, queued=%u", this->itf_, tx_data_size, queued); + + tx_data_size -= queued; + data_head += queued; + + ESP_LOGV(TAG, "USB TX itf=%d: waiting 10ms for flush", this->itf_); + esp_err_t flush_ret = + tinyusb_cdcacm_write_flush(static_cast(this->itf_), pdMS_TO_TICKS(10)); + + if (flush_ret != ESP_OK) { + ESP_LOGE(TAG, "USB TX itf=%d: flush failed", this->itf_); + tud_cdc_n_write_clear(this->itf_); + break; + } + } + } +} + +//============================================================================== +// UARTComponent Interface Implementation +//============================================================================== + +void USBCDCACMInstance::write_array(const uint8_t *data, size_t len) { + if (len == 0) { + return; + } + + // Write data to TX ring buffer + BaseType_t send_res = xRingbufferSend(this->usb_tx_ringbuf_, data, len, 0); + if (send_res != pdTRUE) { + ESP_LOGW(TAG, "USB TX itf=%d: buffer full, %u bytes dropped", this->itf_, len); + return; + } + + // Notify TX task that data is available + if (this->usb_tx_task_handle_ != nullptr) { + xTaskNotifyGive(this->usb_tx_task_handle_); + } +} + +bool USBCDCACMInstance::peek_byte(uint8_t *data) { + if (this->has_peek_) { + *data = this->peek_buffer_; + return true; + } + + if (this->read_byte(&this->peek_buffer_)) { + *data = this->peek_buffer_; + this->has_peek_ = true; + return true; + } + + return false; +} + +bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) { + if (len == 0) { + return true; + } + + size_t original_len = len; + size_t bytes_read = 0; + + // First, use the peek buffer if available + if (this->has_peek_) { + data[0] = this->peek_buffer_; + this->has_peek_ = false; + bytes_read = 1; + data++; + if (--len == 0) { // Decrement len first, then check it... + return true; // No more to read + } + } + + // Read remaining bytes from RX ring buffer + size_t rx_size = 0; + uint8_t *buf = static_cast(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len)); + if (buf == nullptr) { + return false; + } + + memcpy(data, buf, rx_size); + vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf); + bytes_read += rx_size; + data += rx_size; + len -= rx_size; + if (len == 0) { + return true; // No more to read + } + + // Buffer's data may wrap around, in which case we should perform another read + buf = static_cast(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len)); + if (buf == nullptr) { + return false; + } + + memcpy(data, buf, rx_size); + vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf); + bytes_read += rx_size; + + return bytes_read == original_len; +} + +int USBCDCACMInstance::available() { + UBaseType_t waiting = 0; + if (this->usb_rx_ringbuf_ != nullptr) { + vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting); + } + return static_cast(waiting) + (this->has_peek_ ? 1 : 0); +} + +void USBCDCACMInstance::flush() { + // Wait for TX ring buffer to be empty + if (this->usb_tx_ringbuf_ == nullptr) { + return; + } + + UBaseType_t waiting = 1; + while (waiting > 0) { + vRingbufferGetInfo(this->usb_tx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting); + if (waiting > 0) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + // Also wait for USB to finish transmitting + tinyusb_cdcacm_write_flush(static_cast(this->itf_), pdMS_TO_TICKS(100)); +} + +void USBCDCACMInstance::check_logger_conflict() {} + +} // namespace esphome::usb_cdc_acm +#endif diff --git a/esphome/components/usb_host/__init__.py b/esphome/components/usb_host/__init__.py index cccabcf646..e4c11be489 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -53,8 +53,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()), } ), - cv.only_with_esp_idf, - only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3, VARIANT_ESP32P4]), + only_on_variant(supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3]), ) diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 664f49d137..09da6e3b73 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -39,37 +39,46 @@ static void print_ep_desc(const usb_ep_desc_t *ep_desc) { break; } - ESP_LOGV(TAG, "\t\t*** Endpoint descriptor ***"); - ESP_LOGV(TAG, "\t\tbLength %d", ep_desc->bLength); - ESP_LOGV(TAG, "\t\tbDescriptorType %d", ep_desc->bDescriptorType); - ESP_LOGV(TAG, "\t\tbEndpointAddress 0x%x\tEP %d %s", ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc), - USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT"); - ESP_LOGV(TAG, "\t\tbmAttributes 0x%x\t%s", ep_desc->bmAttributes, ep_type_str); - ESP_LOGV(TAG, "\t\twMaxPacketSize %d", ep_desc->wMaxPacketSize); - ESP_LOGV(TAG, "\t\tbInterval %d", ep_desc->bInterval); + ESP_LOGV(TAG, + "\t\t*** Endpoint descriptor ***\n" + "\t\tbLength %d\n" + "\t\tbDescriptorType %d\n" + "\t\tbEndpointAddress 0x%x\tEP %d %s\n" + "\t\tbmAttributes 0x%x\t%s\n" + "\t\twMaxPacketSize %d\n" + "\t\tbInterval %d", + ep_desc->bLength, ep_desc->bDescriptorType, ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc), + USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT", ep_desc->bmAttributes, ep_type_str, ep_desc->wMaxPacketSize, + ep_desc->bInterval); } static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc) { - ESP_LOGV(TAG, "\t*** Interface descriptor ***"); - ESP_LOGV(TAG, "\tbLength %d", intf_desc->bLength); - ESP_LOGV(TAG, "\tbDescriptorType %d", intf_desc->bDescriptorType); - ESP_LOGV(TAG, "\tbInterfaceNumber %d", intf_desc->bInterfaceNumber); - ESP_LOGV(TAG, "\tbAlternateSetting %d", intf_desc->bAlternateSetting); - ESP_LOGV(TAG, "\tbNumEndpoints %d", intf_desc->bNumEndpoints); - ESP_LOGV(TAG, "\tbInterfaceClass 0x%x", intf_desc->bInterfaceProtocol); - ESP_LOGV(TAG, "\tiInterface %d", intf_desc->iInterface); + ESP_LOGV(TAG, + "\t*** Interface descriptor ***\n" + "\tbLength %d\n" + "\tbDescriptorType %d\n" + "\tbInterfaceNumber %d\n" + "\tbAlternateSetting %d\n" + "\tbNumEndpoints %d\n" + "\tbInterfaceClass 0x%x\n" + "\tiInterface %d", + intf_desc->bLength, intf_desc->bDescriptorType, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting, + intf_desc->bNumEndpoints, intf_desc->bInterfaceProtocol, intf_desc->iInterface); } static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) { - ESP_LOGV(TAG, "*** Configuration descriptor ***"); - ESP_LOGV(TAG, "bLength %d", cfg_desc->bLength); - ESP_LOGV(TAG, "bDescriptorType %d", cfg_desc->bDescriptorType); - ESP_LOGV(TAG, "wTotalLength %d", cfg_desc->wTotalLength); - ESP_LOGV(TAG, "bNumInterfaces %d", cfg_desc->bNumInterfaces); - ESP_LOGV(TAG, "bConfigurationValue %d", cfg_desc->bConfigurationValue); - ESP_LOGV(TAG, "iConfiguration %d", cfg_desc->iConfiguration); - ESP_LOGV(TAG, "bmAttributes 0x%x", cfg_desc->bmAttributes); - ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2); + ESP_LOGV(TAG, + "*** Configuration descriptor ***\n" + "bLength %d\n" + "bDescriptorType %d\n" + "wTotalLength %d\n" + "bNumInterfaces %d\n" + "bConfigurationValue %d\n" + "iConfiguration %d\n" + "bmAttributes 0x%x\n" + "bMaxPower %dmA", + cfg_desc->bLength, cfg_desc->bDescriptorType, cfg_desc->wTotalLength, cfg_desc->bNumInterfaces, + cfg_desc->bConfigurationValue, cfg_desc->iConfiguration, cfg_desc->bmAttributes, cfg_desc->bMaxPower * 2); } static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) { @@ -77,21 +86,27 @@ static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_des return; } - ESP_LOGV(TAG, "*** Device descriptor ***"); - ESP_LOGV(TAG, "bLength %d", devc_desc->bLength); - ESP_LOGV(TAG, "bDescriptorType %d", devc_desc->bDescriptorType); - ESP_LOGV(TAG, "bcdUSB %d.%d0", ((devc_desc->bcdUSB >> 8) & 0xF), ((devc_desc->bcdUSB >> 4) & 0xF)); - ESP_LOGV(TAG, "bDeviceClass 0x%x", devc_desc->bDeviceClass); - ESP_LOGV(TAG, "bDeviceSubClass 0x%x", devc_desc->bDeviceSubClass); - ESP_LOGV(TAG, "bDeviceProtocol 0x%x", devc_desc->bDeviceProtocol); - ESP_LOGV(TAG, "bMaxPacketSize0 %d", devc_desc->bMaxPacketSize0); - ESP_LOGV(TAG, "idVendor 0x%x", devc_desc->idVendor); - ESP_LOGV(TAG, "idProduct 0x%x", devc_desc->idProduct); - ESP_LOGV(TAG, "bcdDevice %d.%d0", ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF)); - ESP_LOGV(TAG, "iManufacturer %d", devc_desc->iManufacturer); - ESP_LOGV(TAG, "iProduct %d", devc_desc->iProduct); - ESP_LOGV(TAG, "iSerialNumber %d", devc_desc->iSerialNumber); - ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations); + ESP_LOGV(TAG, + "*** Device descriptor ***\n" + "bLength %d\n" + "bDescriptorType %d\n" + "bcdUSB %d.%d0\n" + "bDeviceClass 0x%x\n" + "bDeviceSubClass 0x%x\n" + "bDeviceProtocol 0x%x\n" + "bMaxPacketSize0 %d\n" + "idVendor 0x%x\n" + "idProduct 0x%x\n" + "bcdDevice %d.%d0\n" + "iManufacturer %d\n" + "iProduct %d\n" + "iSerialNumber %d\n" + "bNumConfigurations %d", + devc_desc->bLength, devc_desc->bDescriptorType, ((devc_desc->bcdUSB >> 8) & 0xF), + ((devc_desc->bcdUSB >> 4) & 0xF), devc_desc->bDeviceClass, devc_desc->bDeviceSubClass, + devc_desc->bDeviceProtocol, devc_desc->bMaxPacketSize0, devc_desc->idVendor, devc_desc->idProduct, + ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF), devc_desc->iManufacturer, + devc_desc->iProduct, devc_desc->iSerialNumber, devc_desc->bNumConfigurations); } static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc, diff --git a/esphome/components/usb_uart/cp210x.cpp b/esphome/components/usb_uart/cp210x.cpp index be024d1ba2..483286560a 100644 --- a/esphome/components/usb_uart/cp210x.cpp +++ b/esphome/components/usb_uart/cp210x.cpp @@ -58,8 +58,10 @@ std::vector USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev ESP_LOGE(TAG, "get_active_config_descriptor failed"); return {}; } - ESP_LOGD(TAG, "bDeviceClass: %u, bDeviceSubClass: %u", device_desc->bDeviceClass, device_desc->bDeviceSubClass); - ESP_LOGD(TAG, "bNumInterfaces: %u", config_desc->bNumInterfaces); + ESP_LOGD(TAG, + "bDeviceClass: %u, bDeviceSubClass: %u\n" + "bNumInterfaces: %u", + device_desc->bDeviceClass, device_desc->bDeviceSubClass, config_desc->bNumInterfaces); if (device_desc->bDeviceClass != 0) { ESP_LOGE(TAG, "bDeviceClass != 0"); return {}; diff --git a/esphome/components/valve/valve.h b/esphome/components/valve/valve.h index 2cb28e4b2f..2b3419b67a 100644 --- a/esphome/components/valve/valve.h +++ b/esphome/components/valve/valve.h @@ -144,7 +144,7 @@ class Valve : public EntityBase, public EntityBase_DeviceClass { optional restore_state_(); - CallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/vbus/vbus.cpp b/esphome/components/vbus/vbus.cpp index e474dcfe17..b9496a08de 100644 --- a/esphome/components/vbus/vbus.cpp +++ b/esphome/components/vbus/vbus.cpp @@ -8,6 +8,9 @@ namespace vbus { static const char *const TAG = "vbus"; +// Maximum bytes to log in verbose hex output (16 frames * 4 bytes = 64 bytes typical) +static constexpr size_t VBUS_MAX_LOG_BYTES = 64; + void VBus::dump_config() { ESP_LOGCONFIG(TAG, "VBus:"); check_uart_settings(9600); @@ -101,8 +104,11 @@ void VBus::loop() { this->buffer_.push_back(this->fbytes_[i]); if (++this->cframe_ < this->frames_) continue; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_size(VBUS_MAX_LOG_BYTES)]; +#endif ESP_LOGV(TAG, "P2 C%04x %04x->%04x: %s", this->command_, this->source_, this->dest_, - format_hex(this->buffer_).c_str()); + format_hex_to(hex_buf, this->buffer_.data(), this->buffer_.size())); for (auto &listener : this->listeners_) listener->on_message(this->command_, this->source_, this->dest_, this->buffer_); this->state_ = 0; diff --git a/esphome/components/version/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 78d0fb501b..584b8abfb2 100644 --- a/esphome/components/version/version_text_sensor.cpp +++ b/esphome/components/version/version_text_sensor.cpp @@ -1,8 +1,9 @@ #include "version_text_sensor.h" -#include "esphome/core/log.h" #include "esphome/core/application.h" +#include "esphome/core/log.h" #include "esphome/core/version.h" #include "esphome/core/helpers.h" +#include "esphome/core/progmem.h" namespace esphome { namespace version { @@ -10,11 +11,26 @@ namespace version { static const char *const TAG = "version.text_sensor"; void VersionTextSensor::setup() { - if (this->hide_timestamp_) { - this->publish_state(ESPHOME_VERSION); - } else { - this->publish_state(str_sprintf(ESPHOME_VERSION " %s", App.get_compilation_time_ref().c_str())); + static const char PREFIX[] PROGMEM = ESPHOME_VERSION " (config hash 0x"; + static const char BUILT_STR[] PROGMEM = ", built "; + // Buffer size: PREFIX + 8 hex chars + BUILT_STR + BUILD_TIME_STR_SIZE + ")" + null + constexpr size_t buf_size = sizeof(PREFIX) + 8 + sizeof(BUILT_STR) + esphome::Application::BUILD_TIME_STR_SIZE + 2; + char version_str[buf_size]; + + ESPHOME_strncpy_P(version_str, PREFIX, sizeof(version_str)); + + size_t len = strlen(version_str); + snprintf(version_str + len, sizeof(version_str) - len, "%08" PRIx32, App.get_config_hash()); + + if (!this->hide_timestamp_) { + size_t len = strlen(version_str); + ESPHOME_strncat_P(version_str, BUILT_STR, sizeof(version_str) - len - 1); + ESPHOME_strncat_P(version_str, ESPHOME_BUILD_TIME_STR, sizeof(version_str) - strlen(version_str) - 1); } + + strncat(version_str, ")", sizeof(version_str) - strlen(version_str) - 1); + version_str[sizeof(version_str) - 1] = '\0'; + this->publish_state(version_str); } float VersionTextSensor::get_setup_priority() const { return setup_priority::DATA; } void VersionTextSensor::set_hide_timestamp(bool hide_timestamp) { this->hide_timestamp_ = hide_timestamp; } diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index d2548a5bbd..e833657fc4 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -27,8 +27,10 @@ void VL53L0XSensor::dump_config() { if (this->enable_pin_ != nullptr) { LOG_PIN(" Enable Pin: ", this->enable_pin_); } - ESP_LOGCONFIG(TAG, " Timeout: %u%s", this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)"); - ESP_LOGCONFIG(TAG, " Timing Budget %uus ", this->measurement_timing_budget_us_); + ESP_LOGCONFIG(TAG, + " Timeout: %u%s\n" + " Timing Budget %uus ", + this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)", this->measurement_timing_budget_us_); } void VL53L0XSensor::setup() { diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 59c7ec8383..d28c786dd8 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_ON_CLIENT_DISCONNECTED, CONF_ON_ERROR, CONF_ON_IDLE, + CONF_ON_START, CONF_SPEAKER, ) @@ -24,7 +25,6 @@ CONF_ON_INTENT_END = "on_intent_end" CONF_ON_INTENT_PROGRESS = "on_intent_progress" CONF_ON_INTENT_START = "on_intent_start" CONF_ON_LISTENING = "on_listening" -CONF_ON_START = "on_start" CONF_ON_STT_END = "on_stt_end" CONF_ON_STT_VAD_END = "on_stt_vad_end" CONF_ON_STT_VAD_START = "on_stt_vad_start" diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 551f0370f2..e2516d5fb8 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -3,6 +3,7 @@ #ifdef USE_VOICE_ASSISTANT +#include "esphome/components/socket/socket.h" #include "esphome/core/log.h" #include @@ -238,10 +239,10 @@ void VoiceAssistant::loop() { api::VoiceAssistantRequest msg; msg.start = true; - msg.set_conversation_id(StringRef(this->conversation_id_)); + msg.conversation_id = StringRef(this->conversation_id_); msg.flags = flags; msg.audio_settings = audio_settings; - msg.set_wake_word_phrase(StringRef(this->wake_word_)); + msg.wake_word_phrase = StringRef(this->wake_word_); // Reset media player state tracking #ifdef USE_MEDIA_PLAYER @@ -272,7 +273,8 @@ void VoiceAssistant::loop() { size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0); if (this->audio_mode_ == AUDIO_MODE_API) { api::VoiceAssistantAudio msg; - msg.set_data(this->send_buffer_, read_bytes); + msg.data = this->send_buffer_; + msg.data_len = read_bytes; this->api_client_->send_message(msg, api::VoiceAssistantAudio::MESSAGE_TYPE); } else { if (!this->udp_socket_running_) { @@ -428,10 +430,12 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr } if (this->api_client_ != nullptr) { - ESP_LOGE(TAG, "Multiple API Clients attempting to connect to Voice Assistant"); - ESP_LOGE(TAG, "Current client: %s (%s)", this->api_client_->get_name().c_str(), - this->api_client_->get_peername().c_str()); - ESP_LOGE(TAG, "New client: %s (%s)", client->get_name().c_str(), client->get_peername().c_str()); + ESP_LOGE(TAG, + "Multiple API Clients attempting to connect to Voice Assistant\n" + "Current client: %s (%s)\n" + "New client: %s (%s)", + this->api_client_->get_name(), this->api_client_->get_peername(), client->get_name(), + client->get_peername()); return; } @@ -627,9 +631,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { ESP_LOGD(TAG, "Assist Pipeline running"); #ifdef USE_MEDIA_PLAYER this->started_streaming_tts_ = false; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "url") { - this->tts_response_url_ = std::move(arg.value); + this->tts_response_url_ = arg.value; } } #endif @@ -648,9 +652,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { break; case api::enums::VOICE_ASSISTANT_STT_END: { std::string text; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "text") { - text = std::move(arg.value); + text = arg.value; } } if (text.empty()) { @@ -693,9 +697,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { break; } case api::enums::VOICE_ASSISTANT_INTENT_END: { - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "conversation_id") { - this->conversation_id_ = std::move(arg.value); + this->conversation_id_ = arg.value; } else if (arg.name == "continue_conversation") { this->continue_conversation_ = (arg.value == "1"); } @@ -705,9 +709,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_START: { std::string text; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "text") { - text = std::move(arg.value); + text = arg.value; } } if (text.empty()) { @@ -731,9 +735,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_TTS_END: { std::string url; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "url") { - url = std::move(arg.value); + url = arg.value; } } if (url.empty()) { @@ -778,11 +782,11 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { case api::enums::VOICE_ASSISTANT_ERROR: { std::string code = ""; std::string message = ""; - for (auto arg : msg.data) { + for (const auto &arg : msg.data) { if (arg.name == "code") { - code = std::move(arg.value); + code = arg.value; } else if (arg.name == "message") { - message = std::move(arg.value); + message = arg.value; } } if (code == "wake-word-timeout" || code == "wake_word_detection_aborted" || code == "no_wake_word") { @@ -841,12 +845,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { #ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) { - if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) { - memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length()); - this->speaker_buffer_index_ += msg.data.length(); - this->speaker_buffer_size_ += msg.data.length(); - this->speaker_bytes_received_ += msg.data.length(); - ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length()); + if (this->speaker_buffer_index_ + msg.data_len < SPEAKER_BUFFER_SIZE) { + memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data, msg.data_len); + this->speaker_buffer_index_ += msg.data_len; + this->speaker_buffer_size_ += msg.data_len; + this->speaker_bytes_received_ += msg.data_len; + ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data_len); } else { ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); } @@ -863,9 +867,12 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse .is_active = msg.is_active, }; this->timers_[timer.id] = timer; - ESP_LOGD(TAG, "Timer Event"); - ESP_LOGD(TAG, " Type: %" PRId32, msg.event_type); - ESP_LOGD(TAG, " %s", timer.to_string().c_str()); + char timer_buf[Timer::TO_STR_BUFFER_SIZE]; + ESP_LOGD(TAG, + "Timer Event\n" + " Type: %" PRId32 "\n" + " %s", + msg.event_type, timer.to_str(timer_buf)); switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_TIMER_STARTED: @@ -947,7 +954,7 @@ void VoiceAssistant::on_set_configuration(const std::vector &active } // Enable only active wake words - for (auto ww_id : active_wake_words) { + for (const auto &ww_id : active_wake_words) { for (auto &model : this->micro_wake_word_->get_wake_words()) { if (model->get_id() == ww_id) { model->enable(); diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 8d3d3497ec..b1b3df7bbd 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -23,6 +23,7 @@ #endif #include "esphome/components/socket/socket.h" +#include #include #include @@ -71,10 +72,18 @@ struct Timer { uint32_t seconds_left; bool is_active; + /// Buffer size for to_str() - sufficient for typical timer names + static constexpr size_t TO_STR_BUFFER_SIZE = 128; + /// Format to buffer, returns pointer to buffer (may truncate long names) + const char *to_str(std::span buffer) const { + snprintf(buffer.data(), buffer.size(), + "Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)", + this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active)); + return buffer.data(); + } std::string to_string() const { - return str_sprintf("Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)", - this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, - YESNO(this->is_active)); + char buffer[TO_STR_BUFFER_SIZE]; + return this->to_str(buffer); } }; diff --git a/esphome/components/water_heater/__init__.py b/esphome/components/water_heater/__init__.py new file mode 100644 index 0000000000..5420e7c435 --- /dev/null +++ b/esphome/components/water_heater/__init__.py @@ -0,0 +1,111 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + CONF_VISUAL, +) +from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity +from esphome.cpp_generator import MockObjClass +from esphome.types import ConfigType + +CODEOWNERS = ["@dhoeben"] + +IS_PLATFORM_COMPONENT = True + +water_heater_ns = cg.esphome_ns.namespace("water_heater") +WaterHeater = water_heater_ns.class_("WaterHeater", cg.EntityBase, cg.Component) +WaterHeaterCall = water_heater_ns.class_("WaterHeaterCall") +WaterHeaterTraits = water_heater_ns.class_("WaterHeaterTraits") + +CONF_TARGET_TEMPERATURE_STEP = "target_temperature_step" + +WaterHeaterMode = water_heater_ns.enum("WaterHeaterMode") +WATER_HEATER_MODES = { + "OFF": WaterHeaterMode.WATER_HEATER_MODE_OFF, + "ECO": WaterHeaterMode.WATER_HEATER_MODE_ECO, + "ELECTRIC": WaterHeaterMode.WATER_HEATER_MODE_ELECTRIC, + "PERFORMANCE": WaterHeaterMode.WATER_HEATER_MODE_PERFORMANCE, + "HIGH_DEMAND": WaterHeaterMode.WATER_HEATER_MODE_HIGH_DEMAND, + "HEAT_PUMP": WaterHeaterMode.WATER_HEATER_MODE_HEAT_PUMP, + "GAS": WaterHeaterMode.WATER_HEATER_MODE_GAS, +} +validate_water_heater_mode = cv.enum(WATER_HEATER_MODES, upper=True) + +_WATER_HEATER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( + { + cv.Optional(CONF_VISUAL, default={}): cv.Schema( + { + cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, + cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, + cv.Optional(CONF_TARGET_TEMPERATURE_STEP): cv.float_, + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + +_WATER_HEATER_SCHEMA.add_extra(entity_duplicate_validator("water_heater")) + + +def water_heater_schema( + class_: MockObjClass, + *, + icon: str = cv.UNDEFINED, + entity_category: str = cv.UNDEFINED, +) -> cv.Schema: + schema = {cv.GenerateID(): cv.declare_id(class_)} + + for key, default, validator in [ + (CONF_ICON, icon, cv.icon), + (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), + ]: + if default is not cv.UNDEFINED: + schema[cv.Optional(key, default=default)] = validator + + return _WATER_HEATER_SCHEMA.extend(schema) + + +async def setup_water_heater_core_(var: cg.Pvariable, config: ConfigType) -> None: + """Set up the core water heater properties in C++.""" + await setup_entity(var, config, "water_heater") + + visual = config[CONF_VISUAL] + if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: + cg.add_define("USE_WATER_HEATER_VISUAL_OVERRIDES") + cg.add(var.set_visual_min_temperature_override(min_temp)) + if (max_temp := visual.get(CONF_MAX_TEMPERATURE)) is not None: + cg.add_define("USE_WATER_HEATER_VISUAL_OVERRIDES") + cg.add(var.set_visual_max_temperature_override(max_temp)) + if (temp_step := visual.get(CONF_TARGET_TEMPERATURE_STEP)) is not None: + cg.add_define("USE_WATER_HEATER_VISUAL_OVERRIDES") + cg.add(var.set_visual_target_temperature_step_override(temp_step)) + + +async def register_water_heater(var: cg.Pvariable, config: ConfigType) -> cg.Pvariable: + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + + cg.add_define("USE_WATER_HEATER") + + await cg.register_component(var, config) + + cg.add(cg.App.register_water_heater(var)) + + CORE.register_platform_component("water_heater", var) + await setup_water_heater_core_(var, config) + return var + + +async def new_water_heater(config: ConfigType, *args) -> cg.Pvariable: + var = cg.new_Pvariable(config[CONF_ID], *args) + await register_water_heater(var, config) + return var + + +@coroutine_with_priority(CoroPriority.CORE) +async def to_code(config: ConfigType) -> None: + cg.add_global(water_heater_ns.using) diff --git a/esphome/components/water_heater/water_heater.cpp b/esphome/components/water_heater/water_heater.cpp new file mode 100644 index 0000000000..d092203d06 --- /dev/null +++ b/esphome/components/water_heater/water_heater.cpp @@ -0,0 +1,283 @@ +#include "water_heater.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/core/controller_registry.h" + +#include + +namespace esphome::water_heater { + +static const char *const TAG = "water_heater"; + +void log_water_heater(const char *tag, const char *prefix, const char *type, WaterHeater *obj) { + if (obj != nullptr) { + ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); + } +} + +WaterHeaterCall::WaterHeaterCall(WaterHeater *parent) : parent_(parent) {} + +WaterHeaterCall &WaterHeaterCall::set_mode(WaterHeaterMode mode) { + this->mode_ = mode; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_mode(const std::string &mode) { + if (str_equals_case_insensitive(mode, "OFF")) { + this->set_mode(WATER_HEATER_MODE_OFF); + } else if (str_equals_case_insensitive(mode, "ECO")) { + this->set_mode(WATER_HEATER_MODE_ECO); + } else if (str_equals_case_insensitive(mode, "ELECTRIC")) { + this->set_mode(WATER_HEATER_MODE_ELECTRIC); + } else if (str_equals_case_insensitive(mode, "PERFORMANCE")) { + this->set_mode(WATER_HEATER_MODE_PERFORMANCE); + } else if (str_equals_case_insensitive(mode, "HIGH_DEMAND")) { + this->set_mode(WATER_HEATER_MODE_HIGH_DEMAND); + } else if (str_equals_case_insensitive(mode, "HEAT_PUMP")) { + this->set_mode(WATER_HEATER_MODE_HEAT_PUMP); + } else if (str_equals_case_insensitive(mode, "GAS")) { + this->set_mode(WATER_HEATER_MODE_GAS); + } else { + ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str()); + } + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_target_temperature(float temperature) { + this->target_temperature_ = temperature; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_target_temperature_low(float temperature) { + this->target_temperature_low_ = temperature; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_target_temperature_high(float temperature) { + this->target_temperature_high_ = temperature; + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_away(bool away) { + if (away) { + this->state_ |= WATER_HEATER_STATE_AWAY; + } else { + this->state_ &= ~WATER_HEATER_STATE_AWAY; + } + return *this; +} + +WaterHeaterCall &WaterHeaterCall::set_on(bool on) { + if (on) { + this->state_ |= WATER_HEATER_STATE_ON; + } else { + this->state_ &= ~WATER_HEATER_STATE_ON; + } + return *this; +} + +void WaterHeaterCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + this->validate_(); + if (this->mode_.has_value()) { + ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(water_heater_mode_to_string(*this->mode_))); + } + if (!std::isnan(this->target_temperature_)) { + ESP_LOGD(TAG, " Target Temperature: %.2f", this->target_temperature_); + } + if (!std::isnan(this->target_temperature_low_)) { + ESP_LOGD(TAG, " Target Temperature Low: %.2f", this->target_temperature_low_); + } + if (!std::isnan(this->target_temperature_high_)) { + ESP_LOGD(TAG, " Target Temperature High: %.2f", this->target_temperature_high_); + } + if (this->state_ & WATER_HEATER_STATE_AWAY) { + ESP_LOGD(TAG, " Away: YES"); + } + if (this->state_ & WATER_HEATER_STATE_ON) { + ESP_LOGD(TAG, " On: YES"); + } + this->parent_->control(*this); +} + +void WaterHeaterCall::validate_() { + auto traits = this->parent_->get_traits(); + if (this->mode_.has_value()) { + if (!traits.supports_mode(*this->mode_)) { + ESP_LOGW(TAG, "'%s' - Mode %d not supported", this->parent_->get_name().c_str(), *this->mode_); + this->mode_.reset(); + } + } + if (!std::isnan(this->target_temperature_)) { + if (traits.get_supports_two_point_target_temperature()) { + ESP_LOGW(TAG, "'%s' - Cannot set target temperature for device with two-point target temperature", + this->parent_->get_name().c_str()); + this->target_temperature_ = NAN; + } else if (this->target_temperature_ < traits.get_min_temperature() || + this->target_temperature_ > traits.get_max_temperature()) { + ESP_LOGW(TAG, "'%s' - Target temperature %.1f is out of range [%.1f - %.1f]", this->parent_->get_name().c_str(), + this->target_temperature_, traits.get_min_temperature(), traits.get_max_temperature()); + this->target_temperature_ = + std::max(traits.get_min_temperature(), std::min(this->target_temperature_, traits.get_max_temperature())); + } + } + if (!std::isnan(this->target_temperature_low_) || !std::isnan(this->target_temperature_high_)) { + if (!traits.get_supports_two_point_target_temperature()) { + ESP_LOGW(TAG, "'%s' - Cannot set low/high target temperature", this->parent_->get_name().c_str()); + this->target_temperature_low_ = NAN; + this->target_temperature_high_ = NAN; + } + } + if (!std::isnan(this->target_temperature_low_) && !std::isnan(this->target_temperature_high_)) { + if (this->target_temperature_low_ > this->target_temperature_high_) { + ESP_LOGW(TAG, "'%s' - Target temperature low %.2f must be less than high %.2f", this->parent_->get_name().c_str(), + this->target_temperature_low_, this->target_temperature_high_); + this->target_temperature_low_ = NAN; + this->target_temperature_high_ = NAN; + } + } + if ((this->state_ & WATER_HEATER_STATE_AWAY) && !traits.get_supports_away_mode()) { + ESP_LOGW(TAG, "'%s' - Away mode not supported", this->parent_->get_name().c_str()); + this->state_ &= ~WATER_HEATER_STATE_AWAY; + } + // If ON/OFF not supported, device is always on - clear the flag silently + if (!traits.has_feature_flags(WATER_HEATER_SUPPORTS_ON_OFF)) { + this->state_ &= ~WATER_HEATER_STATE_ON; + } +} + +void WaterHeater::setup() { + this->pref_ = global_preferences->make_preference(this->get_preference_hash()); +} + +void WaterHeater::publish_state() { + auto traits = this->get_traits(); + ESP_LOGD(TAG, + "'%s' - Sending state:\n" + " Mode: %s", + this->name_.c_str(), LOG_STR_ARG(water_heater_mode_to_string(this->mode_))); + if (!std::isnan(this->current_temperature_)) { + ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature_); + } + if (traits.get_supports_two_point_target_temperature()) { + ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low_, + this->target_temperature_high_); + } else if (!std::isnan(this->target_temperature_)) { + ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature_); + } + if (this->state_ & WATER_HEATER_STATE_AWAY) { + ESP_LOGD(TAG, " Away: YES"); + } + if (traits.has_feature_flags(WATER_HEATER_SUPPORTS_ON_OFF)) { + ESP_LOGD(TAG, " On: %s", (this->state_ & WATER_HEATER_STATE_ON) ? "YES" : "NO"); + } + +#if defined(USE_WATER_HEATER) && defined(USE_CONTROLLER_REGISTRY) + ControllerRegistry::notify_water_heater_update(this); +#endif + + SavedWaterHeaterState saved{}; + saved.mode = this->mode_; + if (traits.get_supports_two_point_target_temperature()) { + saved.target_temperature_low = this->target_temperature_low_; + saved.target_temperature_high = this->target_temperature_high_; + } else { + saved.target_temperature = this->target_temperature_; + } + saved.state = this->state_; + this->pref_.save(&saved); +} + +optional WaterHeater::restore_state() { + SavedWaterHeaterState recovered{}; + if (!this->pref_.load(&recovered)) + return {}; + + auto traits = this->get_traits(); + auto call = this->make_call(); + call.set_mode(recovered.mode); + if (traits.get_supports_two_point_target_temperature()) { + call.set_target_temperature_low(recovered.target_temperature_low); + call.set_target_temperature_high(recovered.target_temperature_high); + } else { + call.set_target_temperature(recovered.target_temperature); + } + call.set_away((recovered.state & WATER_HEATER_STATE_AWAY) != 0); + call.set_on((recovered.state & WATER_HEATER_STATE_ON) != 0); + return call; +} + +WaterHeaterTraits WaterHeater::get_traits() { + auto traits = this->traits(); +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES + if (!std::isnan(this->visual_min_temperature_override_)) { + traits.set_min_temperature(this->visual_min_temperature_override_); + } + if (!std::isnan(this->visual_max_temperature_override_)) { + traits.set_max_temperature(this->visual_max_temperature_override_); + } + if (!std::isnan(this->visual_target_temperature_step_override_)) { + traits.set_target_temperature_step(this->visual_target_temperature_step_override_); + } +#endif + return traits; +} + +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES +void WaterHeater::set_visual_min_temperature_override(float min_temperature_override) { + this->visual_min_temperature_override_ = min_temperature_override; +} +void WaterHeater::set_visual_max_temperature_override(float max_temperature_override) { + this->visual_max_temperature_override_ = max_temperature_override; +} +void WaterHeater::set_visual_target_temperature_step_override(float visual_target_temperature_step_override) { + this->visual_target_temperature_step_override_ = visual_target_temperature_step_override; +} +#endif + +const LogString *water_heater_mode_to_string(WaterHeaterMode mode) { + switch (mode) { + case WATER_HEATER_MODE_OFF: + return LOG_STR("OFF"); + case WATER_HEATER_MODE_ECO: + return LOG_STR("ECO"); + case WATER_HEATER_MODE_ELECTRIC: + return LOG_STR("ELECTRIC"); + case WATER_HEATER_MODE_PERFORMANCE: + return LOG_STR("PERFORMANCE"); + case WATER_HEATER_MODE_HIGH_DEMAND: + return LOG_STR("HIGH_DEMAND"); + case WATER_HEATER_MODE_HEAT_PUMP: + return LOG_STR("HEAT_PUMP"); + case WATER_HEATER_MODE_GAS: + return LOG_STR("GAS"); + default: + return LOG_STR("UNKNOWN"); + } +} + +void WaterHeater::dump_traits_(const char *tag) { + auto traits = this->get_traits(); + ESP_LOGCONFIG(tag, + " Min Temperature: %.1f°C\n" + " Max Temperature: %.1f°C\n" + " Temperature Step: %.1f", + traits.get_min_temperature(), traits.get_max_temperature(), traits.get_target_temperature_step()); + if (traits.get_supports_two_point_target_temperature()) { + ESP_LOGCONFIG(tag, " Supports Two-Point Target Temperature: YES"); + } + if (traits.get_supports_away_mode()) { + ESP_LOGCONFIG(tag, " Supports Away Mode: YES"); + } + if (traits.has_feature_flags(WATER_HEATER_SUPPORTS_ON_OFF)) { + ESP_LOGCONFIG(tag, " Supports On/Off: YES"); + } + if (!traits.get_supported_modes().empty()) { + ESP_LOGCONFIG(tag, " Supported Modes:"); + for (WaterHeaterMode m : traits.get_supported_modes()) { + ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(water_heater_mode_to_string(m))); + } + } +} + +} // namespace esphome::water_heater diff --git a/esphome/components/water_heater/water_heater.h b/esphome/components/water_heater/water_heater.h new file mode 100644 index 0000000000..e223dd59b2 --- /dev/null +++ b/esphome/components/water_heater/water_heater.h @@ -0,0 +1,259 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/finite_set_mask.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/preferences.h" + +namespace esphome::water_heater { + +class WaterHeater; +struct WaterHeaterCallInternal; + +void log_water_heater(const char *tag, const char *prefix, const char *type, WaterHeater *obj); +#define LOG_WATER_HEATER(prefix, type, obj) log_water_heater(TAG, prefix, LOG_STR_LITERAL(type), obj) + +enum WaterHeaterMode : uint32_t { + WATER_HEATER_MODE_OFF = 0, + WATER_HEATER_MODE_ECO = 1, + WATER_HEATER_MODE_ELECTRIC = 2, + WATER_HEATER_MODE_PERFORMANCE = 3, + WATER_HEATER_MODE_HIGH_DEMAND = 4, + WATER_HEATER_MODE_HEAT_PUMP = 5, + WATER_HEATER_MODE_GAS = 6, +}; + +// Type alias for water heater mode bitmask +// Replaces std::set to eliminate red-black tree overhead +using WaterHeaterModeMask = + FiniteSetMask>; + +/// Feature flags for water heater capabilities (matches Home Assistant WaterHeaterEntityFeature) +enum WaterHeaterFeature : uint32_t { + /// The water heater supports reporting the current temperature. + WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE = 1 << 0, + /// The water heater supports a target temperature. + WATER_HEATER_SUPPORTS_TARGET_TEMPERATURE = 1 << 1, + /// The water heater supports operation mode selection. + WATER_HEATER_SUPPORTS_OPERATION_MODE = 1 << 2, + /// The water heater supports an away/vacation mode. + WATER_HEATER_SUPPORTS_AWAY_MODE = 1 << 3, + /// The water heater can be turned on/off. + WATER_HEATER_SUPPORTS_ON_OFF = 1 << 4, + /// The water heater supports two-point target temperature (low/high range). + WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE = 1 << 5, +}; + +/// State flags for water heater current state (bitmask) +enum WaterHeaterStateFlag : uint32_t { + /// Away/vacation mode is currently active + WATER_HEATER_STATE_AWAY = 1 << 0, + /// Water heater is on (not in standby) + WATER_HEATER_STATE_ON = 1 << 1, +}; + +struct SavedWaterHeaterState { + WaterHeaterMode mode; + union { + float target_temperature; + struct { + float target_temperature_low; + float target_temperature_high; + }; + } __attribute__((packed)); + uint32_t state; +} __attribute__((packed)); + +class WaterHeaterCall { + friend struct WaterHeaterCallInternal; + + public: + WaterHeaterCall() : parent_(nullptr) {} + + WaterHeaterCall(WaterHeater *parent); + + WaterHeaterCall &set_mode(WaterHeaterMode mode); + WaterHeaterCall &set_mode(const std::string &mode); + WaterHeaterCall &set_target_temperature(float temperature); + WaterHeaterCall &set_target_temperature_low(float temperature); + WaterHeaterCall &set_target_temperature_high(float temperature); + WaterHeaterCall &set_away(bool away); + WaterHeaterCall &set_on(bool on); + + void perform(); + + const optional &get_mode() const { return this->mode_; } + float get_target_temperature() const { return this->target_temperature_; } + float get_target_temperature_low() const { return this->target_temperature_low_; } + float get_target_temperature_high() const { return this->target_temperature_high_; } + /// Get state flags value + uint32_t get_state() const { return this->state_; } + + protected: + void validate_(); + WaterHeater *parent_; + optional mode_; + float target_temperature_{NAN}; + float target_temperature_low_{NAN}; + float target_temperature_high_{NAN}; + uint32_t state_{0}; +}; + +struct WaterHeaterCallInternal : public WaterHeaterCall { + WaterHeaterCallInternal(WaterHeater *parent) : WaterHeaterCall(parent) {} + + WaterHeaterCallInternal &set_from_restore(const WaterHeaterCall &restore) { + this->mode_ = restore.mode_; + this->target_temperature_ = restore.target_temperature_; + this->target_temperature_low_ = restore.target_temperature_low_; + this->target_temperature_high_ = restore.target_temperature_high_; + this->state_ = restore.state_; + return *this; + } +}; + +class WaterHeaterTraits { + public: + /// Get/set feature flags (see WaterHeaterFeature enum) + void add_feature_flags(uint32_t flags) { this->feature_flags_ |= flags; } + void clear_feature_flags(uint32_t flags) { this->feature_flags_ &= ~flags; } + bool has_feature_flags(uint32_t flags) const { return (this->feature_flags_ & flags) == flags; } + uint32_t get_feature_flags() const { return this->feature_flags_; } + + bool get_supports_current_temperature() const { + return this->has_feature_flags(WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE); + } + void set_supports_current_temperature(bool supports) { + if (supports) { + this->add_feature_flags(WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE); + } else { + this->clear_feature_flags(WATER_HEATER_SUPPORTS_CURRENT_TEMPERATURE); + } + } + + bool get_supports_away_mode() const { return this->has_feature_flags(WATER_HEATER_SUPPORTS_AWAY_MODE); } + void set_supports_away_mode(bool supports) { + if (supports) { + this->add_feature_flags(WATER_HEATER_SUPPORTS_AWAY_MODE); + } else { + this->clear_feature_flags(WATER_HEATER_SUPPORTS_AWAY_MODE); + } + } + + bool get_supports_two_point_target_temperature() const { + return this->has_feature_flags(WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE); + } + void set_supports_two_point_target_temperature(bool supports) { + if (supports) { + this->add_feature_flags(WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE); + } else { + this->clear_feature_flags(WATER_HEATER_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE); + } + } + + void set_min_temperature(float min_temperature) { this->min_temperature_ = min_temperature; } + float get_min_temperature() const { return this->min_temperature_; } + + void set_max_temperature(float max_temperature) { this->max_temperature_ = max_temperature; } + float get_max_temperature() const { return this->max_temperature_; } + + void set_target_temperature_step(float target_temperature_step) { + this->target_temperature_step_ = target_temperature_step; + } + float get_target_temperature_step() const { return this->target_temperature_step_; } + + void set_supported_modes(WaterHeaterModeMask modes) { this->supported_modes_ = modes; } + const WaterHeaterModeMask &get_supported_modes() const { return this->supported_modes_; } + bool supports_mode(WaterHeaterMode mode) const { return this->supported_modes_.count(mode); } + + protected: + // Ordered to minimize padding: 4-byte members first + uint32_t feature_flags_{0}; + float min_temperature_{0.0f}; + float max_temperature_{0.0f}; + float target_temperature_step_{0.0f}; + WaterHeaterModeMask supported_modes_; +}; + +class WaterHeater : public EntityBase, public Component { + public: + WaterHeaterMode get_mode() const { return this->mode_; } + float get_current_temperature() const { return this->current_temperature_; } + float get_target_temperature() const { return this->target_temperature_; } + float get_target_temperature_low() const { return this->target_temperature_low_; } + float get_target_temperature_high() const { return this->target_temperature_high_; } + /// Get the current state flags bitmask + uint32_t get_state() const { return this->state_; } + /// Check if away mode is currently active + bool is_away() const { return (this->state_ & WATER_HEATER_STATE_AWAY) != 0; } + /// Check if the water heater is on + bool is_on() const { return (this->state_ & WATER_HEATER_STATE_ON) != 0; } + + void set_current_temperature(float current_temperature) { this->current_temperature_ = current_temperature; } + + virtual void publish_state(); + virtual WaterHeaterTraits get_traits(); + virtual WaterHeaterCallInternal make_call() = 0; + +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES + void set_visual_min_temperature_override(float min_temperature_override); + void set_visual_max_temperature_override(float max_temperature_override); + void set_visual_target_temperature_step_override(float visual_target_temperature_step_override); +#endif + virtual void control(const WaterHeaterCall &call) = 0; + + void setup() override; + + optional restore_state(); + + protected: + virtual WaterHeaterTraits traits() = 0; + + /// Log the traits of this water heater for dump_config(). + void dump_traits_(const char *tag); + + /// Set the mode of the water heater. Should only be called from control(). + void set_mode_(WaterHeaterMode mode) { this->mode_ = mode; } + /// Set the target temperature of the water heater. Should only be called from control(). + void set_target_temperature_(float target_temperature) { this->target_temperature_ = target_temperature; } + /// Set the low target temperature (for two-point control). Should only be called from control(). + void set_target_temperature_low_(float target_temperature_low) { + this->target_temperature_low_ = target_temperature_low; + } + /// Set the high target temperature (for two-point control). Should only be called from control(). + void set_target_temperature_high_(float target_temperature_high) { + this->target_temperature_high_ = target_temperature_high; + } + /// Set the state flags. Should only be called from control(). + void set_state_(uint32_t state) { this->state_ = state; } + /// Set or clear a state flag. Should only be called from control(). + void set_state_flag_(uint32_t flag, bool value) { + if (value) { + this->state_ |= flag; + } else { + this->state_ &= ~flag; + } + } + + WaterHeaterMode mode_{WATER_HEATER_MODE_OFF}; + float current_temperature_{NAN}; + float target_temperature_{NAN}; + float target_temperature_low_{NAN}; + float target_temperature_high_{NAN}; + uint32_t state_{0}; // Bitmask of WaterHeaterStateFlag + +#ifdef USE_WATER_HEATER_VISUAL_OVERRIDES + float visual_min_temperature_override_{NAN}; + float visual_max_temperature_override_{NAN}; + float visual_target_temperature_step_override_{NAN}; +#endif + + ESPPreferenceObject pref_; +}; + +/// Convert the given WaterHeaterMode to a human-readable string for logging. +const LogString *water_heater_mode_to_string(WaterHeaterMode mode); + +} // namespace esphome::water_heater diff --git a/esphome/components/waveshare_epaper/waveshare_213v3.cpp b/esphome/components/waveshare_epaper/waveshare_213v3.cpp index 068cb91d31..b55f3c8d26 100644 --- a/esphome/components/waveshare_epaper/waveshare_213v3.cpp +++ b/esphome/components/waveshare_epaper/waveshare_213v3.cpp @@ -177,10 +177,10 @@ uint32_t WaveshareEPaper2P13InV3::idle_timeout_() { return 5000; } void WaveshareEPaper2P13InV3::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this) ESP_LOGCONFIG(TAG, " Model: 2.13inV3"); - LOG_PIN(" CS Pin: ", this->cs_) - LOG_PIN(" Reset Pin: ", this->reset_pin_) - LOG_PIN(" DC Pin: ", this->dc_pin_) - LOG_PIN(" Busy Pin: ", this->busy_pin_) + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 3510d157d6..4db9438206 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -172,6 +172,12 @@ void WaveshareEPaperBase::update() { this->display(); } void WaveshareEPaper::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + // flip logic const uint8_t fill = color.is_on() ? 0x00 : 0xFF; for (uint32_t i = 0; i < this->get_buffer_length_(); i++) @@ -234,6 +240,12 @@ uint8_t WaveshareEPaper7C::color_to_hex(Color color) { return hex_code; } void WaveshareEPaper7C::fill(Color color) { + // If clipping is active, use base class (3-bit packing is complex for partial fills) + if (this->get_clipping().is_set()) { + display::Display::fill(color); + return; + } + uint8_t pixel_color; if (color.is_on()) { pixel_color = this->color_to_hex(color); @@ -1813,8 +1825,10 @@ void WaveshareEPaper2P9InV2R2::write_lut_(const uint8_t *lut, const uint8_t size void WaveshareEPaper2P9InV2R2::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 2.9inV2R2"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 2.9inV2R2\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); @@ -2516,8 +2530,10 @@ int GDEY042T81::get_height_internal() { return 300; } uint32_t GDEY042T81::idle_timeout_() { return 5000; } void GDEY042T81::dump_config() { LOG_DISPLAY("", "GoodDisplay E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 4.2in B/W GDEY042T81"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 4.2in B/W GDEY042T81\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); @@ -3147,8 +3163,10 @@ int GDEY0583T81::get_height_internal() { return 480; } uint32_t GDEY0583T81::idle_timeout_() { return 5000; } void GDEY0583T81::dump_config() { LOG_DISPLAY("", "GoodDisplay E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 5.83in B/W GDEY0583T81"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 5.83in B/W GDEY0583T81\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); @@ -4328,8 +4346,10 @@ int WaveshareEPaper7P5InV2P::get_height_internal() { return 480; } uint32_t WaveshareEPaper7P5InV2P::idle_timeout_() { return 10000; } void WaveshareEPaper7P5InV2P::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); - ESP_LOGCONFIG(TAG, " Model: 7.50inv2p"); - ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + ESP_LOGCONFIG(TAG, + " Model: 7.50inv2p\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 17ad496f30..16ac9d054c 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -4,10 +4,12 @@ import gzip import esphome.codegen as cg from esphome.components import web_server_base +from esphome.components.logger import request_log_listener from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID import esphome.config_validation as cv from esphome.const import ( CONF_AUTH, + CONF_COMPRESSION, CONF_CSS_INCLUDE, CONF_CSS_URL, CONF_ENABLE_PRIVATE_NETWORK_ACCESS, @@ -201,6 +203,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_OTA): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, + cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"), cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), } ).extend(cv.COMPONENT_SCHEMA), @@ -311,6 +314,8 @@ async def to_code(config): if config.get(CONF_OTA) is False: cg.add_define("USE_WEBSERVER_OTA_DISABLED") cg.add(var.set_expose_log(config[CONF_LOG])) + if config[CONF_LOG]: + request_log_listener() # Request a log listener slot for web server log streaming if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]: cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS") if CONF_AUTH in config: @@ -330,6 +335,8 @@ async def to_code(config): cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) if CONF_LOCAL in config and config[CONF_LOCAL]: cg.add_define("USE_WEBSERVER_LOCAL") + if config[CONF_COMPRESSION] == "gzip": + cg.add_define("USE_WEBSERVER_GZIP") if (sorting_group_config := config.get(CONF_SORTING_GROUPS)) is not None: cg.add_define("USE_WEBSERVER_SORTING") diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index 6b27545549..0af9521326 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -6,8 +6,7 @@ #include "web_server.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { #ifdef USE_ESP32 ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, AsyncEventSource *es) : web_server_(ws), events_(es) {} @@ -135,6 +134,20 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont } #endif +#ifdef USE_WATER_HEATER +bool ListEntitiesIterator::on_water_heater(water_heater::WaterHeater *obj) { + this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::water_heater_all_json_generator); + return true; +} +#endif + +#ifdef USE_INFRARED +bool ListEntitiesIterator::on_infrared(infrared::Infrared *obj) { + // Infrared web_server support not yet implemented - this stub acknowledges the entity + return true; +} +#endif + #ifdef USE_EVENT bool ListEntitiesIterator::on_event(event::Event *obj) { // Null event type, since we are just iterating over entities @@ -150,6 +163,5 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *obj) { } #endif -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 43e1cc2544..d0a4fa2725 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -4,13 +4,13 @@ #ifdef USE_WEBSERVER #include "esphome/core/component.h" #include "esphome/core/component_iterator.h" -namespace esphome { +namespace esphome::web_server_idf { #ifdef USE_ESP32 -namespace web_server_idf { class AsyncEventSource; -} #endif -namespace web_server { +} // namespace esphome::web_server_idf + +namespace esphome::web_server { #if !defined(USE_ESP32) && defined(USE_ARDUINO) class DeferredUpdateEventSource; @@ -79,6 +79,12 @@ class ListEntitiesIterator : public ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *obj) override; #endif +#ifdef USE_WATER_HEATER + bool on_water_heater(water_heater::WaterHeater *obj) override; +#endif +#ifdef USE_INFRARED + bool on_infrared(infrared::Infrared *obj) override; +#endif #ifdef USE_EVENT bool on_event(event::Event *obj) override; #endif @@ -96,6 +102,5 @@ class ListEntitiesIterator : public ComponentIterator { #endif }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index 7929f3647f..3793f01eb5 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -10,9 +10,7 @@ #endif #ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include -#elif defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_LIBRETINY) #include #endif #endif // USE_ARDUINO @@ -23,8 +21,7 @@ using PlatformString = std::string; using PlatformString = String; #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { static const char *const TAG = "web_server.ota"; @@ -84,9 +81,9 @@ void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) { } else { ESP_LOGD(TAG, "OTA in progress: %" PRIu32 " bytes read", this->ota_read_length_); } -#ifdef USE_OTA_STATE_CALLBACK - // Report progress - use call_deferred since we're in web server task - this->parent_->state_callback_.call_deferred(ota::OTA_IN_PROGRESS, percentage, 0); +#ifdef USE_OTA_STATE_LISTENER + // Report progress - use notify_state_deferred_ since we're in web server task + this->parent_->notify_state_deferred_(ota::OTA_IN_PROGRESS, percentage, 0); #endif this->last_ota_progress_ = now; } @@ -114,17 +111,14 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf // Initialize OTA on first call this->ota_init_(filename.c_str()); -#ifdef USE_OTA_STATE_CALLBACK - // Notify OTA started - use call_deferred since we're in web server task - this->parent_->state_callback_.call_deferred(ota::OTA_STARTED, 0.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + // Notify OTA started - use notify_state_deferred_ since we're in web server task + this->parent_->notify_state_deferred_(ota::OTA_STARTED, 0.0f, 0); #endif // Platform-specific pre-initialization #ifdef USE_ARDUINO -#ifdef USE_ESP8266 - Update.runAsync(true); -#endif -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_LIBRETINY) if (Update.isRunning()) { Update.abort(); } @@ -134,9 +128,9 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf this->ota_backend_ = ota::make_ota_backend(); if (!this->ota_backend_) { ESP_LOGE(TAG, "Failed to create OTA backend"); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, - static_cast(ota::OTA_RESPONSE_ERROR_UNKNOWN)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, + static_cast(ota::OTA_RESPONSE_ERROR_UNKNOWN)); #endif return; } @@ -148,8 +142,8 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf if (error_code != ota::OTA_RESPONSE_OK) { ESP_LOGE(TAG, "OTA begin failed: %d", error_code); this->ota_backend_.reset(); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif return; } @@ -166,8 +160,8 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf ESP_LOGE(TAG, "OTA write failed: %d", error_code); this->ota_backend_->abort(); this->ota_backend_.reset(); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif return; } @@ -186,15 +180,15 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf error_code = this->ota_backend_->end(); if (error_code == ota::OTA_RESPONSE_OK) { this->ota_success_ = true; -#ifdef USE_OTA_STATE_CALLBACK - // Report completion before reboot - use call_deferred since we're in web server task - this->parent_->state_callback_.call_deferred(ota::OTA_COMPLETED, 100.0f, 0); +#ifdef USE_OTA_STATE_LISTENER + // Report completion before reboot - use notify_state_deferred_ since we're in web server task + this->parent_->notify_state_deferred_(ota::OTA_COMPLETED, 100.0f, 0); #endif this->schedule_ota_reboot_(); } else { ESP_LOGE(TAG, "OTA end failed: %d", error_code); -#ifdef USE_OTA_STATE_CALLBACK - this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#ifdef USE_OTA_STATE_LISTENER + this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif } this->ota_backend_.reset(); @@ -232,15 +226,10 @@ void WebServerOTAComponent::setup() { // AsyncWebServer takes ownership of the handler and will delete it when the server is destroyed base->add_handler(new OTARequestHandler(this)); // NOLINT -#ifdef USE_OTA_STATE_CALLBACK - // Register with global OTA callback system - ota::register_ota_platform(this); -#endif } void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); } -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/ota/ota_web_server.h b/esphome/components/web_server/ota/ota_web_server.h index a7170c0e34..53ff99899c 100644 --- a/esphome/components/web_server/ota/ota_web_server.h +++ b/esphome/components/web_server/ota/ota_web_server.h @@ -7,8 +7,7 @@ #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { class WebServerOTAComponent : public ota::OTAComponent { public: @@ -20,7 +19,6 @@ class WebServerOTAComponent : public ota::OTAComponent { friend class OTARequestHandler; }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/server_index_v2.h b/esphome/components/web_server/server_index_v2.h index e675d81552..b224354a6b 100644 --- a/esphome/components/web_server/server_index_v2.h +++ b/esphome/components/web_server/server_index_v2.h @@ -6,646 +6,1250 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { +#ifdef USE_WEBSERVER_GZIP const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x7d, 0xdb, 0x72, 0xdb, 0xc6, 0xb6, 0xe0, 0xf3, - 0xe4, 0x2b, 0x20, 0x44, 0x5b, 0x41, 0x6f, 0x36, 0x21, 0x92, 0x92, 0x6c, 0x19, 0x54, 0x93, 0x5b, 0x96, 0x9d, 0xed, - 0x64, 0xfb, 0x16, 0xcb, 0x4e, 0x76, 0xc2, 0x68, 0x4b, 0x10, 0xd1, 0x24, 0x3a, 0x06, 0xd1, 0x0c, 0xd0, 0xa4, 0xa4, - 0x90, 0x38, 0x35, 0x1f, 0x30, 0x55, 0x53, 0x35, 0x4f, 0xf3, 0x32, 0x35, 0xe7, 0x61, 0x3e, 0x62, 0x9e, 0xcf, 0xa7, - 0x9c, 0x1f, 0x98, 0xf9, 0x84, 0xa9, 0xd5, 0x17, 0xa0, 0xc1, 0x8b, 0xac, 0x5c, 0xce, 0x39, 0x53, 0x2e, 0xdb, 0x44, - 0xa3, 0x2f, 0xab, 0x57, 0xaf, 0x5e, 0xf7, 0x6e, 0x9c, 0xec, 0x44, 0x7c, 0x28, 0xee, 0xa6, 0xd4, 0x89, 0xc5, 0x24, - 0xe9, 0x9d, 0xe8, 0x7f, 0x69, 0x18, 0xf5, 0x4e, 0x12, 0x96, 0x7e, 0x74, 0x32, 0x9a, 0x10, 0x36, 0xe4, 0xa9, 0x13, - 0x67, 0x74, 0x44, 0xa2, 0x50, 0x84, 0x01, 0x9b, 0x84, 0x63, 0xea, 0xec, 0xf7, 0x4e, 0x26, 0x54, 0x84, 0xce, 0x30, - 0x0e, 0xb3, 0x9c, 0x0a, 0xf2, 0xe1, 0xfd, 0x97, 0xcd, 0xe3, 0xde, 0x49, 0x3e, 0xcc, 0xd8, 0x54, 0x38, 0xd0, 0x25, - 0x99, 0xf0, 0x68, 0x96, 0xd0, 0xde, 0xfe, 0xfe, 0xcd, 0xcd, 0x8d, 0xff, 0x53, 0xfe, 0xd9, 0x90, 0xa7, 0xb9, 0x70, - 0x5e, 0x92, 0x1b, 0x96, 0x46, 0xfc, 0x06, 0x33, 0x41, 0x5e, 0xfa, 0xe7, 0x71, 0x18, 0xf1, 0x9b, 0x77, 0x9c, 0x8b, - 0xbd, 0x3d, 0x4f, 0x3d, 0xde, 0x9d, 0x9d, 0x9f, 0x13, 0x42, 0xe6, 0x9c, 0x45, 0x4e, 0x6b, 0xb9, 0xac, 0x0a, 0xfd, - 0x34, 0x14, 0x6c, 0x4e, 0x55, 0x13, 0xb4, 0xb7, 0xe7, 0x86, 0x11, 0x9f, 0x0a, 0x1a, 0x9d, 0x8b, 0xbb, 0x84, 0x9e, - 0xc7, 0x94, 0x8a, 0xdc, 0x65, 0xa9, 0xf3, 0x8c, 0x0f, 0x67, 0x13, 0x9a, 0x0a, 0x7f, 0x9a, 0x71, 0xc1, 0x01, 0x92, - 0xbd, 0x3d, 0x37, 0xa3, 0xd3, 0x24, 0x1c, 0x52, 0x78, 0x7f, 0x76, 0x7e, 0x5e, 0xb5, 0xa8, 0x2a, 0xe1, 0x5c, 0x90, - 0xf3, 0xbb, 0xc9, 0x35, 0x4f, 0x3c, 0x84, 0x43, 0x41, 0x52, 0x7a, 0xe3, 0x7c, 0x47, 0xc3, 0x8f, 0xaf, 0xc2, 0x69, - 0x77, 0x98, 0x84, 0x79, 0xee, 0xdc, 0x88, 0x85, 0x9c, 0x42, 0x36, 0x1b, 0x0a, 0x9e, 0x79, 0x02, 0x53, 0xcc, 0xd0, - 0x82, 0x8d, 0x3c, 0x11, 0xb3, 0xdc, 0xbf, 0xdc, 0x1d, 0xe6, 0xf9, 0x3b, 0x9a, 0xcf, 0x12, 0xb1, 0x4b, 0x76, 0x5a, - 0x98, 0xed, 0x10, 0x92, 0x0b, 0x24, 0xe2, 0x8c, 0xdf, 0x38, 0xcf, 0xb3, 0x8c, 0x67, 0x9e, 0x7b, 0x76, 0x7e, 0xae, - 0x6a, 0x38, 0x2c, 0x77, 0x52, 0x2e, 0x9c, 0xb2, 0xbf, 0xf0, 0x3a, 0xa1, 0xbe, 0xf3, 0x21, 0xa7, 0xce, 0xd5, 0x2c, - 0xcd, 0xc3, 0x11, 0x3d, 0x3b, 0x3f, 0xbf, 0x72, 0x78, 0xe6, 0x5c, 0x0d, 0xf3, 0xfc, 0xca, 0x61, 0x69, 0x2e, 0x68, - 0x18, 0xf9, 0x2e, 0xea, 0xca, 0xc1, 0x86, 0x79, 0xfe, 0x9e, 0xde, 0x0a, 0x22, 0xb0, 0x7c, 0x14, 0x84, 0x16, 0x63, - 0x2a, 0x9c, 0xbc, 0x9c, 0x97, 0x87, 0x16, 0x09, 0x15, 0x8e, 0x20, 0xf2, 0x3d, 0xef, 0x2a, 0xdc, 0x53, 0xf5, 0x28, - 0xba, 0x6c, 0xe4, 0x31, 0xb1, 0xb7, 0x27, 0x4a, 0x3c, 0x23, 0x35, 0x35, 0x87, 0x11, 0xba, 0x63, 0xca, 0xf6, 0xf6, - 0xa8, 0x9f, 0xd0, 0x74, 0x2c, 0x62, 0x42, 0x48, 0xbb, 0xcb, 0xf6, 0xf6, 0x3c, 0x41, 0x42, 0xe1, 0x8f, 0xa9, 0xf0, - 0x28, 0x42, 0xb8, 0x6a, 0xbd, 0xb7, 0xe7, 0x29, 0x24, 0x70, 0xa2, 0x10, 0x57, 0xc3, 0x31, 0xf2, 0x35, 0xf6, 0xcf, - 0xef, 0xd2, 0xa1, 0x67, 0xc3, 0x8f, 0x30, 0xdb, 0xdb, 0x0b, 0x85, 0x9f, 0x43, 0x8f, 0x58, 0x20, 0x54, 0x64, 0x54, - 0xcc, 0xb2, 0xd4, 0x11, 0x85, 0xe0, 0xe7, 0x22, 0x63, 0xe9, 0xd8, 0x43, 0x0b, 0x53, 0x66, 0x35, 0x2c, 0x0a, 0x05, - 0xee, 0x07, 0x41, 0x52, 0xd2, 0x83, 0x11, 0x6f, 0x84, 0x07, 0xab, 0xc8, 0x47, 0x4e, 0x4a, 0x88, 0x9b, 0xcb, 0xb6, - 0x6e, 0x3f, 0x0d, 0xd2, 0x86, 0xeb, 0x62, 0x05, 0x25, 0xce, 0x05, 0xc2, 0x6f, 0x88, 0x97, 0x62, 0xdf, 0xf7, 0x05, - 0x22, 0xbd, 0x85, 0xc1, 0x4a, 0x6a, 0xcd, 0xb3, 0x9f, 0x0e, 0x5a, 0x17, 0x81, 0xf0, 0x33, 0x1a, 0xcd, 0x86, 0xd4, - 0xf3, 0x18, 0xce, 0x71, 0x86, 0x48, 0x8f, 0x35, 0x3c, 0x4e, 0x7a, 0xb0, 0xdc, 0xbc, 0xbe, 0xd6, 0x84, 0xec, 0xb4, - 0x90, 0x86, 0x91, 0x1b, 0x00, 0x01, 0xc3, 0x1a, 0x1e, 0x4e, 0x88, 0x9b, 0xce, 0x26, 0xd7, 0x34, 0x73, 0xcb, 0x6a, - 0xdd, 0x1a, 0x59, 0xcc, 0x72, 0xea, 0x0c, 0xf3, 0xdc, 0x19, 0xcd, 0xd2, 0xa1, 0x60, 0x3c, 0x75, 0xdc, 0x06, 0x6f, - 0xb8, 0x8a, 0x1c, 0x4a, 0x6a, 0x70, 0x51, 0x81, 0xbc, 0x1c, 0x35, 0xd2, 0x41, 0xd6, 0x68, 0x5f, 0x60, 0x80, 0x12, - 0x75, 0x75, 0x7f, 0x1a, 0x01, 0x14, 0xa7, 0x30, 0xc7, 0x02, 0xbf, 0x17, 0x30, 0x4b, 0x39, 0x45, 0x26, 0xfa, 0xa9, - 0xbf, 0xbe, 0x51, 0x88, 0xf0, 0x27, 0xe1, 0xd4, 0xa3, 0xa4, 0x47, 0x25, 0x71, 0x85, 0xe9, 0x10, 0x60, 0xad, 0xad, - 0x5b, 0x9f, 0x06, 0xd4, 0xaf, 0x48, 0x0a, 0x05, 0xc2, 0x1f, 0xf1, 0xec, 0x79, 0x38, 0x8c, 0xa1, 0x5d, 0x49, 0x30, - 0x91, 0xd9, 0x6f, 0xc3, 0x8c, 0x86, 0x82, 0x3e, 0x4f, 0x28, 0x3c, 0x79, 0xae, 0x6c, 0xe9, 0x22, 0x9c, 0x93, 0x97, - 0x7e, 0xc2, 0xc4, 0x6b, 0x9e, 0x0e, 0x69, 0x37, 0xb7, 0xa8, 0x8b, 0xc1, 0xba, 0x9f, 0x0a, 0x91, 0xb1, 0xeb, 0x99, - 0xa0, 0x9e, 0x9b, 0x42, 0x0d, 0x17, 0xe7, 0x08, 0x33, 0x5f, 0xd0, 0x5b, 0x71, 0xc6, 0x53, 0x41, 0x53, 0x41, 0xa8, - 0x41, 0x2a, 0x4e, 0xfd, 0x70, 0x3a, 0xa5, 0x69, 0x74, 0x16, 0xb3, 0x24, 0xf2, 0x18, 0x2a, 0x50, 0x81, 0x63, 0x41, - 0x60, 0x8e, 0xa4, 0x97, 0x06, 0xf0, 0xcf, 0xf6, 0xd9, 0x78, 0x82, 0xf4, 0xe4, 0xa6, 0xa0, 0xc4, 0x75, 0xbb, 0x23, - 0x9e, 0x79, 0x7a, 0x06, 0x0e, 0x1f, 0x39, 0x02, 0xc6, 0x78, 0x37, 0x4b, 0x68, 0x8e, 0x68, 0x83, 0xb0, 0x72, 0x19, - 0x35, 0x82, 0x3f, 0x00, 0xc5, 0x17, 0xc8, 0x4b, 0x51, 0x90, 0x76, 0xe7, 0x61, 0xe6, 0x7c, 0xa7, 0x77, 0xd4, 0x33, - 0xc3, 0xcd, 0x86, 0x82, 0x3c, 0xf3, 0x45, 0x36, 0xcb, 0x05, 0x8d, 0xde, 0xdf, 0x4d, 0x69, 0x8e, 0x5f, 0x0b, 0x32, - 0x14, 0xfd, 0xa1, 0xf0, 0xe9, 0x64, 0x2a, 0xee, 0xce, 0x25, 0x63, 0x0c, 0x5c, 0x17, 0x47, 0x50, 0x33, 0xa3, 0xe1, - 0x10, 0x98, 0x99, 0xc6, 0xd6, 0x5b, 0x9e, 0xdc, 0x8d, 0x58, 0x92, 0x9c, 0xcf, 0xa6, 0x53, 0x9e, 0x09, 0xfc, 0x77, - 0xb2, 0x10, 0xbc, 0x42, 0x0d, 0xac, 0xe5, 0x22, 0xbf, 0x61, 0x62, 0x18, 0x7b, 0x02, 0x2d, 0x86, 0x61, 0x4e, 0x9d, - 0xa7, 0x9c, 0x27, 0x34, 0x84, 0x49, 0xa7, 0xfd, 0xd7, 0x22, 0x48, 0x67, 0x49, 0xd2, 0xbd, 0xce, 0x68, 0xf8, 0xb1, - 0x2b, 0x5f, 0xbf, 0xb9, 0xfe, 0x89, 0x0e, 0x45, 0x20, 0x7f, 0x9f, 0x66, 0x59, 0x78, 0x07, 0x15, 0x09, 0x81, 0x6a, - 0xfd, 0x34, 0xf8, 0xfa, 0xfc, 0xcd, 0x6b, 0x5f, 0x6d, 0x12, 0x36, 0xba, 0xf3, 0xd2, 0x72, 0xe3, 0xa5, 0x05, 0x1e, - 0x65, 0x7c, 0xb2, 0x32, 0xb4, 0xc2, 0x5a, 0xda, 0xdd, 0x02, 0x02, 0x25, 0xe9, 0x8e, 0xea, 0xda, 0x86, 0xe0, 0xb5, - 0xa4, 0x79, 0x78, 0x49, 0xcc, 0xb8, 0xb3, 0x24, 0x09, 0x54, 0xb1, 0x97, 0xa2, 0xfb, 0xa1, 0x15, 0xd9, 0xdd, 0x82, - 0x12, 0x09, 0xe7, 0x14, 0x24, 0x0c, 0xc0, 0x38, 0x0c, 0xc5, 0x30, 0x5e, 0x50, 0xd9, 0x59, 0x61, 0x20, 0xa6, 0x45, - 0x81, 0x6f, 0x4b, 0x7a, 0x17, 0x00, 0x88, 0x64, 0x54, 0x44, 0x2c, 0x97, 0x30, 0x61, 0x84, 0x7f, 0x20, 0x8b, 0xd0, - 0xcc, 0x27, 0xd8, 0x69, 0x61, 0xd8, 0x97, 0x81, 0xe2, 0x2e, 0x78, 0xc8, 0xd3, 0x39, 0xcd, 0x04, 0xcd, 0x82, 0xbf, - 0xe3, 0x8c, 0x8e, 0x12, 0x80, 0x62, 0xa7, 0x8d, 0xe3, 0x30, 0x3f, 0x8b, 0xc3, 0x74, 0x4c, 0xa3, 0xe0, 0x56, 0x14, - 0x58, 0x08, 0xe2, 0x8e, 0x58, 0x1a, 0x26, 0xec, 0x17, 0x1a, 0xb9, 0x5a, 0x1c, 0x3c, 0x77, 0xe8, 0xad, 0xa0, 0x69, - 0x94, 0x3b, 0x2f, 0xde, 0xbf, 0x7a, 0xa9, 0x17, 0xb2, 0x26, 0x21, 0xd0, 0x22, 0x9f, 0x4d, 0x69, 0xe6, 0x21, 0xac, - 0x25, 0xc4, 0x73, 0x26, 0xb9, 0xe3, 0xab, 0x70, 0xaa, 0x4a, 0x58, 0xfe, 0x61, 0x1a, 0x85, 0x82, 0xbe, 0xa5, 0x69, - 0xc4, 0xd2, 0x31, 0xd9, 0x69, 0xab, 0xf2, 0x38, 0xd4, 0x2f, 0xa2, 0xb2, 0xe8, 0x72, 0xf7, 0x79, 0x22, 0x27, 0x5e, - 0x3e, 0xce, 0x3c, 0x54, 0xe4, 0x22, 0x14, 0x6c, 0xe8, 0x84, 0x51, 0xf4, 0x55, 0xca, 0x04, 0x93, 0x00, 0x66, 0xb0, - 0x3e, 0x40, 0xa3, 0x54, 0xc9, 0x0a, 0x03, 0xb8, 0x87, 0xb0, 0xe7, 0x69, 0x09, 0x10, 0x23, 0xbd, 0x60, 0x7b, 0x7b, - 0x15, 0xbf, 0xef, 0xd3, 0x40, 0xbd, 0x24, 0x83, 0x0b, 0xe4, 0x4f, 0x67, 0x39, 0xac, 0xb4, 0x19, 0x02, 0xc4, 0x0b, - 0xbf, 0xce, 0x69, 0x36, 0xa7, 0x51, 0x49, 0x1d, 0xb9, 0x87, 0x16, 0x2b, 0x63, 0xe8, 0x7d, 0x21, 0xc8, 0xe0, 0xa2, - 0x6b, 0x33, 0x6e, 0xaa, 0x09, 0x3d, 0xe3, 0x53, 0x9a, 0x09, 0x46, 0xf3, 0x92, 0x97, 0x78, 0x20, 0x46, 0x4b, 0x7e, - 0x92, 0x13, 0x33, 0xbf, 0xa9, 0xc7, 0x30, 0x45, 0x35, 0x8e, 0x61, 0x24, 0xed, 0xf3, 0xb9, 0x14, 0x19, 0x39, 0x66, - 0x08, 0x0b, 0x05, 0x69, 0x8e, 0x50, 0x81, 0xb0, 0x30, 0xe0, 0x2a, 0x5e, 0xa4, 0x47, 0xbb, 0x03, 0x59, 0x4d, 0x7e, - 0x90, 0xb2, 0x1a, 0x38, 0x5a, 0x28, 0xe8, 0xde, 0x9e, 0x47, 0xfd, 0x92, 0x2a, 0xc8, 0x4e, 0x5b, 0xaf, 0x91, 0x85, - 0xac, 0x2d, 0x60, 0xc3, 0xc0, 0x02, 0x53, 0x84, 0x77, 0xa8, 0x9f, 0xf2, 0xd3, 0xe1, 0x90, 0xe6, 0x39, 0xcf, 0xf6, - 0xf6, 0x76, 0x64, 0xfd, 0x52, 0x9d, 0x80, 0x35, 0x7c, 0x73, 0x93, 0x56, 0x10, 0xa0, 0x4a, 0xc4, 0x6a, 0xc1, 0x20, - 0x40, 0x50, 0x49, 0x8d, 0xc3, 0xed, 0x1b, 0xcd, 0x23, 0x70, 0x2f, 0x2f, 0xdd, 0x86, 0xc0, 0x1a, 0x0d, 0x63, 0x6a, - 0x86, 0xbe, 0x7b, 0x46, 0x95, 0x6e, 0x25, 0x35, 0x8f, 0x35, 0xcc, 0xa8, 0x0d, 0xe4, 0x47, 0x74, 0xc4, 0x52, 0x6b, - 0xda, 0x35, 0x90, 0xb0, 0xc0, 0x39, 0x2a, 0xac, 0x05, 0xdd, 0xd8, 0xb5, 0x54, 0x6a, 0xd4, 0xca, 0x2d, 0xc6, 0x52, - 0x91, 0xb0, 0x96, 0x71, 0x40, 0x2f, 0x0a, 0x2c, 0x51, 0x6f, 0x66, 0x93, 0x49, 0x40, 0x07, 0xe2, 0xa2, 0xab, 0xdf, - 0x93, 0x5c, 0x61, 0x2e, 0xa3, 0x3f, 0xcf, 0x68, 0x2e, 0x14, 0x1d, 0x7b, 0x02, 0x67, 0x98, 0xa1, 0x02, 0xf6, 0xdb, - 0x88, 0x8d, 0x67, 0x19, 0xe8, 0x3b, 0xb0, 0x17, 0x69, 0x3a, 0x9b, 0x50, 0xf3, 0xb4, 0x09, 0xb6, 0x37, 0x53, 0x90, - 0x88, 0x39, 0xd0, 0xf4, 0xfd, 0xe4, 0x04, 0xb0, 0x0a, 0xb4, 0x5c, 0xfe, 0x60, 0x3a, 0xa9, 0x96, 0xb2, 0xd4, 0xd1, - 0x56, 0xd7, 0x44, 0x20, 0x2d, 0x91, 0x77, 0xda, 0x0a, 0x7c, 0x21, 0x2e, 0xc8, 0x4e, 0xab, 0xa4, 0x61, 0x8d, 0x55, - 0x05, 0x8e, 0x42, 0xe2, 0x1b, 0xd5, 0x15, 0x92, 0x02, 0xbe, 0x46, 0x2e, 0x7e, 0xbc, 0x46, 0xa9, 0x31, 0x19, 0x80, - 0xaa, 0xe1, 0xc7, 0x17, 0xdb, 0xc8, 0xc9, 0xf0, 0x03, 0x4f, 0xac, 0xbf, 0xab, 0xd8, 0xc6, 0xbc, 0xce, 0x36, 0x56, - 0xa6, 0xe1, 0x4e, 0xcb, 0x26, 0x6e, 0x49, 0x65, 0x7a, 0xa3, 0x57, 0xaf, 0x30, 0x93, 0xc0, 0x54, 0x53, 0xb2, 0xba, - 0x78, 0x1d, 0x4e, 0x68, 0xee, 0x51, 0x84, 0xb7, 0x55, 0x50, 0xe4, 0x09, 0x55, 0x2e, 0x2c, 0xc9, 0x99, 0x83, 0xe4, - 0x64, 0x48, 0x29, 0x66, 0xf5, 0x0d, 0x97, 0x63, 0x3a, 0xc8, 0x2f, 0x2a, 0x7d, 0xce, 0x9a, 0xbc, 0x14, 0xc9, 0x9a, - 0xbe, 0x0d, 0xfe, 0x54, 0x99, 0x42, 0x9a, 0xd4, 0x1b, 0x72, 0x84, 0x77, 0x5a, 0xab, 0x2b, 0x69, 0x6a, 0x55, 0x73, - 0x1c, 0x5c, 0xc0, 0x3a, 0x48, 0x89, 0xe1, 0xb3, 0x5c, 0xfe, 0x5f, 0xdb, 0x69, 0x80, 0xb6, 0x73, 0x20, 0x0c, 0x7f, - 0x94, 0x84, 0xc2, 0x6b, 0xef, 0xb7, 0x40, 0x19, 0x9d, 0x53, 0x10, 0x28, 0x08, 0xad, 0x4f, 0x85, 0xfa, 0xb3, 0x34, - 0x8f, 0xd9, 0x48, 0x78, 0xb1, 0x90, 0x2c, 0x85, 0x26, 0x39, 0x75, 0x44, 0x4d, 0x25, 0x96, 0xec, 0x26, 0x06, 0x62, - 0x2b, 0xf5, 0x2f, 0x6a, 0x20, 0x95, 0x6c, 0x0b, 0xb8, 0x43, 0xa5, 0x4e, 0x57, 0x5c, 0xc6, 0xd4, 0x66, 0xa0, 0x32, - 0xb6, 0xfb, 0xaa, 0xc7, 0x40, 0x33, 0x03, 0x66, 0x69, 0xad, 0x2c, 0xb0, 0x39, 0x84, 0x2e, 0x14, 0xbe, 0xe0, 0x2f, - 0xf9, 0x0d, 0xcd, 0xce, 0x42, 0x00, 0x3e, 0x50, 0xcd, 0x0b, 0x25, 0x08, 0x24, 0xbf, 0x17, 0x5d, 0x43, 0x2f, 0x97, - 0x72, 0xe2, 0x6f, 0x33, 0x3e, 0x61, 0x39, 0x05, 0x65, 0x4d, 0xe1, 0x3f, 0x85, 0x7d, 0x26, 0x37, 0x24, 0x08, 0x1b, - 0x5a, 0xd2, 0xd7, 0xe9, 0xcb, 0x3a, 0x7d, 0x5d, 0xee, 0x3e, 0x1f, 0x1b, 0x06, 0x58, 0xdf, 0xc6, 0x08, 0x7b, 0xda, - 0xa4, 0xb0, 0xe4, 0x9c, 0x1f, 0x23, 0x2d, 0xe1, 0x97, 0x4b, 0x61, 0x59, 0x6e, 0x35, 0x75, 0x91, 0xaa, 0x6d, 0x83, - 0x8a, 0x30, 0x8a, 0x40, 0xb1, 0xcb, 0x78, 0x92, 0x58, 0xa2, 0x0a, 0xb3, 0x6e, 0x29, 0x9c, 0x2e, 0x77, 0x9f, 0x9f, - 0xdf, 0x27, 0x9f, 0xe0, 0xbd, 0x2d, 0xa2, 0x0c, 0xa0, 0x69, 0x44, 0x33, 0xb0, 0x24, 0xad, 0xd5, 0xd2, 0x52, 0xf6, - 0x8c, 0xa7, 0x29, 0x1d, 0x0a, 0x1a, 0x81, 0xa1, 0xc2, 0x88, 0xf0, 0x63, 0x9e, 0x8b, 0xb2, 0xb0, 0x82, 0x9e, 0x59, - 0xd0, 0x33, 0x7f, 0x18, 0x26, 0x89, 0xa7, 0x8c, 0x92, 0x09, 0x9f, 0xd3, 0x0d, 0x50, 0x77, 0x6b, 0x20, 0x97, 0xdd, - 0x50, 0xab, 0x1b, 0xea, 0xe7, 0xd3, 0x84, 0x0d, 0x69, 0x29, 0xba, 0xce, 0x7d, 0x96, 0x46, 0xf4, 0x16, 0xf8, 0x08, - 0xea, 0xf5, 0x7a, 0x2d, 0xdc, 0x46, 0x85, 0x42, 0xf8, 0x62, 0x0d, 0xb1, 0xf7, 0x08, 0x4d, 0x20, 0x32, 0xd2, 0x5b, - 0x6c, 0xe2, 0x07, 0x14, 0x59, 0x92, 0x92, 0x19, 0xe3, 0x4a, 0x71, 0x67, 0x84, 0x23, 0x9a, 0x50, 0x41, 0x0d, 0x37, - 0x07, 0x15, 0x5a, 0x6d, 0xdd, 0x77, 0x25, 0xfe, 0x4a, 0x72, 0x32, 0xbb, 0xcc, 0xac, 0x79, 0x5e, 0x1a, 0xeb, 0xd5, - 0xf2, 0x54, 0xd8, 0xee, 0x0b, 0xb5, 0x3c, 0xa1, 0x10, 0xe1, 0x30, 0x56, 0x56, 0xba, 0xb7, 0x36, 0xa5, 0xaa, 0x0f, - 0xcd, 0xd9, 0xcb, 0x4d, 0xf4, 0xde, 0x80, 0xb9, 0x09, 0x05, 0xe7, 0x9a, 0x29, 0x50, 0x30, 0xfc, 0xd4, 0xb2, 0x9d, - 0x85, 0x49, 0x72, 0x1d, 0x0e, 0x3f, 0xd6, 0xa9, 0xbf, 0x22, 0x03, 0xb2, 0xca, 0x8d, 0xad, 0x57, 0x16, 0xcb, 0xb2, - 0xe7, 0x6d, 0xb8, 0x74, 0x6d, 0xa3, 0x78, 0x3b, 0xad, 0x8a, 0xec, 0xeb, 0x0b, 0xbd, 0x95, 0xda, 0x25, 0x44, 0x4c, - 0xcf, 0xcc, 0x03, 0x2e, 0xf0, 0x49, 0x8a, 0x33, 0xfc, 0x40, 0xd3, 0x1d, 0x98, 0x1b, 0xc5, 0x0a, 0x20, 0x02, 0x2d, - 0x8a, 0x88, 0xe5, 0xdb, 0x31, 0xf0, 0x87, 0x40, 0xf9, 0xcc, 0x1a, 0xe1, 0xa1, 0x80, 0x96, 0x3c, 0x4e, 0x6b, 0xcd, - 0x25, 0x64, 0x5a, 0x9f, 0x30, 0x8c, 0xe6, 0x6f, 0xa0, 0xbb, 0x48, 0x7a, 0x7f, 0xa3, 0x5e, 0x81, 0x56, 0x06, 0x50, - 0xe4, 0x5d, 0x5b, 0x9d, 0xa8, 0x51, 0x80, 0xe6, 0xa9, 0x4c, 0x8a, 0xdc, 0xac, 0x66, 0x3f, 0x6a, 0x8d, 0x5d, 0x99, - 0xe0, 0x9a, 0xe5, 0x72, 0xe2, 0x79, 0x5e, 0x0e, 0x26, 0x9c, 0x51, 0xed, 0xab, 0x49, 0xe4, 0x6b, 0x93, 0xc8, 0x7d, - 0xcb, 0xce, 0x42, 0x15, 0x2d, 0x5b, 0xcd, 0x83, 0xbf, 0x23, 0xbb, 0x12, 0xa8, 0xab, 0x3e, 0xf0, 0x67, 0x54, 0xb2, - 0xdb, 0x84, 0x08, 0xcc, 0xb5, 0x8d, 0xa3, 0x29, 0x0d, 0x18, 0x46, 0xd5, 0x24, 0x43, 0x6a, 0x6b, 0xd4, 0xec, 0xdd, - 0x0c, 0x73, 0xb4, 0xa2, 0xdb, 0x17, 0x85, 0xc6, 0x11, 0x45, 0x7a, 0x6d, 0x6a, 0x4a, 0xb1, 0x85, 0x15, 0x9c, 0x11, - 0xad, 0x08, 0x2b, 0xbd, 0x67, 0x15, 0x37, 0x65, 0xbf, 0x3b, 0x84, 0x64, 0x15, 0x6a, 0x6a, 0x1a, 0xa5, 0x51, 0xad, - 0x32, 0x84, 0x63, 0xa3, 0x93, 0xf2, 0x6a, 0xde, 0x84, 0xb8, 0xc6, 0x21, 0xe1, 0xf6, 0x17, 0x35, 0xab, 0x30, 0xb0, - 0xaa, 0x15, 0x01, 0xb0, 0x54, 0xbe, 0x09, 0xdd, 0x9b, 0x68, 0xa6, 0xd6, 0x8f, 0x85, 0x70, 0x6e, 0x23, 0xdc, 0xc2, - 0x6c, 0xa6, 0x38, 0x57, 0x76, 0x41, 0xe2, 0x7a, 0x5b, 0x8f, 0x62, 0xae, 0xd6, 0x61, 0x0d, 0x89, 0xab, 0xaa, 0xa7, - 0x24, 0x41, 0xb0, 0x61, 0x73, 0x50, 0xee, 0x6c, 0xf9, 0xe0, 0x01, 0xec, 0x6c, 0xb9, 0x5c, 0x23, 0xba, 0x8d, 0x1a, - 0x28, 0xf2, 0x2b, 0xbb, 0x70, 0xb9, 0xbc, 0x15, 0xc8, 0xd3, 0xba, 0x2f, 0xa6, 0xa8, 0x6f, 0x38, 0xee, 0xe9, 0x4b, - 0xa8, 0x25, 0x55, 0xd1, 0xaa, 0xa4, 0x34, 0x1a, 0xea, 0x34, 0x5b, 0x5f, 0x27, 0x61, 0xb1, 0xed, 0xb3, 0x35, 0xee, - 0x25, 0x0b, 0xb5, 0x98, 0xae, 0xa6, 0x7c, 0xa6, 0xbb, 0x66, 0x08, 0xa1, 0x20, 0x97, 0x76, 0xcc, 0xce, 0x26, 0xd3, - 0x72, 0x6f, 0x2f, 0xb7, 0x3a, 0xba, 0x2c, 0xd9, 0xc4, 0x4f, 0x1e, 0x88, 0xe4, 0xfc, 0x2e, 0x95, 0xba, 0xcb, 0x4f, - 0x46, 0x08, 0xad, 0x19, 0xa6, 0xad, 0x2e, 0x18, 0xe4, 0xe1, 0x4d, 0xc8, 0x84, 0x53, 0xf6, 0xa2, 0x0c, 0x72, 0x8f, - 0xa2, 0x85, 0x56, 0x35, 0xfc, 0x8c, 0x82, 0xf2, 0x08, 0x3c, 0xc1, 0xa8, 0xd0, 0x8a, 0xee, 0x87, 0x31, 0x05, 0x5f, - 0xb0, 0xd1, 0x22, 0x4a, 0xcb, 0x70, 0x47, 0x4b, 0x11, 0xdd, 0xf1, 0x66, 0xd8, 0x8b, 0xd5, 0xe6, 0x35, 0x4b, 0x60, - 0x4a, 0xb3, 0x11, 0xcf, 0x26, 0xe6, 0x5d, 0xb1, 0xf2, 0xac, 0x39, 0x23, 0x1b, 0x79, 0x1b, 0xfb, 0xd6, 0xfa, 0x7f, - 0x77, 0xc5, 0xec, 0xae, 0x0c, 0xf6, 0x9a, 0x28, 0x2d, 0xa5, 0xaf, 0x72, 0x09, 0x1a, 0xca, 0xcc, 0x6d, 0x03, 0x5f, - 0xfb, 0x53, 0xbb, 0xca, 0x67, 0xb2, 0xd3, 0xee, 0x96, 0x56, 0x9f, 0xa1, 0x86, 0xae, 0xf2, 0x6d, 0x68, 0x91, 0xca, - 0x67, 0x49, 0xa4, 0x81, 0x65, 0x08, 0x53, 0x4d, 0x47, 0x37, 0x2c, 0x49, 0xaa, 0xd2, 0x5f, 0xc3, 0xd7, 0x73, 0xcd, - 0xd7, 0x33, 0xc3, 0xd7, 0x81, 0x53, 0x00, 0x5f, 0x57, 0xdd, 0x55, 0xcd, 0xb3, 0xb5, 0xdd, 0x99, 0x29, 0x8e, 0x9e, - 0x4b, 0x4b, 0x1a, 0xc6, 0x9b, 0x19, 0x08, 0x50, 0xa9, 0x79, 0x7d, 0xf4, 0xb4, 0x1f, 0x06, 0x4c, 0x40, 0xe5, 0xc5, - 0xa4, 0xb6, 0x93, 0xe2, 0xa3, 0x87, 0x70, 0x5e, 0xd0, 0x92, 0xb2, 0x4f, 0x9f, 0x83, 0x9f, 0xce, 0x9a, 0x0e, 0x08, - 0x31, 0x59, 0xfc, 0xab, 0x94, 0x28, 0x33, 0x3b, 0xa6, 0x67, 0x97, 0x9b, 0xd9, 0x01, 0xa7, 0xaf, 0x66, 0x17, 0xdd, - 0xcf, 0xeb, 0xe5, 0xf4, 0x58, 0x39, 0xbd, 0x6a, 0xbd, 0x97, 0x4b, 0x6f, 0xa5, 0x04, 0x5c, 0xf8, 0xda, 0x44, 0xc9, - 0xca, 0xde, 0x81, 0x07, 0xd8, 0x98, 0x81, 0x82, 0x42, 0x4d, 0xba, 0x14, 0x71, 0x2f, 0x3f, 0xe5, 0xe2, 0x91, 0x9e, - 0x7a, 0xd5, 0xfe, 0x8c, 0x4f, 0xa6, 0xa0, 0x8d, 0xad, 0x90, 0xf4, 0x98, 0xea, 0x01, 0xab, 0xf7, 0xc5, 0x86, 0xb2, - 0x5a, 0x1b, 0xb9, 0x1f, 0x6b, 0xd4, 0x54, 0x5a, 0xcc, 0x3b, 0xad, 0x62, 0x56, 0x16, 0x95, 0x8c, 0x63, 0x93, 0x5b, - 0xe5, 0x6c, 0xd5, 0x29, 0x63, 0x5e, 0xbc, 0xf1, 0x98, 0xe2, 0xc3, 0x0c, 0x78, 0x9d, 0xc5, 0x7e, 0x0c, 0xb9, 0xdb, - 0xeb, 0x5f, 0x54, 0xc8, 0x59, 0x14, 0x2b, 0xe8, 0x5b, 0x14, 0xc5, 0x73, 0x6d, 0x65, 0xe3, 0xe7, 0xdb, 0xcd, 0xe1, - 0xea, 0x9d, 0xb6, 0x16, 0x07, 0x17, 0xf8, 0xf9, 0xba, 0xee, 0x48, 0x16, 0x13, 0x1e, 0xd1, 0xc0, 0xe5, 0x53, 0x9a, - 0xba, 0x05, 0x78, 0x56, 0xf5, 0xe2, 0x47, 0xc2, 0x5b, 0xbc, 0xab, 0xbb, 0x58, 0x83, 0xe7, 0x05, 0x38, 0xc0, 0xbe, - 0x5b, 0x77, 0xbe, 0x7e, 0x4b, 0xb3, 0x5c, 0x6a, 0xa2, 0xa5, 0x52, 0xfb, 0x5d, 0x25, 0x97, 0xbe, 0x0b, 0xb6, 0xd6, - 0xaf, 0x6c, 0x10, 0xb7, 0xed, 0x3f, 0xf2, 0x0f, 0x5c, 0x24, 0x5d, 0xc3, 0x5f, 0xeb, 0x1d, 0xff, 0x93, 0x71, 0x0d, - 0x9f, 0x93, 0x9f, 0xea, 0x9e, 0xe1, 0x99, 0x20, 0xe7, 0xfd, 0x73, 0x63, 0x32, 0xf3, 0x84, 0x0d, 0xef, 0x3c, 0x37, - 0x61, 0xa2, 0x09, 0xe1, 0x37, 0x17, 0x2f, 0xd4, 0x0b, 0xf0, 0x2a, 0x4a, 0x97, 0x76, 0x61, 0x8c, 0x3d, 0x4c, 0x05, - 0x71, 0x77, 0x13, 0x26, 0x76, 0x5d, 0x3c, 0x21, 0x57, 0xf0, 0x63, 0x77, 0xe1, 0xbd, 0x0a, 0x45, 0xec, 0x67, 0x61, - 0x1a, 0xf1, 0x89, 0x87, 0x1a, 0xae, 0x8b, 0xfc, 0x5c, 0x1a, 0x1c, 0x4f, 0x50, 0xb1, 0x7b, 0x85, 0x4f, 0x05, 0x71, - 0xfb, 0x6e, 0x63, 0x82, 0xdf, 0x09, 0x72, 0x75, 0xb2, 0xbb, 0x38, 0x15, 0x45, 0xef, 0x0a, 0xdf, 0x96, 0x5e, 0x7b, - 0xfc, 0x81, 0x78, 0x88, 0xf4, 0x6e, 0x35, 0x34, 0x67, 0x7c, 0xa2, 0xbc, 0xf7, 0x2e, 0xc2, 0xef, 0x65, 0x6c, 0xa5, - 0x62, 0x37, 0x3a, 0xbc, 0xb2, 0x43, 0x5c, 0x2e, 0x7d, 0x04, 0xee, 0xde, 0x9e, 0x55, 0x56, 0xea, 0x0a, 0xf8, 0xb9, - 0x20, 0x35, 0x8b, 0x1c, 0xbf, 0x90, 0x51, 0x9a, 0xe7, 0xc2, 0x4b, 0x91, 0xe9, 0xc6, 0x33, 0xbe, 0x68, 0xbd, 0x37, - 0xd3, 0x81, 0x72, 0x31, 0xf8, 0x4c, 0xd0, 0x2c, 0x14, 0x3c, 0xbb, 0x40, 0xb6, 0xfe, 0x81, 0xff, 0x46, 0xae, 0x06, - 0xce, 0x7f, 0xfa, 0xec, 0xc7, 0xd1, 0x8f, 0xd9, 0xc5, 0x15, 0x7e, 0x4b, 0xf6, 0x4f, 0xbc, 0x7e, 0xe0, 0xed, 0x34, - 0x9b, 0xcb, 0x1f, 0xf7, 0x07, 0xff, 0x08, 0x9b, 0xbf, 0x9c, 0x36, 0x7f, 0xb8, 0x40, 0x4b, 0xef, 0xc7, 0xfd, 0xfe, - 0x40, 0x3f, 0x0d, 0xfe, 0xd1, 0xfb, 0x31, 0xbf, 0xf8, 0xb3, 0x2a, 0xdc, 0x45, 0x68, 0x7f, 0x8c, 0xa7, 0x82, 0xec, - 0x37, 0x9b, 0xbd, 0xfd, 0x31, 0x1e, 0x0b, 0xb2, 0x0f, 0xff, 0x5f, 0x93, 0x77, 0x74, 0xfc, 0xfc, 0x76, 0xea, 0x5d, - 0xf5, 0x96, 0xbb, 0x8b, 0xbf, 0x15, 0xd0, 0xeb, 0xe0, 0x1f, 0x3f, 0xfe, 0x98, 0xbb, 0x5f, 0xf4, 0xc8, 0xfe, 0x45, - 0x03, 0x79, 0x50, 0xfa, 0x67, 0x22, 0xff, 0xf5, 0xfa, 0xc1, 0xe0, 0x1f, 0x1a, 0x0a, 0xf7, 0x8b, 0x1f, 0xaf, 0x4e, - 0x7a, 0xe4, 0x62, 0xe9, 0xb9, 0xcb, 0x2f, 0xd0, 0x12, 0xa1, 0xe5, 0x2e, 0xba, 0xc2, 0xee, 0xd8, 0x45, 0x78, 0x2e, - 0xc8, 0xfe, 0x17, 0xfb, 0x63, 0x3c, 0x12, 0x64, 0xdf, 0xdd, 0x1f, 0xe3, 0x73, 0x41, 0xf6, 0xff, 0xe1, 0xf5, 0x03, - 0xe5, 0x64, 0x5b, 0x4a, 0xff, 0xc6, 0x12, 0x02, 0x1c, 0x61, 0x46, 0xc3, 0xa5, 0x60, 0x22, 0xa1, 0x68, 0x77, 0x9f, - 0xe1, 0x33, 0x89, 0x26, 0x4f, 0x80, 0x17, 0x06, 0x8c, 0x3b, 0x6f, 0x71, 0x09, 0x8b, 0x0d, 0x34, 0xb3, 0x1b, 0x40, - 0x64, 0x07, 0x1c, 0x01, 0x79, 0x20, 0xf0, 0x3c, 0x4c, 0x66, 0x34, 0x0f, 0x68, 0x81, 0xf0, 0x90, 0x9c, 0x09, 0xaf, - 0x8d, 0xf0, 0x53, 0x01, 0x3f, 0x3a, 0x08, 0x9f, 0xe9, 0x20, 0x26, 0xec, 0x64, 0x45, 0x54, 0x29, 0x57, 0x2a, 0x8b, - 0x8b, 0xf0, 0x74, 0xc3, 0x4b, 0x11, 0x83, 0x7b, 0x01, 0xe1, 0xdd, 0x5a, 0xc8, 0x13, 0xdf, 0x10, 0x43, 0x12, 0xef, - 0x33, 0x4a, 0xbf, 0x0b, 0x93, 0x8f, 0x34, 0xf3, 0x6e, 0x71, 0xbb, 0xf3, 0x04, 0x4b, 0x2f, 0xf4, 0x4e, 0x1b, 0x75, - 0xcb, 0x78, 0xd5, 0x47, 0xa1, 0xe2, 0x04, 0x20, 0x65, 0xeb, 0xce, 0x18, 0x58, 0xf1, 0x9d, 0x74, 0xcd, 0x63, 0x95, - 0x85, 0x37, 0x2e, 0xaa, 0xc7, 0x46, 0x59, 0x3a, 0x0f, 0x13, 0x16, 0x39, 0x82, 0x4e, 0xa6, 0x49, 0x28, 0xa8, 0xa3, - 0xe7, 0xeb, 0x84, 0xd0, 0x91, 0x5b, 0xea, 0x0c, 0x33, 0xcb, 0xe2, 0x9c, 0x99, 0xa0, 0x13, 0xec, 0x15, 0x0f, 0x22, - 0x54, 0x5a, 0xef, 0x78, 0x55, 0x05, 0xc0, 0x56, 0x63, 0x7c, 0xcd, 0x36, 0x78, 0xc2, 0x2e, 0xa4, 0x7c, 0xce, 0x71, - 0x46, 0x40, 0x8a, 0x76, 0xfa, 0xee, 0x49, 0x3e, 0x1f, 0xf7, 0x5c, 0x88, 0xcf, 0x70, 0xf2, 0x56, 0x3a, 0x86, 0xa0, - 0x42, 0x4c, 0x5a, 0xdd, 0xf8, 0x84, 0x76, 0xe3, 0x46, 0xc3, 0x28, 0xd1, 0x09, 0x49, 0x07, 0xb1, 0x6a, 0x1e, 0xe2, - 0x08, 0xcf, 0x48, 0xb3, 0x8d, 0xc7, 0xa4, 0x25, 0x9b, 0x74, 0xc7, 0x27, 0x89, 0x1e, 0x66, 0x6f, 0xcf, 0xe3, 0x7e, - 0x12, 0xe6, 0xe2, 0x2b, 0xb0, 0xf6, 0xc9, 0x18, 0x47, 0x84, 0xfb, 0xf4, 0x96, 0x0e, 0xbd, 0x04, 0xe1, 0x48, 0x73, - 0x1a, 0xd4, 0x45, 0x63, 0x62, 0x55, 0x03, 0x2b, 0x82, 0xbc, 0xed, 0x47, 0x83, 0xf6, 0x05, 0x21, 0xc4, 0xdd, 0x69, - 0x36, 0xdd, 0x3e, 0x27, 0x53, 0x11, 0x40, 0x89, 0xa5, 0x2b, 0x93, 0x31, 0x14, 0x75, 0xac, 0x22, 0xef, 0x5c, 0xf8, - 0x82, 0xe6, 0xc2, 0x83, 0x62, 0xb0, 0xff, 0x73, 0x43, 0xd8, 0xee, 0xc9, 0xbe, 0xdb, 0x80, 0x52, 0x49, 0x9c, 0x08, - 0x73, 0x72, 0x8d, 0x82, 0x68, 0x70, 0x70, 0x61, 0x0b, 0x00, 0x59, 0x08, 0x83, 0x5f, 0xf7, 0xa3, 0x41, 0x4b, 0x0e, - 0xde, 0x73, 0xfb, 0x1e, 0x27, 0xb9, 0xd2, 0xd0, 0xfa, 0x79, 0xf0, 0x56, 0x4e, 0x15, 0x05, 0x1a, 0x38, 0xb3, 0x02, - 0xa4, 0xd9, 0x09, 0xbc, 0x99, 0x3d, 0x89, 0x26, 0x0c, 0xa6, 0xb1, 0x80, 0x43, 0x02, 0xf5, 0x31, 0x27, 0x30, 0x62, - 0xd5, 0xec, 0x3a, 0xd0, 0xcf, 0x5f, 0xb8, 0x5f, 0xf4, 0x47, 0x22, 0x98, 0x0b, 0x35, 0xfc, 0x48, 0x2c, 0x97, 0xf0, - 0xff, 0x5c, 0xf4, 0x39, 0xb9, 0x96, 0x45, 0x53, 0x5d, 0x34, 0x86, 0xa2, 0xb7, 0x01, 0x80, 0x8a, 0xf3, 0x52, 0xcb, - 0x52, 0x6b, 0x32, 0x27, 0x12, 0xf6, 0xbd, 0xbd, 0x74, 0x10, 0x37, 0xda, 0x17, 0xe0, 0xe2, 0xcf, 0x44, 0xfe, 0x1d, - 0x13, 0xb1, 0xe7, 0xee, 0xf7, 0x5c, 0xd4, 0x77, 0x1d, 0x58, 0xda, 0x6e, 0xd6, 0x20, 0x0a, 0xc3, 0x49, 0xe3, 0x9d, - 0x08, 0x66, 0x3d, 0xd2, 0xea, 0x7b, 0x4c, 0xb1, 0xf0, 0x10, 0xe1, 0x44, 0x33, 0xce, 0x16, 0x9e, 0xa1, 0x06, 0x15, - 0x0d, 0xf3, 0x3c, 0x43, 0x8d, 0x49, 0x63, 0x8e, 0x82, 0xa4, 0x31, 0x69, 0x78, 0x33, 0x42, 0x48, 0xb3, 0x53, 0x36, - 0x33, 0xe2, 0x2f, 0x46, 0xc1, 0xdc, 0x78, 0x3b, 0x07, 0x72, 0x3b, 0x64, 0x0d, 0x2f, 0x1d, 0xd0, 0x8b, 0xe5, 0xd2, - 0x3d, 0xe9, 0xf7, 0x5c, 0xd4, 0xf0, 0x0c, 0xa1, 0xed, 0x1b, 0x4a, 0x43, 0x08, 0xb3, 0x8b, 0x42, 0x47, 0x93, 0x5e, - 0xd7, 0x22, 0x47, 0x8b, 0x6a, 0xb3, 0x5b, 0x3c, 0x80, 0x16, 0xa5, 0x21, 0xa3, 0x14, 0xd6, 0x29, 0x4c, 0xd3, 0x10, - 0x73, 0x46, 0x5a, 0x98, 0x13, 0xe3, 0xbc, 0x8e, 0x89, 0xa8, 0x08, 0x3e, 0x21, 0x55, 0x75, 0x3c, 0x08, 0x71, 0x74, - 0x41, 0x5e, 0x29, 0x83, 0xa4, 0x6b, 0x5c, 0xe3, 0x34, 0x21, 0xaf, 0x57, 0x22, 0xb8, 0x21, 0x84, 0x57, 0x6e, 0xfc, - 0xe1, 0x2c, 0xcb, 0x68, 0x2a, 0x5e, 0xf3, 0x48, 0xeb, 0x69, 0x34, 0x01, 0x53, 0x09, 0x42, 0xb3, 0x18, 0x94, 0xb4, - 0x8e, 0xd9, 0x19, 0xb3, 0xb5, 0xd7, 0x63, 0x32, 0x53, 0xfa, 0x93, 0x0c, 0xd8, 0x76, 0xc7, 0xda, 0x30, 0xf6, 0x10, - 0x9e, 0xe9, 0x48, 0xae, 0xe7, 0xfb, 0xfe, 0xd8, 0x1f, 0xc2, 0x6b, 0x18, 0x20, 0x47, 0x85, 0xdc, 0x47, 0x5e, 0x4e, - 0x6e, 0xfc, 0x94, 0xde, 0xca, 0x51, 0x3d, 0x54, 0x49, 0x66, 0xb3, 0xbd, 0x4e, 0xe2, 0xae, 0x64, 0x37, 0xb9, 0x9f, - 0xf2, 0x88, 0x02, 0x7a, 0x20, 0x76, 0xaf, 0x8b, 0xe2, 0x30, 0xb7, 0x43, 0x54, 0x15, 0x7c, 0x03, 0xdb, 0x7b, 0x3d, - 0x06, 0x97, 0xaf, 0x54, 0xb6, 0xca, 0xca, 0xca, 0x0f, 0x8e, 0x10, 0x1b, 0x79, 0x63, 0x1f, 0x42, 0x7b, 0x92, 0x84, - 0x28, 0xd8, 0x72, 0x63, 0x9b, 0xa8, 0x26, 0x65, 0x9f, 0x73, 0x12, 0x0d, 0x78, 0xa3, 0x21, 0xdd, 0xd0, 0x33, 0x45, - 0x12, 0x63, 0x84, 0xe7, 0xe5, 0xde, 0x32, 0xf5, 0xbe, 0x24, 0xf5, 0x91, 0xbc, 0x79, 0xdd, 0x9d, 0xdb, 0x80, 0x34, - 0x09, 0xf0, 0x14, 0x0a, 0x6f, 0x82, 0xf0, 0x29, 0xd9, 0xf7, 0x06, 0x7e, 0xff, 0x2f, 0x17, 0xa8, 0xef, 0xf9, 0x7f, - 0x46, 0xfb, 0x8a, 0x71, 0xcc, 0x51, 0x37, 0x51, 0x43, 0x2c, 0x64, 0x08, 0xb3, 0x8d, 0xa5, 0x27, 0x31, 0xc8, 0x70, - 0x1a, 0x4e, 0x68, 0x70, 0x0a, 0x7b, 0xdc, 0xd0, 0xcd, 0x97, 0x18, 0xe8, 0x28, 0x38, 0xd5, 0x9c, 0xc4, 0x77, 0xfb, - 0xcf, 0x44, 0xf9, 0xd4, 0x77, 0xfb, 0x5f, 0x55, 0x4f, 0x7f, 0x71, 0xfb, 0x3f, 0x8b, 0xe0, 0x97, 0x42, 0x3b, 0xbb, - 0x6b, 0x43, 0x3c, 0x32, 0x43, 0x14, 0x6a, 0x61, 0x2c, 0xcc, 0xcd, 0xd0, 0xba, 0x9f, 0x63, 0x8c, 0x0a, 0x36, 0x2a, - 0x59, 0x51, 0xee, 0x8b, 0x70, 0x0c, 0x28, 0xb5, 0x56, 0x20, 0xb7, 0x23, 0xfb, 0xd5, 0x84, 0x81, 0x50, 0x0c, 0xb5, - 0x02, 0x2a, 0xc7, 0xbd, 0x16, 0x5a, 0xd4, 0xea, 0x4a, 0x8d, 0xa9, 0x1e, 0x49, 0x2f, 0xb9, 0xf4, 0x9c, 0xb4, 0xba, - 0xf3, 0x93, 0x71, 0x77, 0xde, 0x68, 0xa0, 0xdc, 0x10, 0xd6, 0x6c, 0x30, 0xbf, 0xc0, 0x1f, 0xc0, 0xa7, 0x67, 0x53, - 0x12, 0xae, 0x4d, 0xaf, 0xa3, 0xa7, 0xd7, 0x68, 0x64, 0x05, 0xea, 0x5a, 0x4d, 0xc7, 0xaa, 0x69, 0x51, 0x28, 0x9c, - 0xac, 0x12, 0xda, 0x31, 0x92, 0x25, 0x90, 0x0e, 0x45, 0x08, 0x39, 0x15, 0x68, 0x63, 0xaf, 0xd0, 0x27, 0x34, 0x97, - 0x3b, 0x16, 0x98, 0xa7, 0x92, 0x11, 0x1e, 0x60, 0x01, 0x9a, 0x96, 0x8e, 0xe0, 0x09, 0x9e, 0x35, 0xda, 0x92, 0xc8, - 0x9b, 0xed, 0x6e, 0xbd, 0xaf, 0xc7, 0x55, 0x5f, 0x78, 0xd6, 0x20, 0x93, 0x12, 0x4b, 0x45, 0xd6, 0x68, 0x14, 0xf5, - 0x68, 0xa7, 0xd9, 0xb7, 0xb5, 0xf8, 0xc3, 0xed, 0x6a, 0x5a, 0x86, 0x91, 0xaf, 0x95, 0x44, 0x65, 0x3e, 0x4b, 0x53, - 0x9a, 0x81, 0x0c, 0x25, 0x02, 0xb3, 0xa2, 0xa8, 0xe4, 0x3a, 0x08, 0x51, 0x4c, 0x49, 0x0a, 0x7c, 0x47, 0x9a, 0x5d, - 0x38, 0xc3, 0x1c, 0xc7, 0x92, 0x6b, 0x10, 0x42, 0xce, 0x4c, 0x42, 0x8b, 0x90, 0x1c, 0x28, 0x21, 0xcc, 0x92, 0x48, - 0x39, 0xa1, 0xfe, 0xe5, 0xee, 0x19, 0xbf, 0xd7, 0x24, 0x1b, 0xb0, 0x8b, 0x40, 0x56, 0x4b, 0x34, 0xdf, 0x0a, 0xc9, - 0x7b, 0x4f, 0xa0, 0x32, 0x38, 0xe2, 0x4b, 0xf6, 0xf7, 0x8c, 0x65, 0x54, 0x6a, 0xe0, 0xbb, 0xc6, 0xec, 0x4b, 0xea, - 0xea, 0x63, 0x62, 0x3b, 0x6f, 0x00, 0x91, 0x21, 0xf8, 0x76, 0x32, 0xb2, 0x56, 0xed, 0x72, 0xf7, 0xf4, 0xcd, 0x26, - 0x13, 0x78, 0xb9, 0xd4, 0xc6, 0xaf, 0xd4, 0x6c, 0x70, 0x58, 0x41, 0x9a, 0xe8, 0x1f, 0x81, 0x97, 0x48, 0x05, 0x29, - 0xf4, 0x52, 0xa0, 0xa2, 0xcb, 0xdd, 0xd3, 0xf7, 0x5e, 0x2a, 0x5d, 0x4b, 0x08, 0xdb, 0xd3, 0xf6, 0x38, 0xf1, 0x62, - 0x42, 0x91, 0x9a, 0x7b, 0xc9, 0xb8, 0xb8, 0x25, 0xbe, 0x83, 0x58, 0xbe, 0x04, 0xfb, 0x61, 0xc0, 0x2e, 0x48, 0xa2, - 0x31, 0x40, 0x12, 0x84, 0x93, 0x9a, 0x59, 0x46, 0x60, 0x01, 0xe4, 0x58, 0xe7, 0xb0, 0x12, 0xbe, 0x52, 0xfc, 0x10, - 0x4e, 0xe4, 0xa8, 0xa2, 0x50, 0xa2, 0xe3, 0xe5, 0x5a, 0x5e, 0x5a, 0x65, 0x8d, 0x7e, 0x0b, 0x96, 0x93, 0x79, 0x78, - 0xad, 0xbb, 0x2e, 0x0b, 0x9e, 0x99, 0x04, 0xb2, 0xcb, 0xdd, 0xd3, 0x57, 0x3a, 0x87, 0x6c, 0x1a, 0x1a, 0x6e, 0xbf, - 0x66, 0x61, 0x9e, 0xbe, 0xf2, 0xab, 0xb7, 0xb2, 0xf2, 0xe5, 0xee, 0xe9, 0x87, 0x4d, 0xd5, 0xa0, 0xbc, 0x98, 0x55, - 0x26, 0xbe, 0x84, 0x6f, 0x41, 0x93, 0x60, 0xa1, 0x45, 0x43, 0xc0, 0x0a, 0x2c, 0xc5, 0x51, 0x90, 0x17, 0xa5, 0x67, - 0xe4, 0x19, 0xce, 0x88, 0x8c, 0x02, 0xd5, 0x57, 0x4d, 0x2b, 0x79, 0x8c, 0xa7, 0xe7, 0x43, 0x3e, 0xa5, 0x5b, 0x42, - 0x43, 0xb7, 0xc8, 0x67, 0x13, 0x48, 0x9e, 0x91, 0xa0, 0x33, 0xbc, 0xd3, 0x42, 0xdd, 0xba, 0xf0, 0xca, 0x24, 0x91, - 0xf2, 0x9a, 0x64, 0xc1, 0x31, 0x69, 0xe1, 0x84, 0xb4, 0x70, 0x48, 0xf2, 0x41, 0x4b, 0x89, 0x87, 0x6e, 0x58, 0xf6, - 0xab, 0x84, 0x0c, 0xe4, 0x85, 0xe9, 0xdd, 0xaa, 0xc4, 0x6f, 0xd4, 0x0d, 0xa5, 0xeb, 0x51, 0x4a, 0xf4, 0x48, 0x92, - 0xc5, 0x0b, 0x8f, 0x63, 0x2e, 0x3b, 0x3e, 0x67, 0xd7, 0x09, 0xa4, 0x96, 0xc0, 0xac, 0xb0, 0x40, 0x41, 0x59, 0xb5, - 0xad, 0xab, 0x86, 0xbe, 0x5c, 0x27, 0x8e, 0x43, 0x1f, 0x18, 0x37, 0x0e, 0x75, 0x26, 0x4e, 0xbe, 0xde, 0xe4, 0xd1, - 0xde, 0x9e, 0xa7, 0x1a, 0xfd, 0x22, 0x3c, 0x6e, 0xde, 0x57, 0x81, 0xbb, 0x6f, 0x15, 0xaf, 0x88, 0x90, 0x84, 0xbf, - 0xd1, 0x48, 0x2e, 0x0a, 0x88, 0x42, 0x7b, 0x61, 0x1d, 0x83, 0x06, 0x78, 0xa9, 0xe9, 0xd5, 0xa7, 0xdf, 0x68, 0x94, - 0x41, 0xda, 0x3a, 0xb6, 0x6e, 0x71, 0x56, 0xcc, 0xbd, 0x32, 0xf9, 0xa7, 0xb5, 0x96, 0x31, 0x65, 0x40, 0x40, 0xcc, - 0xa6, 0x59, 0x66, 0x26, 0x63, 0x6d, 0x09, 0x06, 0xf5, 0xbe, 0xd2, 0x69, 0x0b, 0x58, 0xe6, 0x57, 0xe9, 0x4a, 0x86, - 0x9d, 0x75, 0x50, 0x60, 0x2a, 0x41, 0x50, 0x0a, 0x2a, 0x35, 0x0a, 0x4d, 0xde, 0x2f, 0xd6, 0xb3, 0x2e, 0x71, 0x8e, - 0xb4, 0x8f, 0x4b, 0x42, 0x21, 0x91, 0xd5, 0x29, 0x91, 0xf2, 0x82, 0x4c, 0xb7, 0x93, 0xfc, 0xa9, 0x45, 0xf2, 0x4f, - 0x09, 0xb5, 0xc8, 0x5f, 0x79, 0x38, 0x7c, 0xae, 0x5d, 0x0b, 0xb9, 0x79, 0x75, 0x36, 0x25, 0xe0, 0x43, 0xab, 0x63, - 0xb4, 0x16, 0x55, 0xdc, 0xc2, 0x50, 0xec, 0x1d, 0x22, 0xbd, 0x90, 0xd8, 0x84, 0x80, 0xbd, 0x2a, 0xa6, 0x06, 0x43, - 0x6f, 0x72, 0xe9, 0xd9, 0x1c, 0xf0, 0xf4, 0xc3, 0xfd, 0xe1, 0xd0, 0xb3, 0xe9, 0xfa, 0xce, 0xb5, 0xb2, 0x3f, 0x61, - 0xd6, 0xd6, 0xc6, 0xad, 0xe7, 0x82, 0xc2, 0xf8, 0x65, 0x18, 0xbb, 0xce, 0x7c, 0x56, 0x36, 0xa1, 0x91, 0x7f, 0x00, - 0x6d, 0xbb, 0x2d, 0x6b, 0x50, 0xab, 0x5b, 0xe0, 0x47, 0x2a, 0x07, 0x35, 0xcc, 0xb6, 0xb0, 0x8f, 0x53, 0x59, 0x81, - 0xa6, 0xd1, 0xe6, 0xd7, 0x4f, 0x0b, 0x4d, 0x26, 0x0a, 0x34, 0xb4, 0x00, 0xfe, 0xa7, 0x48, 0x1e, 0xe8, 0x46, 0xca, - 0x05, 0x40, 0xd0, 0x54, 0xe2, 0xa9, 0x42, 0x98, 0xeb, 0x56, 0xce, 0xf7, 0x17, 0x3b, 0x84, 0x4c, 0x2b, 0xe7, 0xe3, - 0xbb, 0x2a, 0xf7, 0x0a, 0xc8, 0x02, 0x05, 0x60, 0x3c, 0x96, 0x05, 0x2a, 0x7a, 0x79, 0x66, 0xaa, 0x4b, 0x03, 0xd2, - 0xaf, 0xf4, 0x6d, 0x2b, 0xb2, 0x29, 0xbd, 0x72, 0xea, 0xbd, 0x41, 0xc3, 0xca, 0xdb, 0x5d, 0x78, 0xfb, 0x42, 0x48, - 0x18, 0xe1, 0xf9, 0xbd, 0xac, 0x6d, 0xfa, 0x2d, 0x3e, 0xae, 0x26, 0xb0, 0xac, 0x2c, 0x8a, 0xcf, 0xd2, 0x9c, 0x66, - 0xe2, 0x29, 0x1d, 0xf1, 0x0c, 0x42, 0x16, 0x25, 0x4e, 0x50, 0xb1, 0x6b, 0xb9, 0xed, 0xe4, 0xfc, 0xac, 0x38, 0xc1, - 0xca, 0x04, 0xe5, 0xaf, 0x8f, 0x32, 0x66, 0x7d, 0xb9, 0xda, 0x6a, 0xba, 0xb7, 0xf7, 0xbe, 0x42, 0x93, 0x86, 0x52, - 0x42, 0x61, 0x31, 0x2d, 0xa5, 0xd2, 0xe8, 0x40, 0xee, 0xae, 0x57, 0xba, 0x00, 0x0c, 0xc3, 0xb0, 0x79, 0xcf, 0x0b, - 0x22, 0x8a, 0xf1, 0x2a, 0x8b, 0xd7, 0xae, 0x09, 0x66, 0x9b, 0x2d, 0xc0, 0xe1, 0xc1, 0xd0, 0x56, 0xbe, 0xa2, 0xbc, - 0x4a, 0x87, 0x2d, 0x61, 0x38, 0x03, 0x64, 0x79, 0xd2, 0x08, 0xb1, 0x28, 0x70, 0xa3, 0x51, 0xf2, 0x11, 0xf4, 0xca, - 0x18, 0xe7, 0x7e, 0x0c, 0x09, 0xb0, 0xb5, 0x2d, 0x8b, 0x10, 0x56, 0x79, 0x39, 0x56, 0x26, 0xc1, 0xe9, 0x8b, 0x4d, - 0x1e, 0x65, 0x43, 0xd4, 0x54, 0x4a, 0x1d, 0xa8, 0x91, 0xa1, 0xb2, 0x81, 0x3f, 0xf7, 0x98, 0x56, 0xdc, 0x4c, 0xd8, - 0x0c, 0x18, 0xf0, 0x4b, 0xe1, 0xa9, 0x58, 0x14, 0xc8, 0x0c, 0xee, 0xcf, 0xbc, 0xda, 0xd0, 0x5d, 0x2e, 0x9b, 0x61, - 0x8d, 0xb8, 0xd8, 0x46, 0x13, 0x97, 0x61, 0xbd, 0xb3, 0x8a, 0x97, 0xee, 0xaa, 0x1c, 0x6a, 0x61, 0xb8, 0x60, 0x95, - 0x47, 0x62, 0x4d, 0x7f, 0x57, 0xa5, 0x45, 0x97, 0x95, 0x40, 0x0d, 0xa3, 0x37, 0xce, 0x6b, 0xb9, 0x06, 0xb4, 0x00, - 0xfa, 0x5a, 0x3c, 0x17, 0xd6, 0x8a, 0x1a, 0x1f, 0xb6, 0x1c, 0xd3, 0x92, 0xfa, 0xef, 0x20, 0xd3, 0x65, 0x75, 0xcf, - 0xbf, 0x90, 0xb2, 0x90, 0xe1, 0xbc, 0xc6, 0xd8, 0x33, 0xc9, 0xd8, 0x11, 0xe8, 0x69, 0x26, 0xf5, 0xbb, 0xaf, 0x13, - 0x5e, 0x98, 0x96, 0x72, 0x9a, 0xc4, 0x3e, 0x94, 0xc1, 0x72, 0xeb, 0xf7, 0xca, 0x6a, 0x04, 0x8c, 0x40, 0x12, 0x10, - 0xd6, 0x9c, 0x3d, 0x43, 0x38, 0x6f, 0x34, 0xba, 0xf9, 0x09, 0xad, 0x5c, 0x24, 0x15, 0x8c, 0x0c, 0xe2, 0xb9, 0x40, - 0xf0, 0x35, 0x19, 0x0a, 0x11, 0x7f, 0x93, 0x9b, 0x9d, 0x83, 0xab, 0xfd, 0xf4, 0x9d, 0x67, 0x73, 0x35, 0xbb, 0x6e, - 0x19, 0x33, 0x85, 0xf9, 0x78, 0x55, 0xbc, 0xe5, 0xed, 0xfd, 0xf9, 0x1d, 0x00, 0xf7, 0x4e, 0x1b, 0x43, 0x2e, 0x1a, - 0xea, 0x0a, 0xc5, 0x12, 0xca, 0xdd, 0xd7, 0x45, 0x55, 0x5a, 0xa2, 0x3d, 0x58, 0x57, 0x54, 0xa6, 0xac, 0x20, 0x79, - 0x51, 0xe4, 0xb4, 0x8a, 0xee, 0xaf, 0xe4, 0x5f, 0x4a, 0xe1, 0xb2, 0xee, 0x6c, 0x3f, 0x9b, 0x12, 0x81, 0x2d, 0x42, - 0x7d, 0xbb, 0x2d, 0xf4, 0x51, 0x81, 0x09, 0xfb, 0x5a, 0x0b, 0xc5, 0x5f, 0x36, 0x09, 0x45, 0x9c, 0xe9, 0x2d, 0x2f, - 0x05, 0x62, 0xfb, 0x01, 0x02, 0x51, 0x3b, 0xd9, 0x8d, 0x4c, 0x04, 0x75, 0xa4, 0x26, 0x13, 0xeb, 0x4b, 0x4a, 0x32, - 0xcc, 0xf4, 0x6a, 0xf4, 0x3a, 0xcb, 0x25, 0x1b, 0xb4, 0xc0, 0x89, 0xe4, 0xba, 0xf0, 0xb3, 0xad, 0x7e, 0x5a, 0x9c, - 0x58, 0x39, 0x81, 0x3d, 0x56, 0x9a, 0x2c, 0xc8, 0x87, 0x14, 0x67, 0x4f, 0xe6, 0x64, 0x49, 0x9a, 0xd6, 0x14, 0xa4, - 0x09, 0x9c, 0xb0, 0x32, 0xca, 0x04, 0x10, 0x4b, 0x59, 0xa1, 0x0d, 0x48, 0x6f, 0x63, 0xf2, 0x9f, 0x31, 0x2f, 0x3f, - 0xad, 0x89, 0xd6, 0xe4, 0x8a, 0x52, 0x1f, 0x6a, 0xe9, 0x06, 0x1a, 0x02, 0xad, 0x1f, 0xee, 0x48, 0x13, 0xb4, 0x12, - 0xe5, 0xc8, 0x96, 0x43, 0xb8, 0x05, 0x2e, 0xb4, 0x9d, 0xf7, 0x2a, 0xc0, 0xbb, 0x41, 0x9a, 0x60, 0x6e, 0xd1, 0xf5, - 0x0b, 0x22, 0x6a, 0xac, 0x24, 0x26, 0xda, 0x52, 0xc2, 0xa1, 0x24, 0x53, 0x41, 0xb2, 0x41, 0xeb, 0x02, 0x14, 0xd0, - 0x6e, 0x72, 0x92, 0x55, 0x26, 0x70, 0xd2, 0x68, 0xa0, 0xd0, 0x8c, 0x1a, 0x0f, 0x58, 0x23, 0xb9, 0xc0, 0x14, 0x27, - 0xca, 0x30, 0x39, 0xdb, 0xdb, 0xf3, 0xc2, 0x6a, 0xdc, 0x41, 0x72, 0x81, 0x30, 0x5f, 0x2e, 0x3d, 0x09, 0x56, 0x88, - 0x96, 0xcb, 0xd0, 0x06, 0x4b, 0xbe, 0x86, 0x66, 0xd3, 0xbe, 0x20, 0x53, 0x29, 0x00, 0xa7, 0x00, 0x61, 0x83, 0x78, - 0xa1, 0x76, 0xee, 0x85, 0xe0, 0x8c, 0x6a, 0x64, 0x83, 0xa4, 0xd1, 0xbe, 0xb0, 0x18, 0xd7, 0x20, 0xb9, 0x20, 0x61, - 0xc1, 0xf7, 0xf6, 0x76, 0x72, 0x2d, 0x22, 0x7f, 0x02, 0x51, 0xf6, 0x93, 0x94, 0x2c, 0xaa, 0x43, 0x7b, 0x35, 0x56, - 0x9d, 0x01, 0x25, 0x45, 0xe9, 0x65, 0x35, 0xf5, 0x6a, 0x49, 0x10, 0x65, 0x25, 0xac, 0x63, 0xc1, 0x7d, 0xb0, 0xec, - 0x4b, 0x32, 0x7f, 0x26, 0xca, 0x24, 0xeb, 0x5f, 0x36, 0xa6, 0x56, 0xfb, 0xbe, 0x1f, 0x66, 0x63, 0x19, 0xc9, 0x30, - 0x51, 0x58, 0x49, 0xfc, 0x07, 0x1a, 0x4c, 0x6b, 0xe0, 0x41, 0x39, 0xd6, 0x05, 0x51, 0xe0, 0x1b, 0xd5, 0xc6, 0x9c, - 0x26, 0xf9, 0x69, 0xa3, 0x97, 0x41, 0x41, 0xf2, 0xd5, 0x6f, 0x85, 0xe4, 0x50, 0x43, 0xa2, 0xc8, 0x63, 0x05, 0x67, - 0x5b, 0x70, 0xf1, 0x93, 0x58, 0xc1, 0xd9, 0x76, 0xdc, 0x1a, 0x4c, 0xfd, 0xbc, 0x0d, 0x3e, 0x8b, 0x37, 0x28, 0x40, - 0xab, 0x02, 0x0b, 0xca, 0xa3, 0x55, 0xdd, 0x4b, 0xb1, 0x52, 0x10, 0xa6, 0x82, 0x78, 0xac, 0xbe, 0x01, 0x2a, 0x6d, - 0xd4, 0x32, 0x7c, 0x59, 0x30, 0x45, 0x96, 0x4b, 0xa0, 0x9e, 0xb9, 0x02, 0xe4, 0xa4, 0x7d, 0xed, 0xd3, 0xbd, 0x3d, - 0xb0, 0x0d, 0x40, 0x89, 0xf3, 0x87, 0xe1, 0x54, 0xcc, 0x32, 0x50, 0xa5, 0x72, 0xf3, 0x1b, 0x8a, 0xe1, 0x1c, 0x88, - 0x2c, 0x83, 0x1f, 0x50, 0x30, 0x0d, 0xf3, 0x9c, 0xcd, 0x55, 0x99, 0xfe, 0x8d, 0x39, 0x31, 0xa4, 0x9c, 0x2b, 0x9d, - 0x30, 0x43, 0xdd, 0x4c, 0xd3, 0x69, 0x1d, 0x6d, 0xcf, 0xe7, 0x34, 0x15, 0x2f, 0x59, 0x2e, 0x68, 0x0a, 0xd3, 0xaf, - 0x28, 0x0e, 0x66, 0x94, 0x23, 0xd8, 0xb0, 0xb5, 0x56, 0x61, 0x14, 0xdd, 0xdb, 0x44, 0xd4, 0x75, 0xa0, 0x38, 0x4c, - 0xa3, 0x44, 0x0d, 0x62, 0xa7, 0x33, 0x9a, 0x14, 0xce, 0xb2, 0xa6, 0x9d, 0x4e, 0x53, 0x29, 0x1b, 0x92, 0xbb, 0x7b, - 0x8c, 0x18, 0x49, 0x60, 0xa4, 0xe7, 0xbd, 0x5a, 0x0b, 0x04, 0xbc, 0xb7, 0x2c, 0x82, 0x3d, 0x13, 0x2c, 0x2c, 0x8e, - 0xea, 0xd7, 0xe1, 0x2c, 0x05, 0xc9, 0xc6, 0x43, 0x6d, 0x9b, 0x84, 0x83, 0xa4, 0x93, 0x47, 0xdb, 0x2d, 0xab, 0x57, - 0x46, 0x72, 0x18, 0x69, 0xc1, 0x1e, 0xca, 0x98, 0xd1, 0xc2, 0x90, 0x17, 0x32, 0x5b, 0xf1, 0x52, 0x90, 0x9f, 0xe0, - 0xd4, 0xd0, 0x0b, 0x31, 0x49, 0x56, 0x0e, 0xc7, 0x74, 0x2f, 0x4b, 0xed, 0xff, 0x52, 0x78, 0xaf, 0xf1, 0x0b, 0x08, - 0xeb, 0x7e, 0x5d, 0x55, 0x5f, 0x0f, 0xe7, 0x7e, 0x5d, 0x21, 0xe8, 0xeb, 0x60, 0xad, 0x9e, 0x15, 0xc6, 0xed, 0xf8, - 0xc7, 0x7e, 0xcb, 0x35, 0xda, 0xd2, 0xb7, 0x2a, 0x88, 0xa4, 0x12, 0x2d, 0xe5, 0x7e, 0xc0, 0x55, 0x9a, 0x1a, 0xa4, - 0xcb, 0xd5, 0x2d, 0x24, 0xaa, 0x13, 0x0c, 0x95, 0x0e, 0xbf, 0x6d, 0x79, 0xb4, 0x8c, 0xc9, 0x94, 0x9d, 0xf1, 0x36, - 0xcc, 0xc4, 0x2e, 0xec, 0x32, 0xbe, 0x76, 0x12, 0x2f, 0x26, 0xe0, 0x41, 0x7b, 0xd8, 0x10, 0x96, 0xb1, 0x9d, 0xab, - 0x93, 0x40, 0x76, 0xff, 0x84, 0x1b, 0xdd, 0xad, 0x6e, 0x65, 0x7c, 0x00, 0xfb, 0x1f, 0xe1, 0xd8, 0x1c, 0x8f, 0xa3, - 0x9a, 0x03, 0xd3, 0x60, 0x51, 0x94, 0x4e, 0x01, 0xae, 0x94, 0xb7, 0x14, 0x61, 0x5e, 0xc8, 0xf0, 0xf6, 0x37, 0xf8, - 0x7b, 0xcd, 0x12, 0x47, 0x25, 0xc7, 0x79, 0xfe, 0x50, 0x8e, 0xa8, 0xc0, 0x2f, 0xa3, 0xf7, 0x40, 0xc7, 0x92, 0x42, - 0x0b, 0x43, 0x45, 0xcf, 0xb8, 0x9e, 0xc8, 0xd6, 0xac, 0x54, 0x4c, 0xcb, 0x8c, 0x1a, 0x39, 0xcc, 0x86, 0x34, 0x4e, - 0x63, 0x65, 0x8b, 0x72, 0x57, 0xd5, 0xc6, 0x45, 0x5b, 0xb0, 0x58, 0x05, 0x16, 0x97, 0x4b, 0xaf, 0x8e, 0x6a, 0xc2, - 0xac, 0x38, 0x06, 0xc2, 0xcc, 0x4a, 0xa8, 0xa8, 0x69, 0xd6, 0xaa, 0x8d, 0x87, 0x56, 0xf3, 0x89, 0x8c, 0x6e, 0x5e, - 0x83, 0xc3, 0x76, 0x21, 0xa8, 0xe6, 0xb6, 0x4f, 0x01, 0xab, 0xd9, 0x95, 0x03, 0x59, 0x18, 0xfa, 0xb6, 0xcc, 0x94, - 0xad, 0x52, 0x5a, 0x37, 0xe0, 0x17, 0xdd, 0x93, 0x2b, 0xab, 0x51, 0xb7, 0xfe, 0xde, 0xca, 0x35, 0x7a, 0xc6, 0xb7, - 0xe5, 0x1a, 0xd5, 0xb4, 0xdd, 0x9d, 0x16, 0xba, 0x3f, 0x2b, 0x55, 0x8d, 0xb5, 0xb9, 0xca, 0x6f, 0x18, 0xae, 0x0d, - 0xb4, 0xa9, 0xd0, 0x6c, 0xb8, 0xca, 0x59, 0x51, 0x8c, 0xca, 0xb3, 0x04, 0x32, 0x75, 0x67, 0xa4, 0xe8, 0x5f, 0x5b, - 0x8d, 0xf2, 0x40, 0xae, 0xf7, 0x0d, 0x19, 0x27, 0xfc, 0x3a, 0x4c, 0xde, 0xc3, 0x78, 0xd5, 0xcb, 0x17, 0x77, 0x51, - 0x16, 0x0a, 0xaa, 0xb9, 0x4b, 0x05, 0xc3, 0x37, 0x16, 0x0c, 0xdf, 0x28, 0x3e, 0x5d, 0xb5, 0xc7, 0x8b, 0x97, 0x65, - 0x07, 0xc1, 0xa8, 0x30, 0x2c, 0x63, 0x22, 0x36, 0x8f, 0xb1, 0xca, 0xc2, 0x26, 0x25, 0x0b, 0x9b, 0x08, 0x6f, 0xb5, - 0x2b, 0xcf, 0xfb, 0x7e, 0x73, 0x2f, 0xeb, 0x9c, 0xed, 0xfb, 0x6a, 0xe3, 0x7f, 0x1f, 0xdc, 0xdb, 0xc6, 0xe2, 0x72, - 0x07, 0xfe, 0x81, 0x4c, 0x56, 0x51, 0x20, 0x3f, 0x85, 0xa4, 0x03, 0x41, 0x7a, 0xd6, 0x91, 0x83, 0x4a, 0x4e, 0x99, - 0x3c, 0x20, 0x6f, 0x38, 0xcb, 0x05, 0x9f, 0xe8, 0x3e, 0x73, 0x7d, 0xce, 0x48, 0xbe, 0x04, 0x57, 0xb4, 0x8c, 0xb5, - 0x07, 0xf5, 0x93, 0x5c, 0x8b, 0x8f, 0x2c, 0x8d, 0x82, 0x1c, 0x6b, 0x29, 0x92, 0x07, 0x59, 0x41, 0x4c, 0xae, 0xf1, - 0xfa, 0x3b, 0x3c, 0x62, 0x29, 0xcb, 0x63, 0x9a, 0x79, 0x1c, 0x2d, 0xb6, 0x0d, 0xc6, 0x21, 0x20, 0xa3, 0x06, 0xc3, - 0x5f, 0x56, 0x47, 0xfe, 0x7c, 0xe8, 0x0d, 0xfc, 0x40, 0x13, 0x2a, 0x62, 0x1e, 0x41, 0x5a, 0x8a, 0x1f, 0x95, 0x47, - 0x9a, 0xf6, 0xf6, 0x76, 0x3c, 0x57, 0xba, 0x25, 0xe0, 0xf0, 0xb7, 0xfd, 0x06, 0xf5, 0x17, 0x70, 0x3a, 0xa7, 0x1a, - 0x9a, 0xa2, 0x05, 0x5d, 0x3d, 0xc8, 0x22, 0xfc, 0x8f, 0xf4, 0x0e, 0xa7, 0xa8, 0x28, 0x02, 0x05, 0xb5, 0x3b, 0x62, - 0x34, 0x89, 0x5c, 0xfc, 0x91, 0xde, 0x05, 0xe5, 0x79, 0x71, 0x79, 0xbc, 0x59, 0x2e, 0xa0, 0xcb, 0x6f, 0x52, 0x17, - 0x57, 0x83, 0x04, 0x8b, 0x02, 0xf3, 0x8c, 0x8d, 0x81, 0x38, 0xff, 0x46, 0xef, 0x02, 0xd5, 0x1f, 0xb3, 0x4e, 0xeb, - 0xa1, 0x85, 0x41, 0xbd, 0x6f, 0x15, 0xdb, 0xcb, 0xa0, 0x0d, 0x8a, 0x81, 0x6c, 0x7b, 0x41, 0x6a, 0xf5, 0x2a, 0xf3, - 0x10, 0xa1, 0xe2, 0xa1, 0x53, 0xc1, 0xdf, 0xd9, 0xa2, 0x4d, 0xd4, 0x32, 0x5f, 0x57, 0x1a, 0x51, 0x68, 0x50, 0x65, - 0x7a, 0x5c, 0x7a, 0xa9, 0xd9, 0x75, 0xfa, 0x08, 0x82, 0xe5, 0x08, 0xfb, 0x4e, 0xe8, 0x4e, 0x83, 0x2f, 0x55, 0x42, - 0x48, 0x15, 0x49, 0x7a, 0x55, 0xb5, 0x73, 0x2e, 0x3d, 0xc0, 0x3b, 0x24, 0xb4, 0x84, 0xf2, 0x40, 0x66, 0x61, 0xb2, - 0x45, 0x7f, 0x10, 0xc4, 0x5b, 0x98, 0x29, 0x04, 0xa9, 0x8d, 0x45, 0x51, 0x00, 0x15, 0x6a, 0xfa, 0x52, 0x09, 0x80, - 0x70, 0x86, 0x7d, 0x4d, 0x6a, 0x66, 0x52, 0x6a, 0xfa, 0x16, 0xc6, 0xb7, 0x48, 0x49, 0x2a, 0x91, 0x21, 0x95, 0x48, - 0x29, 0xf4, 0xf4, 0xe2, 0x6a, 0x12, 0xb2, 0x17, 0xb4, 0x3c, 0x3f, 0xa7, 0xd6, 0x3c, 0xab, 0x81, 0xe5, 0xc9, 0x7e, - 0x50, 0x11, 0xc0, 0x94, 0xa8, 0xaa, 0x50, 0x94, 0xc7, 0xb2, 0x4d, 0x7a, 0xab, 0xc7, 0x7d, 0x33, 0x2d, 0x62, 0x50, - 0xe2, 0xc5, 0x68, 0x91, 0x7a, 0x31, 0xce, 0x20, 0x1d, 0x91, 0x17, 0x25, 0xfc, 0xd4, 0x5e, 0x8d, 0x5a, 0xb2, 0xf2, - 0xe6, 0x33, 0x7e, 0xa0, 0xcc, 0x0b, 0x48, 0xd1, 0xc4, 0xa9, 0xe1, 0x29, 0xa9, 0x27, 0x0f, 0xdb, 0x59, 0xcb, 0xf6, - 0xb5, 0x4e, 0xd0, 0xd1, 0x80, 0xfd, 0x20, 0xbc, 0x85, 0x35, 0x0b, 0xfb, 0x34, 0xb7, 0x3e, 0xf3, 0xa7, 0x83, 0x7d, - 0x55, 0x0e, 0xa9, 0x97, 0x93, 0x15, 0x89, 0x73, 0x7f, 0xaa, 0xe5, 0xcf, 0x33, 0x9a, 0xdd, 0x9d, 0x53, 0x48, 0x75, - 0xe6, 0x70, 0xda, 0xb7, 0x5a, 0x86, 0x2a, 0x4d, 0xbd, 0x9f, 0x49, 0x65, 0xa5, 0xa8, 0x9f, 0x02, 0x5c, 0x3d, 0x23, - 0x58, 0xc8, 0x68, 0xa3, 0xe5, 0x88, 0x51, 0xbb, 0x85, 0x6e, 0x3d, 0x3d, 0x49, 0xbb, 0x0c, 0xfc, 0x6b, 0x15, 0xa6, - 0x75, 0xb0, 0x00, 0x73, 0xfb, 0x44, 0xea, 0x20, 0xbf, 0x58, 0xf5, 0xca, 0x40, 0x11, 0x84, 0xef, 0xb2, 0xed, 0x53, - 0xdd, 0x94, 0x34, 0xbb, 0x7d, 0xaa, 0xb5, 0xa0, 0x9f, 0x4c, 0xf8, 0xc1, 0x7a, 0x9c, 0xf2, 0xf8, 0x32, 0x2b, 0x0a, - 0x54, 0x00, 0x78, 0x7f, 0xed, 0x7a, 0xde, 0x5f, 0x75, 0xca, 0xa0, 0x0f, 0xb1, 0xd8, 0xf3, 0x84, 0x1b, 0x26, 0x5e, - 0x8d, 0xff, 0xd7, 0xb5, 0xf1, 0xff, 0x6a, 0x9d, 0x39, 0x05, 0xd3, 0x68, 0x9c, 0xd2, 0xc8, 0xb0, 0x4e, 0xa4, 0x08, - 0x50, 0xea, 0x6d, 0xa9, 0x20, 0x6f, 0xae, 0x02, 0xd0, 0xb8, 0x16, 0x23, 0x9e, 0x8a, 0xe6, 0x28, 0x9c, 0xb0, 0xe4, - 0x2e, 0x98, 0xb1, 0xe6, 0x84, 0xa7, 0x3c, 0x9f, 0x86, 0x43, 0x8a, 0xf3, 0xbb, 0x5c, 0xd0, 0x49, 0x73, 0xc6, 0xf0, - 0x0b, 0x9a, 0xcc, 0xa9, 0x60, 0xc3, 0x10, 0xbb, 0xa7, 0x19, 0x0b, 0x13, 0xe7, 0x75, 0x98, 0x65, 0xfc, 0xc6, 0xc5, - 0xef, 0xf8, 0x35, 0x17, 0x1c, 0xbf, 0xb9, 0xbd, 0x1b, 0xd3, 0x14, 0x7f, 0xb8, 0x9e, 0xa5, 0x62, 0x86, 0xf3, 0x30, - 0xcd, 0x9b, 0x39, 0xcd, 0xd8, 0xa8, 0x3b, 0xe4, 0x09, 0xcf, 0x9a, 0x90, 0xb1, 0x3d, 0xa1, 0x41, 0xc2, 0xc6, 0xb1, - 0x70, 0xa2, 0x30, 0xfb, 0xd8, 0x6d, 0x36, 0xa7, 0x19, 0x9b, 0x84, 0xd9, 0x5d, 0x53, 0xd6, 0x08, 0x3e, 0x6f, 0x1d, - 0x84, 0x4f, 0x46, 0x87, 0x5d, 0x91, 0x85, 0x69, 0xce, 0x60, 0x99, 0x82, 0x30, 0x49, 0x9c, 0x83, 0xa3, 0xd6, 0x24, - 0xdf, 0x51, 0x81, 0xbc, 0x30, 0x15, 0xc5, 0x15, 0x7e, 0x03, 0x70, 0xfb, 0xd7, 0x22, 0xc5, 0xd7, 0x33, 0x21, 0x78, - 0xba, 0x18, 0xce, 0xb2, 0x9c, 0x67, 0xc1, 0x94, 0xb3, 0x54, 0xd0, 0xac, 0x7b, 0xcd, 0xb3, 0x88, 0x66, 0xcd, 0x2c, - 0x8c, 0xd8, 0x2c, 0x0f, 0x0e, 0xa7, 0xb7, 0x5d, 0xd0, 0x2c, 0xc6, 0x19, 0x9f, 0xa5, 0x91, 0x1e, 0x8b, 0xa5, 0x31, - 0xcd, 0x98, 0xb0, 0x5f, 0xc8, 0x4b, 0x4c, 0x82, 0x84, 0xa5, 0x34, 0xcc, 0x9a, 0x63, 0x68, 0x0c, 0x66, 0x51, 0x2b, - 0xa2, 0x63, 0x9c, 0x8d, 0xaf, 0x43, 0xaf, 0xdd, 0x79, 0x8c, 0xcd, 0x5f, 0xff, 0x08, 0x39, 0xad, 0xcd, 0xc5, 0xed, - 0x56, 0xeb, 0x4f, 0xa8, 0xbb, 0x32, 0x8a, 0x04, 0x28, 0x68, 0x4f, 0x6f, 0x9d, 0x9c, 0x43, 0x46, 0xdb, 0xa6, 0x96, - 0xdd, 0x69, 0x18, 0x41, 0x3e, 0x70, 0xd0, 0x99, 0xde, 0x16, 0x30, 0xbb, 0x40, 0xa5, 0x98, 0xea, 0x49, 0xea, 0xa7, - 0xc5, 0x6f, 0x85, 0xf8, 0x78, 0x33, 0xc4, 0x1d, 0x03, 0x71, 0x85, 0xf5, 0x66, 0x34, 0xcb, 0x64, 0x6c, 0x35, 0x68, - 0xe7, 0x0a, 0x90, 0x98, 0xcf, 0x69, 0x66, 0xe0, 0x90, 0x0f, 0xbf, 0x19, 0x8c, 0xce, 0x66, 0x30, 0x8e, 0x3f, 0x05, - 0x46, 0x96, 0x46, 0x8b, 0xfa, 0xba, 0xb6, 0x33, 0x3a, 0xe9, 0xc6, 0x14, 0xe8, 0x29, 0xe8, 0xc0, 0xef, 0x1b, 0x16, - 0x89, 0x58, 0xfd, 0x94, 0xe4, 0x7c, 0xa3, 0xde, 0x1d, 0xb5, 0x5a, 0xea, 0x39, 0x67, 0xbf, 0xd0, 0xa0, 0xed, 0x43, - 0x85, 0xe2, 0x0a, 0xff, 0xad, 0x3c, 0xcb, 0x5b, 0xe7, 0x9e, 0xf8, 0x1b, 0xfb, 0x90, 0xaf, 0x95, 0xa2, 0x58, 0x1d, - 0x89, 0xc6, 0x99, 0x91, 0x95, 0x4a, 0xf8, 0x80, 0xdb, 0x4e, 0x72, 0x47, 0xc2, 0x7a, 0xe5, 0x21, 0x4e, 0xd6, 0xff, - 0x46, 0xe5, 0x5d, 0x04, 0x10, 0xe9, 0xb0, 0x52, 0x0d, 0x79, 0x37, 0xeb, 0x91, 0x56, 0x37, 0x6b, 0x36, 0x91, 0xc7, - 0x49, 0x3a, 0xc8, 0x74, 0x72, 0x9e, 0xc7, 0xfa, 0x5c, 0x1a, 0xdb, 0x39, 0x0a, 0x38, 0x9c, 0x34, 0x5d, 0x2e, 0xab, - 0x30, 0x00, 0x93, 0xa7, 0x35, 0xfe, 0x26, 0x74, 0x05, 0x9c, 0x5b, 0x9c, 0x9c, 0x9b, 0xab, 0x5d, 0x52, 0xc3, 0x2b, - 0x12, 0x3e, 0x94, 0x98, 0xf3, 0xa7, 0xa1, 0x88, 0xc1, 0x4b, 0x51, 0x8a, 0x9f, 0x2a, 0x85, 0xc9, 0xdd, 0x77, 0x51, - 0x3f, 0x2d, 0xf3, 0xdb, 0x20, 0x8f, 0x2f, 0x2d, 0xa0, 0x97, 0xef, 0x05, 0x81, 0x1e, 0xf1, 0x57, 0x44, 0xd9, 0x74, - 0xc6, 0xa2, 0x1b, 0x3d, 0xd4, 0xa2, 0xa3, 0xa9, 0x60, 0x32, 0x73, 0xdb, 0x44, 0x1c, 0xe2, 0x30, 0xbf, 0x1c, 0xaa, - 0xa3, 0x92, 0x79, 0x75, 0x30, 0x20, 0x94, 0xd0, 0x2b, 0x23, 0x8d, 0x66, 0xd2, 0x1e, 0xfd, 0xab, 0xd8, 0x6a, 0x9f, - 0xa4, 0xf7, 0xd9, 0x27, 0xe5, 0xc4, 0x73, 0x3e, 0xcb, 0x86, 0x10, 0x8e, 0xd4, 0x52, 0x6f, 0xdd, 0x71, 0xe3, 0x4a, - 0x15, 0xc3, 0xc5, 0xc2, 0xca, 0x03, 0x15, 0x98, 0xd9, 0xd7, 0x4a, 0x50, 0x19, 0xf2, 0x52, 0xc7, 0x35, 0xb4, 0x88, - 0x33, 0x53, 0x02, 0x99, 0x1d, 0xc9, 0x94, 0x46, 0x2f, 0x23, 0xbd, 0xcc, 0x9f, 0xa5, 0xec, 0xe7, 0x19, 0xbd, 0x64, - 0xa0, 0x6b, 0x32, 0x9f, 0x45, 0x32, 0xd6, 0x04, 0xb2, 0xaf, 0xd9, 0x86, 0xe0, 0x05, 0x8b, 0xd4, 0xc2, 0x64, 0xf2, - 0xa5, 0xce, 0x6d, 0x72, 0x9b, 0x2e, 0xf8, 0x8b, 0x41, 0x3b, 0x60, 0x38, 0xe2, 0x93, 0x90, 0xa5, 0x81, 0x74, 0xf9, - 0x96, 0x9d, 0x05, 0x50, 0x1b, 0xb3, 0x28, 0xc8, 0xf4, 0xf2, 0xb4, 0x91, 0xff, 0x13, 0x67, 0xa9, 0x6c, 0x5a, 0x74, - 0xb9, 0x44, 0xa8, 0x42, 0x1f, 0x31, 0x08, 0x3e, 0x55, 0x72, 0x8d, 0x23, 0x6c, 0xbf, 0x2e, 0x4f, 0x9d, 0xd7, 0x56, - 0xa0, 0xb5, 0xb2, 0x50, 0xca, 0x08, 0xe0, 0xab, 0xa5, 0x39, 0xcf, 0x84, 0xe7, 0xc5, 0x38, 0x41, 0xa4, 0x17, 0x4b, - 0x67, 0xd7, 0x49, 0x22, 0xff, 0xeb, 0x37, 0xdb, 0x41, 0xbb, 0x34, 0xdf, 0x6b, 0x87, 0x81, 0x55, 0x72, 0x94, 0x3e, - 0x50, 0x2a, 0xa7, 0x51, 0xfe, 0x56, 0x53, 0xad, 0x9e, 0xcb, 0xe9, 0x62, 0xbd, 0xdd, 0x94, 0xa8, 0xf2, 0x6a, 0x40, - 0xc8, 0x60, 0xd1, 0x96, 0xa1, 0x50, 0x51, 0xcd, 0xbb, 0x54, 0x25, 0xaf, 0x94, 0x88, 0xbe, 0xdc, 0x5d, 0xa4, 0x7a, - 0xc4, 0xe2, 0x8a, 0x19, 0x27, 0x53, 0x9d, 0xe4, 0x0a, 0x8d, 0x11, 0x4b, 0x0f, 0xdd, 0x54, 0x4d, 0xc1, 0x72, 0x47, - 0xd2, 0x8d, 0x74, 0xeb, 0xab, 0x47, 0xaa, 0x14, 0x84, 0xcd, 0x55, 0x64, 0xaa, 0xde, 0x26, 0xc0, 0xc0, 0x6c, 0xcd, - 0x85, 0x99, 0x02, 0x68, 0x63, 0x23, 0x0a, 0xe7, 0x68, 0xae, 0x76, 0x17, 0xdf, 0x8b, 0x62, 0xdf, 0xaa, 0x2a, 0x7f, - 0xb3, 0x08, 0xfe, 0x07, 0x09, 0xb8, 0x50, 0x4a, 0x69, 0xe0, 0xbe, 0x7d, 0x73, 0xfe, 0xde, 0xc5, 0x70, 0x3b, 0x17, - 0xcd, 0xf2, 0x60, 0xe1, 0xea, 0xd4, 0xb8, 0x26, 0x84, 0x59, 0xdd, 0xc0, 0x0d, 0xa7, 0x70, 0xd2, 0x58, 0xf2, 0x82, - 0xfd, 0xdb, 0xe6, 0xcd, 0xcd, 0x4d, 0x13, 0x0e, 0x42, 0x35, 0x67, 0x59, 0x42, 0xd3, 0x21, 0x8f, 0x68, 0xe4, 0x16, - 0x05, 0xf2, 0x45, 0x4c, 0xd3, 0xf2, 0xfe, 0x1e, 0x9e, 0x50, 0x3f, 0xe1, 0x63, 0x75, 0x88, 0x73, 0xd5, 0xaa, 0x1e, - 0x5e, 0x9d, 0xc8, 0x7b, 0xa9, 0x7a, 0x27, 0x42, 0xdd, 0x08, 0x26, 0x32, 0xf8, 0xd9, 0x83, 0x98, 0xcb, 0xc9, 0xbe, - 0x88, 0xe5, 0xc3, 0x39, 0xec, 0x30, 0xf9, 0xb4, 0xbb, 0x58, 0xa3, 0xbe, 0x3e, 0x74, 0x11, 0xf7, 0xd4, 0x9c, 0x73, - 0x59, 0xeb, 0x2a, 0x18, 0x5e, 0x5d, 0x15, 0x27, 0xfb, 0xd0, 0xd7, 0xbe, 0xe9, 0xf7, 0x9a, 0x47, 0x77, 0xa6, 0x7d, - 0x49, 0x91, 0x70, 0x3f, 0x51, 0x4a, 0x7a, 0xd0, 0x05, 0x8c, 0x1b, 0xf5, 0x00, 0x2b, 0x40, 0x91, 0xd0, 0x3a, 0x2a, - 0x4b, 0xe4, 0x16, 0x57, 0x45, 0xdb, 0x20, 0x50, 0x15, 0xab, 0x8d, 0xa2, 0xdc, 0xaf, 0x15, 0x41, 0x18, 0x90, 0x22, - 0x1b, 0xba, 0x2b, 0x04, 0xff, 0x4b, 0xc8, 0x4e, 0xf6, 0x15, 0x1e, 0xae, 0xec, 0xcb, 0x50, 0xd4, 0x35, 0x05, 0x25, - 0xb6, 0x06, 0xa9, 0xc0, 0x6f, 0x04, 0x7e, 0x73, 0x25, 0xab, 0x1a, 0xe9, 0x05, 0x6a, 0x15, 0x48, 0xf9, 0x96, 0x51, - 0x53, 0x86, 0x3c, 0x49, 0xc2, 0x69, 0x4e, 0x03, 0xf3, 0x43, 0x0b, 0x32, 0x90, 0x87, 0xeb, 0x9a, 0x83, 0xce, 0xc7, - 0x39, 0x03, 0xfd, 0x62, 0x5d, 0xad, 0x99, 0x87, 0x99, 0xd7, 0x6c, 0x0e, 0x9b, 0xd7, 0x63, 0x54, 0x88, 0x78, 0x61, - 0x8b, 0xc1, 0x47, 0xad, 0x56, 0x17, 0x92, 0x27, 0x9b, 0x61, 0xc2, 0xc6, 0x69, 0x90, 0xd0, 0x91, 0x28, 0x04, 0x9c, - 0x6a, 0x5b, 0x18, 0xbd, 0xc3, 0xef, 0x1c, 0x65, 0x74, 0xe2, 0xf8, 0xf0, 0xef, 0xfd, 0x03, 0x17, 0x22, 0x0a, 0x52, - 0x11, 0x37, 0x65, 0x92, 0x2e, 0x1c, 0x31, 0x10, 0x71, 0xed, 0x79, 0x61, 0x0d, 0x34, 0xa4, 0xa0, 0x93, 0x15, 0x22, - 0x73, 0x44, 0x8c, 0x45, 0x66, 0xd7, 0x4b, 0xd1, 0x62, 0x6d, 0x06, 0xeb, 0xaa, 0xc1, 0x01, 0x2a, 0x72, 0xa9, 0x49, - 0xaf, 0x57, 0x36, 0xfa, 0x55, 0xfd, 0x69, 0x0d, 0x7d, 0x96, 0x26, 0x58, 0x28, 0x4f, 0xf4, 0x42, 0xb5, 0x78, 0x08, - 0x32, 0x6b, 0x3a, 0x2a, 0xb6, 0x5b, 0xa0, 0x82, 0xa5, 0xd3, 0x99, 0x18, 0x48, 0x2f, 0x78, 0x06, 0xe7, 0x29, 0x2e, - 0xb0, 0x55, 0x02, 0x38, 0xb8, 0x58, 0x28, 0x60, 0x86, 0x61, 0x32, 0xf4, 0x00, 0x22, 0xa7, 0xe9, 0x1c, 0x67, 0x74, - 0x82, 0xba, 0x13, 0x96, 0x36, 0xd5, 0xbb, 0x23, 0x4b, 0x8f, 0xf1, 0x1f, 0xc3, 0x53, 0xe1, 0xcb, 0xde, 0xb0, 0x4c, - 0x76, 0xdd, 0x80, 0xcb, 0xab, 0x8b, 0xa2, 0xe8, 0x66, 0xc2, 0x1b, 0xbc, 0xf2, 0xd0, 0x05, 0xfe, 0xca, 0xba, 0xce, - 0xc5, 0x35, 0x5b, 0xc5, 0xc5, 0x1d, 0xb4, 0xa5, 0x8a, 0xbd, 0x17, 0x64, 0xb5, 0xaf, 0x08, 0x54, 0x7c, 0xea, 0xb9, - 0x34, 0x9f, 0x36, 0x15, 0xb3, 0x6b, 0x4a, 0x92, 0x75, 0xa1, 0x29, 0xd2, 0xae, 0xdd, 0xbf, 0x8a, 0x85, 0xe4, 0x63, - 0xfa, 0x4c, 0x87, 0xf2, 0x3e, 0x5c, 0x94, 0x67, 0x80, 0xf4, 0xb3, 0x7d, 0xea, 0x07, 0xd5, 0xf8, 0xc9, 0xd5, 0x69, - 0x9d, 0x29, 0x02, 0x23, 0x2b, 0xef, 0xbc, 0x0b, 0x93, 0x04, 0x06, 0xbc, 0x32, 0xfa, 0x8e, 0x7d, 0x49, 0xc8, 0x40, - 0x5c, 0x78, 0xa8, 0xd0, 0xfb, 0xf4, 0xa9, 0xd4, 0x41, 0xad, 0x8b, 0xf6, 0x76, 0x84, 0x89, 0x2e, 0x29, 0x71, 0xcd, - 0x20, 0x3e, 0x5e, 0xcb, 0xa3, 0xee, 0x56, 0xbc, 0x4b, 0x69, 0xb0, 0x8e, 0x9c, 0x10, 0x71, 0xb3, 0x34, 0x72, 0x9d, - 0xbf, 0x0c, 0x13, 0x36, 0xfc, 0x48, 0xdc, 0xdd, 0x85, 0x87, 0xd6, 0x8f, 0x49, 0x4a, 0xae, 0x60, 0x38, 0x3c, 0xaa, - 0x7b, 0xde, 0x33, 0xdf, 0x62, 0xde, 0xea, 0x1e, 0x1d, 0xb7, 0xb7, 0xbb, 0x00, 0xc6, 0xa3, 0xc6, 0xe9, 0x5d, 0x15, - 0x97, 0xd5, 0xf5, 0x58, 0x15, 0x14, 0x80, 0x66, 0x55, 0xee, 0x48, 0xa2, 0x22, 0xee, 0x27, 0x29, 0xcd, 0x75, 0x14, - 0x53, 0x03, 0x38, 0x85, 0xe6, 0x6f, 0xae, 0xf3, 0x97, 0xb2, 0x8c, 0x96, 0x2e, 0x10, 0x99, 0xc3, 0x41, 0x5c, 0x18, - 0x0b, 0xec, 0x5e, 0x3f, 0xa2, 0x22, 0x64, 0x89, 0x6a, 0xd2, 0x35, 0x16, 0xfb, 0xca, 0x8c, 0x96, 0xcb, 0xbc, 0x3e, - 0x17, 0x56, 0xc7, 0xa0, 0x9c, 0xd9, 0xc9, 0x7e, 0x05, 0xb7, 0x9c, 0x99, 0xdc, 0x93, 0x76, 0x2c, 0xb1, 0x9a, 0xa1, - 0x7a, 0xe7, 0xfc, 0x65, 0x28, 0x4f, 0x19, 0x01, 0x80, 0x5c, 0x03, 0x08, 0x51, 0x6e, 0x75, 0x8a, 0xc6, 0x4b, 0x08, - 0xf7, 0x45, 0x98, 0x8d, 0xa9, 0x58, 0x41, 0x6c, 0xa2, 0x92, 0x5a, 0xbb, 0x26, 0xa2, 0xbd, 0x06, 0x6d, 0x58, 0x87, - 0xf6, 0x0a, 0x90, 0xde, 0xdf, 0x5d, 0xb0, 0x82, 0xec, 0x2e, 0x94, 0x5c, 0xfb, 0xf0, 0xee, 0x2b, 0x38, 0x14, 0xc9, - 0x53, 0xb0, 0x44, 0x62, 0x04, 0x92, 0x56, 0x2e, 0x8e, 0x12, 0x21, 0x5c, 0x8a, 0x10, 0xc5, 0x09, 0x1c, 0x39, 0x96, - 0x04, 0xb1, 0x70, 0x9d, 0xbe, 0x82, 0x9c, 0x46, 0x0a, 0x66, 0x92, 0xc9, 0x56, 0xbc, 0x38, 0xd9, 0x57, 0xb5, 0x95, - 0x08, 0x50, 0x95, 0x00, 0x09, 0x72, 0x9f, 0x56, 0x38, 0x80, 0x44, 0x68, 0x1b, 0x0f, 0x11, 0x9b, 0x97, 0xc4, 0x26, - 0xcf, 0x5b, 0xf5, 0x4e, 0x92, 0xf0, 0x9a, 0x26, 0xbd, 0xdd, 0x45, 0xb6, 0x5c, 0xb6, 0x8a, 0x93, 0x7d, 0xf5, 0xe8, - 0x9c, 0x48, 0xbe, 0xa1, 0xee, 0xc8, 0x94, 0x4b, 0x0c, 0x87, 0x18, 0x21, 0x3d, 0xd4, 0xe4, 0x45, 0x05, 0xba, 0x83, - 0xc2, 0x75, 0x64, 0x46, 0x86, 0xac, 0x54, 0x6a, 0x50, 0x85, 0xeb, 0xb0, 0x68, 0xbd, 0x2c, 0x17, 0x74, 0x0a, 0xa5, - 0xf1, 0x72, 0xd9, 0x2e, 0x5c, 0x67, 0xc2, 0x52, 0x78, 0xca, 0x96, 0x4b, 0x79, 0x3e, 0x70, 0xc2, 0x52, 0xaf, 0x05, - 0x64, 0xeb, 0x3a, 0x93, 0xf0, 0x56, 0x4e, 0xd8, 0xbc, 0x09, 0x6f, 0xbd, 0xb6, 0x7e, 0xe5, 0x97, 0xf8, 0xc9, 0x81, - 0xe2, 0xaa, 0x15, 0x4d, 0xf4, 0x8a, 0x46, 0x78, 0xa6, 0x4e, 0x3e, 0x11, 0x2f, 0x22, 0xc9, 0xe6, 0x15, 0x8d, 0xcc, - 0x8a, 0xce, 0xb6, 0xac, 0xe8, 0xec, 0x9e, 0x15, 0x0d, 0xf5, 0xea, 0x39, 0x25, 0xee, 0xf8, 0x72, 0xd9, 0x6e, 0x55, - 0xd8, 0x3b, 0xd9, 0x8f, 0xd8, 0x1c, 0x56, 0x03, 0xf4, 0x42, 0xc1, 0x26, 0x74, 0x33, 0x51, 0xd6, 0x51, 0x4c, 0x7f, - 0x15, 0x26, 0x2b, 0x2c, 0x64, 0x75, 0x2c, 0xd8, 0x74, 0x5d, 0x06, 0xe9, 0xfe, 0x48, 0xca, 0x66, 0x80, 0x87, 0x1c, - 0xf0, 0x10, 0x9b, 0x3b, 0x33, 0x3d, 0xf7, 0xbd, 0x8b, 0x5d, 0xc7, 0x35, 0x64, 0x7d, 0x55, 0x5c, 0x82, 0x8c, 0x90, - 0xf3, 0x7b, 0x10, 0x2d, 0x42, 0x6d, 0xb7, 0xb7, 0x9d, 0xe6, 0x20, 0x9e, 0x7e, 0xc3, 0xb3, 0xc8, 0x0d, 0x54, 0xd5, - 0x5f, 0x85, 0xaa, 0x09, 0x4b, 0x75, 0x76, 0xd6, 0x56, 0x5a, 0xab, 0xde, 0xdb, 0x14, 0xd7, 0x39, 0x3a, 0x52, 0x35, - 0xa6, 0xa1, 0x10, 0x34, 0x4b, 0x35, 0xe5, 0xba, 0xee, 0xff, 0x17, 0x54, 0xb8, 0x81, 0xaf, 0x84, 0x66, 0x01, 0x0c, - 0x01, 0x6a, 0x0d, 0x5f, 0xf3, 0x7c, 0x25, 0x9e, 0x76, 0x2a, 0x0d, 0xf6, 0x0e, 0xd9, 0x56, 0x86, 0x2a, 0x02, 0xa3, - 0x67, 0x36, 0xa1, 0xd1, 0xa5, 0x64, 0xd0, 0xfd, 0xe1, 0x95, 0x56, 0x58, 0x57, 0xc4, 0x5d, 0xd5, 0x00, 0xbb, 0x3f, - 0xce, 0x3a, 0x8f, 0x0f, 0xcf, 0x5c, 0xac, 0x78, 0x3c, 0x1f, 0x8d, 0x5c, 0x54, 0x38, 0x0f, 0x6b, 0xd6, 0x3e, 0xfc, - 0x71, 0xf6, 0xe5, 0xf3, 0xd6, 0x97, 0x65, 0xe3, 0x14, 0x88, 0x48, 0x27, 0x04, 0x18, 0x51, 0x65, 0xc1, 0x6b, 0x66, - 0x34, 0x0a, 0xd3, 0xed, 0xd3, 0x19, 0xd8, 0xd3, 0xc9, 0xa7, 0x94, 0x46, 0x40, 0x9c, 0x78, 0xad, 0xf4, 0x32, 0xa1, - 0x73, 0x6a, 0xee, 0x2a, 0xdc, 0x30, 0xd8, 0x86, 0x16, 0x43, 0x3e, 0x4b, 0x85, 0xce, 0x8c, 0xd0, 0xac, 0xd6, 0x9a, - 0xd2, 0x95, 0x9c, 0x83, 0x6d, 0x23, 0xdc, 0x29, 0x39, 0x57, 0x97, 0x5e, 0xc5, 0x15, 0x76, 0x2d, 0x00, 0xb6, 0x42, - 0xd6, 0xdf, 0x52, 0x1e, 0xb4, 0x70, 0x6b, 0x1b, 0x6c, 0xb8, 0x8d, 0x02, 0xd7, 0xbd, 0x30, 0x78, 0x92, 0xce, 0xcd, - 0xda, 0x05, 0x13, 0x5b, 0xf1, 0xf5, 0x49, 0x0c, 0x5c, 0x67, 0xd0, 0x59, 0x4a, 0xf3, 0x7c, 0x2b, 0x02, 0xca, 0x45, - 0xc4, 0x6e, 0x55, 0xdb, 0xdd, 0xd2, 0x0b, 0x6e, 0x61, 0xd8, 0x61, 0x12, 0xe0, 0x32, 0xc4, 0xaa, 0x6b, 0xd1, 0xd1, - 0x88, 0x0e, 0x4b, 0xdf, 0x30, 0x04, 0xcb, 0x46, 0x2c, 0x11, 0x10, 0x33, 0x92, 0xc1, 0x1c, 0xf7, 0x35, 0x4f, 0xa9, - 0x8b, 0x4c, 0xfa, 0xa7, 0x86, 0x5f, 0xcb, 0xff, 0xcd, 0xf0, 0xa8, 0x1e, 0xeb, 0xb0, 0xe8, 0x51, 0x96, 0x4b, 0xe3, - 0x17, 0xaa, 0x95, 0xd7, 0x11, 0xc9, 0xa5, 0xe3, 0x67, 0xdb, 0x06, 0x7a, 0xd8, 0x36, 0x59, 0xb4, 0xbf, 0x3c, 0x6a, - 0xb7, 0x0a, 0x17, 0xbb, 0xd0, 0xdd, 0x43, 0x77, 0x89, 0x6c, 0x75, 0x00, 0xad, 0x66, 0xe9, 0xaf, 0x69, 0xd7, 0x69, - 0x3f, 0x69, 0xbb, 0x58, 0xdd, 0x3b, 0x80, 0x8a, 0x92, 0x19, 0x0c, 0xc1, 0x5b, 0xfa, 0xbb, 0xa7, 0x52, 0xef, 0xfc, - 0x61, 0xf0, 0x3c, 0x6a, 0xb7, 0x5c, 0xec, 0xe6, 0x82, 0x4f, 0x7f, 0xc5, 0x14, 0x0e, 0x5c, 0xec, 0x0e, 0x13, 0x9e, - 0x53, 0x7b, 0x0e, 0x4a, 0x9d, 0xfd, 0xfd, 0x93, 0x50, 0x10, 0x4d, 0x33, 0x9a, 0xe7, 0x8e, 0xdd, 0xbf, 0x26, 0xa5, - 0x4f, 0x30, 0xcc, 0x8d, 0x14, 0x97, 0x53, 0x21, 0xf1, 0xa2, 0xae, 0x04, 0xb0, 0xa9, 0x4a, 0x95, 0xad, 0x11, 0x9b, - 0x14, 0x01, 0x25, 0x63, 0x53, 0xda, 0xd5, 0x27, 0x47, 0xde, 0xb0, 0xf5, 0xd4, 0xc0, 0x2a, 0x88, 0xbc, 0x3e, 0x40, - 0xad, 0x64, 0xc2, 0xd2, 0xcb, 0x0d, 0xa5, 0xe1, 0xed, 0x86, 0x52, 0x50, 0xd9, 0x4a, 0xe8, 0xf4, 0x75, 0x35, 0x9f, - 0xc6, 0x7a, 0xa5, 0xf8, 0xd8, 0x20, 0x46, 0xd2, 0xd1, 0xf9, 0x09, 0x48, 0xad, 0x65, 0x90, 0x3d, 0xfc, 0xf6, 0xe1, - 0xa0, 0xe4, 0xd7, 0x0c, 0x57, 0xf6, 0xf2, 0xfb, 0x66, 0x08, 0xa5, 0x4d, 0x70, 0x78, 0x27, 0xbf, 0x6a, 0xae, 0xf4, - 0xf6, 0xd3, 0x04, 0x67, 0x69, 0x55, 0xbf, 0x63, 0xe9, 0xf5, 0xb1, 0xf7, 0xd5, 0xb5, 0xdf, 0x50, 0xac, 0x15, 0x9f, - 0x72, 0xfd, 0x87, 0x09, 0x9b, 0x54, 0x24, 0xb0, 0x0e, 0xa6, 0xd4, 0x78, 0x20, 0xfb, 0xc9, 0xee, 0x44, 0xa9, 0x3e, - 0x97, 0x70, 0xa6, 0x13, 0xae, 0xcd, 0x98, 0x65, 0xf4, 0x32, 0xe1, 0x37, 0xab, 0xf7, 0x80, 0x6d, 0xaf, 0x1c, 0xb3, - 0x71, 0x6c, 0x1d, 0xd4, 0xa2, 0xa4, 0x5c, 0x84, 0x7b, 0x07, 0x28, 0xfe, 0xe5, 0x9f, 0x7d, 0xff, 0x5f, 0xfe, 0xf9, - 0x93, 0x55, 0xa1, 0xfb, 0xe2, 0x0a, 0x8b, 0xaa, 0xdb, 0xed, 0xbb, 0x6b, 0xf3, 0x48, 0x75, 0x9c, 0x6f, 0xae, 0xb3, - 0xb6, 0x08, 0xf0, 0x7e, 0x6d, 0x09, 0xd6, 0x0a, 0xd5, 0xee, 0x73, 0x7e, 0x0b, 0x60, 0x30, 0xaf, 0x4f, 0x42, 0x06, - 0x95, 0x7e, 0x17, 0x68, 0x57, 0x28, 0x78, 0xd0, 0x8a, 0xfc, 0x76, 0x0c, 0x7f, 0x6a, 0x0e, 0xbf, 0x13, 0x7c, 0xed, - 0x9f, 0x18, 0x5e, 0x5d, 0x95, 0x19, 0x79, 0x76, 0x53, 0x38, 0xef, 0xdf, 0x5f, 0x2b, 0xd1, 0x8a, 0x47, 0xd0, 0x42, - 0x3d, 0x79, 0x9e, 0x90, 0x0c, 0xaf, 0x5e, 0xc1, 0x25, 0x3f, 0x27, 0xd7, 0x99, 0x71, 0xf0, 0xde, 0x23, 0x1c, 0xa0, - 0x8b, 0xfa, 0xac, 0x64, 0xa7, 0x6b, 0x92, 0x01, 0x4a, 0xc1, 0xdc, 0x00, 0x30, 0xf1, 0xf0, 0x4a, 0x5b, 0x9b, 0x67, - 0xca, 0x0d, 0x13, 0xac, 0x92, 0xb6, 0x76, 0xcf, 0xd4, 0x90, 0x8e, 0x9d, 0xf7, 0x12, 0x5f, 0xb2, 0x32, 0xad, 0xac, - 0x7b, 0xe9, 0xea, 0x02, 0x3b, 0xa2, 0x64, 0x3f, 0xf3, 0x30, 0x99, 0x3f, 0x8c, 0xf1, 0x6d, 0x17, 0xa8, 0x4b, 0x67, - 0xf9, 0x6f, 0xad, 0x12, 0x2c, 0x9b, 0xcb, 0x9a, 0x3e, 0x20, 0xb3, 0x12, 0xfe, 0xbe, 0x2d, 0x70, 0x2a, 0xe8, 0x27, - 0x03, 0xa7, 0xc9, 0x83, 0x02, 0xa7, 0xea, 0x86, 0xbe, 0x3f, 0x32, 0x70, 0xfa, 0x77, 0x3b, 0x70, 0x0a, 0x24, 0xf8, - 0xf3, 0x83, 0x82, 0x9b, 0x26, 0xf0, 0xc4, 0x6f, 0x72, 0xd2, 0xd6, 0x46, 0x40, 0xc2, 0xc7, 0x10, 0xd9, 0xfc, 0xb7, - 0x0f, 0x54, 0x26, 0x7c, 0x6c, 0x87, 0x29, 0xe1, 0x8e, 0x5a, 0x88, 0x4b, 0xe2, 0x8c, 0x2c, 0xdc, 0x1f, 0x6f, 0xdb, - 0x4f, 0x07, 0xed, 0xee, 0x41, 0x7b, 0xe2, 0x06, 0x2e, 0x48, 0x5d, 0x59, 0xd0, 0xea, 0x1e, 0x1c, 0x40, 0xc1, 0x8d, - 0x55, 0xd0, 0x81, 0x02, 0x66, 0x15, 0x1c, 0x41, 0xc1, 0xd0, 0x2a, 0x78, 0x04, 0x05, 0x91, 0x55, 0xf0, 0x18, 0x0a, - 0xe6, 0x6e, 0x31, 0x60, 0x65, 0x74, 0xf8, 0x31, 0x92, 0xd7, 0x59, 0xec, 0x64, 0xf5, 0x54, 0xfe, 0x98, 0x98, 0x2a, - 0x8f, 0xcb, 0x63, 0x40, 0xcd, 0x43, 0x73, 0x6b, 0xc5, 0xd5, 0x67, 0x57, 0x08, 0x27, 0x04, 0x4e, 0xe5, 0x61, 0x30, - 0xca, 0x55, 0xcd, 0x03, 0xf3, 0xda, 0x0d, 0xca, 0x7b, 0xa9, 0x5a, 0xb8, 0x63, 0x22, 0x9c, 0x81, 0x8b, 0xf0, 0xac, - 0xac, 0x7c, 0xd4, 0x88, 0x74, 0xb7, 0x70, 0x21, 0x44, 0x75, 0x1b, 0xcb, 0x01, 0xc2, 0xea, 0x02, 0xec, 0x67, 0x52, - 0x3e, 0xfa, 0x82, 0xbf, 0x67, 0x13, 0x6a, 0x3e, 0x0f, 0x62, 0x06, 0x70, 0x5c, 0x04, 0x07, 0xb8, 0xe3, 0xea, 0x0a, - 0xb3, 0x2f, 0xf1, 0x69, 0x75, 0x01, 0xd0, 0x5b, 0x41, 0xd4, 0x8d, 0x0a, 0x19, 0x56, 0x86, 0xde, 0x18, 0x8b, 0x70, - 0x1c, 0x40, 0xc8, 0x12, 0x7c, 0xa6, 0xc1, 0x29, 0x21, 0xa4, 0xd5, 0x9f, 0x05, 0x5f, 0xe2, 0x9b, 0x98, 0xa6, 0xc1, - 0xbc, 0xe8, 0x96, 0x04, 0xa0, 0x22, 0xa6, 0x6f, 0x45, 0x79, 0x6f, 0x9c, 0xa4, 0x8a, 0xea, 0xb5, 0x82, 0xb3, 0x59, - 0x52, 0xcf, 0x96, 0x58, 0x9a, 0xe5, 0x93, 0x19, 0x25, 0xfc, 0xa6, 0x79, 0xeb, 0xf6, 0x36, 0xc7, 0xd7, 0x60, 0x76, - 0x65, 0x7c, 0xed, 0x25, 0x00, 0x5b, 0x3e, 0xbd, 0x0f, 0xc7, 0xe5, 0xef, 0x57, 0x34, 0xcf, 0xc3, 0xb1, 0xae, 0xb9, - 0x3d, 0x9e, 0x26, 0x41, 0xb4, 0x63, 0x69, 0x06, 0x08, 0x88, 0x89, 0x01, 0x46, 0xc0, 0xa7, 0xa1, 0x43, 0x64, 0x30, - 0xf5, 0x7a, 0x74, 0x4d, 0xe2, 0xaa, 0x5e, 0x24, 0xc2, 0x71, 0x55, 0x70, 0x32, 0xcd, 0xa8, 0x2c, 0x55, 0x68, 0x2c, - 0x4e, 0xf6, 0xa1, 0x40, 0xbd, 0xde, 0x12, 0x45, 0x33, 0x0e, 0x94, 0xed, 0xb1, 0x34, 0xc7, 0x44, 0xd1, 0xec, 0x44, - 0xa5, 0x32, 0x4b, 0x69, 0x3d, 0x76, 0xf3, 0x79, 0x7b, 0x08, 0x7f, 0x74, 0x64, 0xe8, 0xf3, 0xd1, 0x68, 0x74, 0x6f, - 0x54, 0xed, 0xf3, 0x68, 0x44, 0x3b, 0xf4, 0xa8, 0x0b, 0x49, 0x2c, 0x4d, 0x1d, 0x8b, 0x69, 0x17, 0x12, 0x77, 0x8b, - 0x87, 0x55, 0x86, 0xb0, 0x8d, 0x88, 0x17, 0x0f, 0x8f, 0xb0, 0x15, 0xd3, 0x8c, 0x2e, 0x26, 0x61, 0x36, 0x66, 0x69, - 0xd0, 0x2a, 0xfc, 0xb9, 0x0e, 0x49, 0x7d, 0x7e, 0x7c, 0x7c, 0x5c, 0xf8, 0x91, 0x79, 0x6a, 0x45, 0x51, 0xe1, 0x0f, - 0x17, 0xe5, 0x34, 0x5a, 0xad, 0xd1, 0xa8, 0xf0, 0x99, 0x29, 0x38, 0xe8, 0x0c, 0xa3, 0x83, 0x4e, 0xe1, 0xdf, 0x58, - 0x35, 0x0a, 0x9f, 0xea, 0xa7, 0x8c, 0x46, 0xb5, 0x4c, 0x98, 0xc7, 0xad, 0x56, 0xe1, 0x2b, 0x42, 0x5b, 0x80, 0x59, - 0xaa, 0x7e, 0x06, 0xe1, 0x4c, 0x70, 0x60, 0xee, 0xdd, 0x44, 0x78, 0x83, 0x4b, 0x7d, 0xcb, 0x88, 0xfa, 0x26, 0x47, - 0x81, 0x2e, 0xf0, 0xcf, 0x76, 0xf0, 0x08, 0x88, 0x59, 0x06, 0x8d, 0x12, 0x13, 0x5b, 0xaa, 0xbd, 0x06, 0xca, 0x92, - 0xaf, 0x7f, 0x26, 0x49, 0x15, 0x53, 0x02, 0x4e, 0x06, 0x35, 0xd5, 0x65, 0x78, 0x94, 0x6e, 0x91, 0x1f, 0xec, 0xd3, - 0xf2, 0xe3, 0xee, 0x21, 0xe2, 0x83, 0xfd, 0xe1, 0xe2, 0x83, 0x52, 0x4b, 0x7c, 0x28, 0xe6, 0x71, 0x27, 0x88, 0x3b, - 0x8c, 0xe9, 0xf0, 0xe3, 0x35, 0xbf, 0x6d, 0xc2, 0x96, 0xc8, 0x5c, 0x29, 0x58, 0x76, 0x7f, 0x6b, 0xd6, 0x8c, 0xe9, - 0xcc, 0xfa, 0xa2, 0x87, 0x54, 0x1f, 0xde, 0xa4, 0xc4, 0x7d, 0x63, 0x6c, 0x5b, 0x55, 0x32, 0x1a, 0x11, 0xf7, 0xcd, - 0x68, 0xe4, 0x9a, 0xb3, 0x92, 0xa1, 0xa0, 0xb2, 0xd6, 0xeb, 0x5a, 0x89, 0xac, 0xf5, 0xe5, 0x97, 0x76, 0x99, 0x5d, - 0xa0, 0x43, 0x4f, 0x76, 0x98, 0x49, 0xbf, 0x89, 0x58, 0x0e, 0x5b, 0x0d, 0x3e, 0x34, 0x52, 0xbf, 0xab, 0x31, 0xad, - 0x5d, 0xab, 0x5d, 0x02, 0xbc, 0xe1, 0x2e, 0xf0, 0xd5, 0x8b, 0x02, 0xc6, 0xd4, 0xe4, 0x2d, 0x3e, 0xbd, 0xfb, 0x2a, - 0xf2, 0xee, 0x04, 0x2a, 0x58, 0xfe, 0x26, 0x5d, 0x39, 0x04, 0xa4, 0x60, 0x24, 0xc4, 0x9e, 0x56, 0x21, 0xf8, 0x78, - 0x9c, 0xc0, 0xb7, 0x5e, 0x16, 0xb5, 0xfb, 0x63, 0x55, 0xf3, 0x7e, 0x6d, 0xbe, 0x81, 0xdd, 0x50, 0xdf, 0xb6, 0x2a, - 0x3f, 0x3d, 0xa5, 0x92, 0xc7, 0xe7, 0xfa, 0x1b, 0x44, 0xd2, 0x2c, 0x5e, 0x68, 0x26, 0xbf, 0x50, 0x29, 0xc7, 0x02, - 0xd2, 0x6d, 0x54, 0xc7, 0x51, 0x51, 0xe8, 0xc3, 0x1a, 0x11, 0xcb, 0xa7, 0x70, 0xaf, 0xa9, 0x6a, 0x49, 0x3f, 0xc5, - 0xc2, 0xf3, 0x1b, 0x2b, 0xbe, 0x53, 0x5b, 0xae, 0xc2, 0x04, 0x78, 0x94, 0xc3, 0xfc, 0x4e, 0x14, 0xae, 0xf6, 0xbb, - 0x1b, 0x24, 0xba, 0x8e, 0xc2, 0xa7, 0x8a, 0x3c, 0x59, 0x33, 0x04, 0xe7, 0x77, 0xb9, 0x20, 0xe6, 0x95, 0x29, 0x28, - 0xec, 0xf8, 0xa5, 0x7c, 0xa3, 0xb0, 0x25, 0xa3, 0x25, 0xf9, 0x34, 0x4c, 0x15, 0x1b, 0x25, 0xae, 0xe2, 0x07, 0xbb, - 0x8b, 0x6a, 0xe5, 0x0b, 0xd7, 0x80, 0xad, 0x88, 0xb7, 0x77, 0xb2, 0x0f, 0x0d, 0x7a, 0x4e, 0x0d, 0xf4, 0x74, 0x2d, - 0xc8, 0xf2, 0x89, 0x74, 0x87, 0x2b, 0x3f, 0xbf, 0xc1, 0x7e, 0x7e, 0xe3, 0xfc, 0x79, 0xd1, 0xbc, 0xa1, 0xd7, 0x1f, - 0x99, 0x68, 0x8a, 0x70, 0xda, 0x04, 0xc3, 0x47, 0x3a, 0x47, 0x35, 0x7b, 0x96, 0x59, 0x7e, 0xea, 0xaa, 0x83, 0xee, - 0x2c, 0x87, 0xac, 0x08, 0xa9, 0xbe, 0x07, 0x29, 0x4f, 0x69, 0xb7, 0x9e, 0xcd, 0x69, 0x07, 0xd9, 0x0d, 0xb6, 0x2e, - 0x16, 0x1c, 0xb2, 0x28, 0xc4, 0x5d, 0xd0, 0xd2, 0x6c, 0xbd, 0x65, 0x22, 0xe8, 0xad, 0x8d, 0xf5, 0x03, 0x8d, 0xdc, - 0x86, 0x94, 0x5e, 0xd9, 0x7a, 0x26, 0xc1, 0xb6, 0x4c, 0x80, 0x4f, 0xe5, 0x36, 0x82, 0x4b, 0xd5, 0xfc, 0xb5, 0x92, - 0x42, 0x57, 0x8b, 0x65, 0x6e, 0xe3, 0x43, 0x20, 0x0b, 0xc2, 0x91, 0xa0, 0x19, 0x7e, 0x48, 0xcd, 0x6b, 0x79, 0x0c, - 0x69, 0x01, 0x62, 0x26, 0x68, 0x1f, 0x4f, 0x6f, 0x1f, 0xde, 0xfd, 0xfd, 0xd3, 0x2f, 0x34, 0x8e, 0xcc, 0xb5, 0x3c, - 0xae, 0xdb, 0x85, 0x8d, 0x90, 0x84, 0x77, 0x01, 0x4b, 0xa5, 0xcc, 0xbb, 0x06, 0xbf, 0x68, 0x77, 0xca, 0x75, 0x92, - 0x6e, 0x46, 0x13, 0xf9, 0x15, 0x3e, 0xbd, 0x14, 0x07, 0x8f, 0xa6, 0xb7, 0x66, 0x35, 0xda, 0x2b, 0xc9, 0xb7, 0x7f, - 0x68, 0x8e, 0xed, 0xf6, 0xa4, 0xde, 0x7a, 0x9e, 0xe8, 0xd1, 0xf4, 0xb6, 0xab, 0x04, 0x6d, 0x33, 0x53, 0x50, 0xb5, - 0xa6, 0xb7, 0x76, 0x96, 0x71, 0xd5, 0x91, 0xe3, 0x1f, 0xe4, 0x0e, 0x0d, 0x73, 0xda, 0x85, 0x7b, 0xc7, 0xd9, 0x30, - 0x4c, 0xb4, 0x30, 0x9f, 0xb0, 0x28, 0x4a, 0x68, 0xd7, 0xc8, 0x6b, 0xa7, 0xfd, 0x08, 0x92, 0x74, 0xed, 0x25, 0xab, - 0xaf, 0x8a, 0x85, 0xbc, 0x12, 0x4f, 0xe1, 0x75, 0xce, 0x13, 0xf8, 0xe8, 0xc7, 0x46, 0x74, 0xea, 0xec, 0xd5, 0x56, - 0x85, 0x3c, 0xf9, 0xbb, 0x3e, 0x97, 0xa3, 0xd6, 0x9f, 0xba, 0x72, 0xc1, 0x5b, 0x5d, 0xc1, 0xa7, 0x41, 0xf3, 0xa0, - 0x3e, 0x11, 0x78, 0x55, 0x4e, 0x01, 0x6f, 0x98, 0x16, 0x06, 0x69, 0xa5, 0xf8, 0xb4, 0xe3, 0xb7, 0x75, 0x99, 0xec, - 0x00, 0xf2, 0xc2, 0xca, 0xa2, 0xa2, 0x3e, 0x99, 0x7f, 0x9b, 0xdd, 0xf2, 0x64, 0xf3, 0x6e, 0x79, 0x62, 0x76, 0xcb, - 0xfd, 0x14, 0xfb, 0xf9, 0xa8, 0x0d, 0x7f, 0xba, 0xd5, 0x84, 0x82, 0x96, 0x73, 0x30, 0xbd, 0x75, 0x40, 0x4f, 0x6b, - 0x76, 0xa6, 0xb7, 0x2a, 0xc7, 0x1a, 0x62, 0x37, 0x2d, 0xc8, 0x3a, 0xc6, 0x2d, 0x07, 0x0a, 0xe1, 0x6f, 0xab, 0xf6, - 0xaa, 0x7d, 0x08, 0xef, 0xa0, 0xd5, 0xd1, 0xfa, 0xbb, 0xce, 0xfd, 0x9b, 0x36, 0x48, 0xb9, 0xf0, 0x02, 0xc3, 0x8d, - 0x91, 0x2f, 0xc2, 0xeb, 0x6b, 0x1a, 0x05, 0x23, 0x3e, 0x9c, 0xe5, 0xff, 0xa4, 0xe1, 0xd7, 0x48, 0xbc, 0x77, 0x4b, - 0xaf, 0xf4, 0x63, 0x9a, 0xaa, 0x8c, 0x6f, 0xd3, 0xc3, 0xa2, 0x5c, 0xa7, 0x20, 0x1f, 0x86, 0x09, 0xf5, 0x3a, 0xfe, - 0xe1, 0x86, 0x4d, 0xf0, 0xef, 0xb2, 0x36, 0x1b, 0x27, 0xf3, 0x7b, 0x91, 0x71, 0x2f, 0x12, 0x7e, 0x15, 0x0e, 0xec, - 0x35, 0x6c, 0x1d, 0x6f, 0x06, 0x77, 0x60, 0x46, 0xba, 0x30, 0x42, 0x41, 0xcb, 0x9d, 0x88, 0x8e, 0xc2, 0x59, 0x22, - 0xee, 0xef, 0x75, 0x1b, 0x65, 0xac, 0xf5, 0x7a, 0x0f, 0x43, 0xaf, 0xea, 0x3e, 0x90, 0x4b, 0x7f, 0xfe, 0xe4, 0x10, - 0xfe, 0xa8, 0xfc, 0xaf, 0xbb, 0x4a, 0x57, 0x57, 0x76, 0x2f, 0xe8, 0xea, 0xbb, 0x35, 0x65, 0x5c, 0x89, 0x70, 0xa9, - 0x8f, 0x3f, 0xb4, 0x36, 0x68, 0x95, 0x0f, 0xaa, 0xae, 0xb5, 0xac, 0x5f, 0x55, 0xfb, 0xd7, 0x75, 0xfe, 0xc0, 0xba, - 0x43, 0xa5, 0xb9, 0xd6, 0xeb, 0xea, 0xcf, 0x10, 0xae, 0x55, 0x36, 0x18, 0x97, 0xf5, 0x77, 0xc9, 0x5d, 0x69, 0xa2, - 0xa8, 0x68, 0x2c, 0x58, 0x29, 0xbb, 0xca, 0x4a, 0xc9, 0x29, 0xb9, 0x3a, 0xe9, 0xdf, 0x4e, 0x12, 0x67, 0xae, 0x8e, - 0x4b, 0x12, 0xb7, 0xed, 0xb7, 0x5c, 0x47, 0xe6, 0x01, 0xc0, 0xad, 0xed, 0xae, 0xfc, 0xbc, 0xad, 0xdb, 0x07, 0x4d, - 0x6b, 0x3e, 0x96, 0x9a, 0xdd, 0xcb, 0xf0, 0x8e, 0x66, 0x97, 0x1d, 0xd7, 0x01, 0x3f, 0x4d, 0x53, 0xa5, 0x4c, 0xc8, - 0x32, 0xa7, 0xe3, 0x3a, 0xb7, 0x93, 0x24, 0xcd, 0x89, 0x1b, 0x0b, 0x31, 0x0d, 0xd4, 0xf7, 0x6f, 0x6f, 0x0e, 0x7c, - 0x9e, 0x8d, 0xf7, 0x3b, 0xad, 0x56, 0x0b, 0x2e, 0x80, 0x75, 0x9d, 0x39, 0xa3, 0x37, 0x4f, 0xf9, 0x2d, 0x71, 0x5b, - 0x4e, 0xcb, 0x69, 0x77, 0x8e, 0x9d, 0x76, 0xe7, 0xd0, 0x7f, 0x74, 0xec, 0xf6, 0x3e, 0x73, 0x9c, 0x93, 0x88, 0x8e, - 0x72, 0xf8, 0xe1, 0x38, 0x27, 0x52, 0xf1, 0x52, 0xbf, 0x1d, 0xc7, 0x1f, 0x26, 0x79, 0xb3, 0xed, 0x2c, 0xf4, 0xa3, - 0xe3, 0xc0, 0xa1, 0xd2, 0xc0, 0xf9, 0x7c, 0xd4, 0x19, 0x1d, 0x8e, 0x9e, 0x74, 0x75, 0x71, 0xf1, 0x59, 0xad, 0x3a, - 0x56, 0xff, 0x77, 0xac, 0x66, 0xb9, 0xc8, 0xf8, 0x47, 0xaa, 0x73, 0x12, 0x1d, 0x10, 0x3d, 0x1b, 0x9b, 0x76, 0xd6, - 0x47, 0x6a, 0x1f, 0x5f, 0x0f, 0x47, 0x9d, 0xaa, 0xba, 0x84, 0x71, 0xbf, 0x04, 0xf2, 0x64, 0xdf, 0x80, 0x7e, 0x62, - 0xa3, 0xa9, 0xdd, 0xdc, 0x84, 0xa8, 0xb6, 0xab, 0xe7, 0x38, 0x36, 0xf3, 0x3b, 0x81, 0x33, 0x0c, 0x46, 0x57, 0x95, - 0x10, 0xb8, 0x4e, 0x44, 0xdc, 0x57, 0xed, 0xce, 0x31, 0x6e, 0xb7, 0x1f, 0xf9, 0x8f, 0x8e, 0x87, 0x2d, 0x7c, 0xe8, - 0x1f, 0x36, 0x0f, 0xfc, 0x47, 0xf8, 0xb8, 0x79, 0x8c, 0x8f, 0x5f, 0x1c, 0x0f, 0x9b, 0x87, 0xfe, 0x21, 0x6e, 0x35, - 0x8f, 0xa1, 0xb0, 0x79, 0xdc, 0x3c, 0x9e, 0x37, 0x0f, 0x8f, 0x87, 0x2d, 0x59, 0xda, 0xf1, 0x8f, 0x8e, 0x9a, 0xed, - 0x96, 0x7f, 0x74, 0x84, 0x8f, 0xfc, 0x47, 0x8f, 0x9a, 0xed, 0x03, 0xff, 0xd1, 0xa3, 0x97, 0x47, 0xc7, 0xfe, 0x01, - 0xbc, 0x3b, 0x38, 0x18, 0x1e, 0xf8, 0xed, 0x76, 0x13, 0xfe, 0xc1, 0xc7, 0x7e, 0x47, 0xfd, 0x68, 0xb7, 0xfd, 0x83, - 0x36, 0x6e, 0x25, 0x47, 0x1d, 0xff, 0xd1, 0x13, 0x2c, 0xff, 0x95, 0xd5, 0xb0, 0xfc, 0x07, 0xba, 0xc1, 0x4f, 0xfc, - 0xce, 0x23, 0xf5, 0x4b, 0x76, 0x38, 0x3f, 0x3c, 0xfe, 0xc1, 0xdd, 0xdf, 0x3a, 0x87, 0xb6, 0x9a, 0xc3, 0xf1, 0x91, - 0x7f, 0x70, 0x80, 0x0f, 0xdb, 0xfe, 0xf1, 0x41, 0xdc, 0x3c, 0xec, 0xf8, 0x8f, 0x1e, 0x0f, 0x9b, 0x6d, 0xff, 0xf1, - 0x63, 0xdc, 0x6a, 0x1e, 0xf8, 0x1d, 0xdc, 0xf6, 0x0f, 0x0f, 0xe4, 0x8f, 0x03, 0xbf, 0x33, 0x7f, 0xfc, 0xc4, 0x7f, - 0x74, 0x14, 0x3f, 0xf2, 0x0f, 0xbf, 0x3d, 0x3c, 0xf6, 0x3b, 0x07, 0xf1, 0xc1, 0x23, 0xbf, 0xf3, 0x78, 0xfe, 0xc8, - 0x3f, 0x8c, 0x9b, 0x9d, 0x47, 0xf7, 0xb6, 0x6c, 0x77, 0x7c, 0xc0, 0x91, 0x7c, 0x0d, 0x2f, 0xb0, 0x7e, 0x01, 0x7f, - 0x63, 0xd9, 0xf6, 0xdf, 0xb1, 0x9b, 0x7c, 0xbd, 0xe9, 0x13, 0xff, 0xf8, 0xf1, 0x50, 0x55, 0x87, 0x82, 0xa6, 0xa9, - 0x01, 0x4d, 0xe6, 0x4d, 0x35, 0xac, 0xec, 0xae, 0x69, 0x3a, 0x32, 0x7f, 0xf5, 0x60, 0xf3, 0x26, 0x0c, 0xac, 0xc6, - 0xfd, 0x0f, 0xed, 0xa7, 0x5c, 0xf2, 0x93, 0xfd, 0xb1, 0x22, 0xfd, 0x71, 0xef, 0x33, 0x75, 0xbb, 0xf3, 0x67, 0x57, - 0x38, 0xdd, 0xe6, 0xf8, 0xc8, 0x3e, 0xed, 0xf8, 0xe0, 0xf4, 0x21, 0x9e, 0x8f, 0xec, 0x0f, 0xf7, 0x7c, 0xa4, 0x74, - 0xc5, 0x71, 0x7e, 0x2d, 0xd6, 0x1c, 0x1c, 0xab, 0x56, 0xf1, 0x53, 0xe1, 0x0d, 0x72, 0xf8, 0x8e, 0x58, 0xd1, 0xbd, - 0x16, 0x84, 0x53, 0xdb, 0x0f, 0xc4, 0x81, 0xc5, 0x5e, 0x0b, 0xc5, 0x63, 0x93, 0x6d, 0x08, 0x09, 0x3f, 0x8d, 0x90, - 0x6f, 0x1f, 0x82, 0x8f, 0xf0, 0x0f, 0xc7, 0x47, 0x62, 0xe3, 0xa3, 0xe6, 0xcb, 0x97, 0x9e, 0x06, 0xe9, 0x29, 0x38, - 0x97, 0xcf, 0x1e, 0x1c, 0xa2, 0x6a, 0xb8, 0xfb, 0x14, 0x8a, 0x72, 0x57, 0x45, 0xbe, 0xde, 0xfd, 0x9a, 0xb0, 0x83, - 0x3a, 0x31, 0x49, 0x5c, 0xed, 0x96, 0x99, 0x4a, 0xa9, 0xa3, 0x1f, 0x4a, 0xa1, 0xd4, 0xf1, 0x5b, 0x7e, 0xab, 0x74, - 0xe9, 0xc0, 0x29, 0x59, 0xb2, 0xe0, 0x22, 0x84, 0x2f, 0xd6, 0x26, 0x7c, 0x2c, 0xbf, 0x6d, 0x0b, 0x5f, 0x13, 0x80, - 0xa4, 0x9f, 0xa1, 0xfa, 0x90, 0x43, 0xe0, 0xba, 0xfa, 0x6e, 0x0d, 0x38, 0x85, 0xf9, 0x0d, 0x9c, 0x54, 0x35, 0x51, - 0x89, 0x09, 0x78, 0x3b, 0x5e, 0xd1, 0x88, 0x85, 0x9e, 0xeb, 0x4d, 0x33, 0x3a, 0xa2, 0x59, 0xde, 0xac, 0x1d, 0xdf, - 0x94, 0x27, 0x37, 0x91, 0x6b, 0x3e, 0x8d, 0x9a, 0xc1, 0xed, 0xd8, 0x64, 0xa0, 0xfd, 0x8d, 0xae, 0x36, 0xc0, 0xdc, - 0x02, 0x9b, 0x92, 0x0c, 0x64, 0x6d, 0xa5, 0xb4, 0xb9, 0x4a, 0x6b, 0x6b, 0xfb, 0x9d, 0x23, 0xe4, 0xc8, 0x62, 0xb8, - 0x77, 0xf8, 0x7b, 0xaf, 0x79, 0xd0, 0xfa, 0x13, 0xb2, 0x9a, 0x95, 0x1d, 0x5d, 0x68, 0x77, 0x5b, 0x5a, 0x7d, 0x53, - 0xba, 0x7e, 0xb6, 0xd6, 0x55, 0x14, 0xf1, 0xb9, 0x9a, 0xbb, 0x8b, 0xba, 0xa9, 0x8e, 0x70, 0xab, 0x1b, 0x22, 0x46, - 0x6c, 0xec, 0xd9, 0x5f, 0x0c, 0x56, 0xf7, 0x1a, 0xcb, 0x0f, 0x8d, 0xa3, 0xa2, 0xaa, 0x92, 0xa2, 0x85, 0x8c, 0xb7, - 0xb0, 0xd4, 0x49, 0x97, 0x4b, 0x2f, 0x05, 0x17, 0x39, 0xb1, 0x70, 0x0a, 0xcf, 0xa8, 0x86, 0xe4, 0x14, 0x97, 0x00, - 0x49, 0x04, 0x93, 0x54, 0xfd, 0x5f, 0x15, 0x9b, 0x1f, 0xda, 0xf1, 0xe5, 0x27, 0x61, 0x3a, 0x06, 0x2a, 0x0c, 0xd3, - 0xf1, 0x9a, 0x5b, 0x4d, 0x85, 0x8c, 0x56, 0x4a, 0xab, 0xae, 0x2a, 0xf7, 0x59, 0xfe, 0xf4, 0xee, 0xbd, 0xbe, 0x00, - 0xcd, 0x05, 0xef, 0xb4, 0x8c, 0x70, 0x54, 0x97, 0x35, 0x37, 0xc8, 0x17, 0x27, 0x13, 0x2a, 0x42, 0x95, 0xaf, 0x09, - 0xfa, 0x04, 0x9c, 0x9a, 0x75, 0xb4, 0x35, 0x4a, 0x5c, 0x29, 0xdd, 0x49, 0x44, 0xe7, 0x6c, 0xa8, 0x45, 0x3d, 0x76, - 0xf4, 0xcd, 0x01, 0x4d, 0xb9, 0x34, 0xa4, 0x8d, 0x95, 0x3f, 0x66, 0x18, 0xca, 0x8c, 0x7c, 0x92, 0x72, 0xb7, 0xf7, - 0x45, 0xf9, 0xf5, 0xd3, 0x6d, 0x8b, 0x90, 0xb0, 0xf4, 0xe3, 0x20, 0xa3, 0xc9, 0x3f, 0x91, 0x2f, 0xd8, 0x90, 0xa7, - 0x5f, 0x5c, 0xc0, 0x57, 0xe9, 0xfd, 0x38, 0xa3, 0x23, 0xf2, 0x05, 0xc8, 0xf8, 0x40, 0x5a, 0x1f, 0xc0, 0x08, 0x1b, - 0xb7, 0x93, 0x04, 0x4b, 0x8d, 0xe9, 0x01, 0x0a, 0x91, 0x02, 0xd7, 0xed, 0x1c, 0xb9, 0x8e, 0xb2, 0x89, 0xe5, 0xef, - 0x9e, 0x12, 0xa7, 0x52, 0x09, 0x70, 0xda, 0x1d, 0xff, 0x28, 0xee, 0xf8, 0x4f, 0xe6, 0x8f, 0xfd, 0xe3, 0xb8, 0xfd, - 0x78, 0xde, 0x84, 0xff, 0x3b, 0xfe, 0x93, 0xa4, 0xd9, 0xf1, 0x9f, 0xc0, 0xdf, 0x6f, 0x0f, 0xfd, 0xa3, 0xb8, 0xd9, - 0xf6, 0x8f, 0xe7, 0x07, 0xfe, 0xc1, 0xcb, 0x76, 0xc7, 0x3f, 0x70, 0xda, 0x8e, 0x6a, 0x07, 0xec, 0x5a, 0x71, 0xe7, - 0x2f, 0x56, 0x36, 0xc4, 0x86, 0x70, 0x9c, 0xca, 0x39, 0x75, 0xb1, 0x57, 0x7e, 0x63, 0x51, 0xef, 0x4f, 0xed, 0xac, - 0x7b, 0x16, 0x66, 0xf0, 0xa1, 0x9b, 0xfa, 0xde, 0xad, 0xbd, 0xc3, 0x35, 0x7e, 0xb1, 0x61, 0x08, 0xd8, 0xe1, 0x2e, - 0xb6, 0x8f, 0xde, 0xc3, 0xb9, 0x75, 0x79, 0x2f, 0xb8, 0xb9, 0x1e, 0x71, 0x3b, 0x69, 0xab, 0x8a, 0xe6, 0x0a, 0x46, - 0xc9, 0x2c, 0x98, 0xfc, 0x02, 0x83, 0x1c, 0xe4, 0xab, 0xa8, 0x58, 0x1d, 0x1f, 0x52, 0x5f, 0x33, 0x6e, 0xdd, 0x3e, - 0x40, 0xab, 0x03, 0x1b, 0x11, 0x83, 0xfb, 0x22, 0x8a, 0xc2, 0x80, 0x5e, 0x73, 0xd3, 0x56, 0x58, 0x92, 0xfc, 0x82, - 0xe6, 0x7d, 0x17, 0x8a, 0xdc, 0xc0, 0x95, 0x2e, 0x3e, 0xb7, 0xfc, 0xd8, 0x4f, 0x49, 0xd8, 0x55, 0x01, 0x96, 0x87, - 0xae, 0x60, 0xd7, 0x02, 0x7e, 0x5c, 0xb4, 0xb7, 0xb7, 0x75, 0xbf, 0x48, 0x05, 0x12, 0xe6, 0x5a, 0x7d, 0x23, 0xc4, - 0x66, 0x45, 0xae, 0x8d, 0xe8, 0xb2, 0x5f, 0x89, 0x42, 0xa4, 0xf1, 0x74, 0x4d, 0x43, 0xe1, 0x87, 0xa9, 0x4a, 0xa2, - 0xb1, 0x18, 0x16, 0x6e, 0xd3, 0x03, 0x54, 0x70, 0x11, 0x5a, 0xdf, 0x01, 0xd6, 0xfb, 0x9c, 0x8b, 0xd0, 0x9c, 0xa5, - 0xb5, 0xae, 0x0d, 0x02, 0x47, 0x6f, 0xdc, 0xe9, 0xbd, 0x79, 0x7f, 0xea, 0xa8, 0xed, 0x79, 0xb2, 0x1f, 0x77, 0x7a, - 0x27, 0xd2, 0x67, 0xa2, 0x4e, 0xe2, 0x11, 0x75, 0x12, 0xcf, 0xd1, 0xa7, 0x32, 0x21, 0x92, 0x56, 0xec, 0xab, 0x69, - 0x4b, 0x9b, 0x41, 0x79, 0x7b, 0x27, 0xb3, 0x44, 0x30, 0xb8, 0xe3, 0x7a, 0x5f, 0x1e, 0xc3, 0x83, 0x05, 0x2b, 0xf3, - 0xb0, 0xb5, 0x76, 0x78, 0x2d, 0x52, 0xe3, 0x1b, 0x1e, 0xb1, 0x84, 0x9a, 0xcc, 0x6b, 0xdd, 0x55, 0x79, 0x52, 0x60, - 0xbd, 0x76, 0x3e, 0xbb, 0x9e, 0x30, 0xe1, 0x9a, 0xf3, 0x0c, 0x1f, 0x74, 0x83, 0x13, 0x39, 0x54, 0xef, 0xaa, 0xd0, - 0xce, 0x6b, 0xf3, 0x35, 0x9f, 0xfa, 0x92, 0xea, 0xd9, 0x6b, 0x09, 0x01, 0x27, 0xe4, 0xe2, 0x83, 0x5e, 0xe9, 0x2e, - 0xb6, 0xdf, 0x15, 0x27, 0xfb, 0xf1, 0x41, 0xef, 0x2a, 0x98, 0xea, 0xfe, 0x5e, 0xf2, 0xf1, 0xe6, 0xbe, 0x12, 0x3e, - 0xee, 0xcb, 0xa3, 0x20, 0xea, 0x90, 0xb2, 0x51, 0x7e, 0x79, 0xe2, 0xf6, 0x4e, 0xb4, 0x32, 0xe0, 0xc8, 0xc0, 0xba, - 0x7b, 0xd4, 0x32, 0xa7, 0x4b, 0x12, 0x3e, 0x86, 0x0d, 0xa9, 0x9a, 0x58, 0x83, 0xd4, 0x3c, 0xee, 0x71, 0xbb, 0x77, - 0x12, 0x3a, 0x92, 0xb7, 0x48, 0xe6, 0x91, 0x07, 0xfb, 0xd0, 0x38, 0xe6, 0x13, 0xea, 0x33, 0xbe, 0x7f, 0x43, 0xaf, - 0x9b, 0xe1, 0x94, 0x55, 0xee, 0x6d, 0x50, 0x3a, 0xca, 0x21, 0xb9, 0xf1, 0x88, 0xeb, 0xb3, 0x57, 0x9d, 0xca, 0xdd, - 0x76, 0x08, 0x36, 0x8f, 0x71, 0xcd, 0x49, 0x9f, 0x9c, 0x05, 0x16, 0xef, 0x9d, 0xec, 0x87, 0x2b, 0x18, 0x91, 0xfc, - 0xbe, 0xd0, 0x8e, 0x76, 0x30, 0x6c, 0x80, 0xde, 0x5c, 0x47, 0x89, 0x03, 0xe3, 0x90, 0xd7, 0x82, 0xba, 0x70, 0x7b, - 0xff, 0xfa, 0x3f, 0xfe, 0x97, 0xf6, 0xb1, 0x9f, 0xec, 0xc7, 0x6d, 0xd3, 0xd7, 0xca, 0xaa, 0x14, 0x27, 0x70, 0xdc, - 0xb3, 0x0a, 0x0a, 0xd3, 0xdb, 0xe6, 0x38, 0x63, 0x51, 0x33, 0x0e, 0x93, 0x91, 0xdb, 0xdb, 0x8e, 0x4d, 0xfb, 0xd8, - 0x96, 0x86, 0xba, 0x5e, 0x04, 0xf4, 0xfa, 0x9b, 0x0e, 0x1e, 0x99, 0xf3, 0x2b, 0x72, 0x6b, 0xdb, 0xc7, 0x90, 0xaa, - 0xdd, 0x57, 0x3b, 0x8a, 0x94, 0xea, 0x4f, 0x84, 0x69, 0x0e, 0x98, 0xd6, 0x4e, 0x20, 0x15, 0xae, 0x53, 0x06, 0xb5, - 0xfe, 0xef, 0xff, 0xfc, 0x2f, 0xff, 0xcd, 0x3c, 0x42, 0xac, 0xea, 0x5f, 0xff, 0xfb, 0x7f, 0xfe, 0x3f, 0xff, 0xfb, - 0xbf, 0xc2, 0xa9, 0x15, 0x1d, 0xcf, 0x92, 0x4c, 0xc5, 0xa9, 0x82, 0x59, 0x8a, 0xbb, 0x38, 0x90, 0xd8, 0x39, 0x61, - 0xb9, 0x60, 0xc3, 0xfa, 0x99, 0xa4, 0x73, 0x39, 0xa0, 0xdc, 0x99, 0x1a, 0x3a, 0xb9, 0xc3, 0x8b, 0x8a, 0xa0, 0x6a, - 0x28, 0x97, 0x84, 0x5b, 0x9c, 0xec, 0x03, 0xbe, 0x1f, 0x76, 0x8c, 0xd3, 0x2f, 0x97, 0x63, 0x61, 0xc8, 0x04, 0x4a, - 0x8a, 0xaa, 0xdc, 0x81, 0xd8, 0xca, 0x02, 0x1e, 0x83, 0x8e, 0x55, 0x2c, 0x57, 0xaf, 0xd6, 0xa6, 0xfb, 0xd3, 0x2c, - 0x17, 0x6c, 0x04, 0x28, 0x57, 0x7e, 0x62, 0x19, 0xc6, 0x6e, 0x82, 0xae, 0x98, 0xdc, 0x15, 0xb2, 0x17, 0x45, 0xa0, - 0x87, 0xc7, 0x7f, 0x2a, 0xfe, 0x32, 0x01, 0x8d, 0xcc, 0xf1, 0x26, 0xe1, 0xad, 0x36, 0xcf, 0x1f, 0xb5, 0x5a, 0xd3, - 0x5b, 0xb4, 0xa8, 0x46, 0xc0, 0xdb, 0x06, 0x93, 0x74, 0x6c, 0x77, 0x28, 0xe3, 0xdf, 0xa5, 0x1b, 0xbb, 0xe5, 0x80, - 0x2f, 0xdc, 0x69, 0x15, 0xc5, 0x9f, 0x17, 0xd2, 0x93, 0xca, 0x7e, 0x81, 0x38, 0xb5, 0x76, 0x3a, 0x5f, 0x73, 0x7b, - 0x72, 0x0b, 0xab, 0x55, 0x47, 0xb5, 0x8a, 0xdb, 0xeb, 0xa7, 0x13, 0xed, 0x38, 0xbb, 0x1d, 0x21, 0x3f, 0x84, 0x98, - 0x77, 0xdc, 0xc6, 0x71, 0x67, 0x51, 0x76, 0x2f, 0x04, 0x9f, 0xd8, 0x81, 0x75, 0x1a, 0xd2, 0x21, 0x1d, 0x19, 0x67, - 0xbd, 0x7e, 0xaf, 0x82, 0xe6, 0x45, 0x7c, 0xb0, 0x61, 0x2c, 0x0d, 0x92, 0x0c, 0xa8, 0x3b, 0xad, 0xe2, 0x73, 0xd8, - 0x81, 0x8b, 0x51, 0xc2, 0x43, 0x11, 0x48, 0x82, 0xed, 0xda, 0xe1, 0xf9, 0x10, 0x78, 0x12, 0x5f, 0x58, 0xf0, 0x74, - 0x55, 0x55, 0x70, 0x9b, 0xd7, 0xcf, 0x90, 0x16, 0xbe, 0x6c, 0x6e, 0x77, 0xa5, 0xbc, 0x6e, 0xdf, 0xea, 0xa8, 0xf7, - 0xbb, 0x9a, 0xbb, 0x4a, 0x0b, 0xa4, 0x0e, 0xda, 0xfc, 0x5e, 0xc9, 0x75, 0xf5, 0xf6, 0x6b, 0xe1, 0xb9, 0x12, 0x4c, - 0x77, 0xb5, 0x96, 0x2c, 0x84, 0x5a, 0xef, 0xc8, 0xb7, 0xa5, 0xc9, 0x14, 0x4e, 0xa7, 0xb2, 0x22, 0xea, 0x9e, 0xec, - 0x2b, 0x4d, 0x17, 0xb8, 0x87, 0x4c, 0xe9, 0x50, 0x19, 0x14, 0xba, 0x92, 0xde, 0x0a, 0xea, 0x97, 0xce, 0xad, 0x80, - 0x4f, 0xc7, 0xf5, 0xfe, 0x1f, 0xe7, 0xe0, 0x1c, 0x12, 0xcf, 0x89, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xed, 0x7d, 0xdb, 0x72, 0xe3, 0x46, 0x96, 0xe0, 0xf3, + 0xfa, 0x2b, 0x20, 0x58, 0x2d, 0x23, 0x9b, 0x49, 0xf0, 0x22, 0xa9, 0x4a, 0x05, 0x32, 0xc9, 0x56, 0xa9, 0xca, 0x5d, + 0x76, 0xd7, 0xcd, 0xa5, 0xb2, 0xdd, 0x6e, 0x5a, 0x2d, 0x42, 0x40, 0x92, 0x48, 0x17, 0x08, 0xd0, 0x40, 0x52, 0x17, + 0x93, 0x98, 0xd8, 0x0f, 0xd8, 0x88, 0x8d, 0xd8, 0xa7, 0x7d, 0xd9, 0xd8, 0x79, 0xd8, 0x8f, 0xd8, 0xe7, 0xf9, 0x94, + 0xf9, 0x81, 0xdd, 0x4f, 0xd8, 0x38, 0x79, 0x01, 0x12, 0xbc, 0xa8, 0x54, 0xb6, 0x7b, 0x66, 0x1e, 0x36, 0x1c, 0x56, + 0x11, 0x89, 0xbc, 0x9c, 0x3c, 0x79, 0xf2, 0xdc, 0x33, 0xd1, 0xdf, 0x0b, 0xd3, 0x80, 0xdf, 0xcd, 0xa9, 0x15, 0xf1, + 0x59, 0x3c, 0xe8, 0xab, 0xbf, 0xd4, 0x0f, 0x07, 0xfd, 0x98, 0x25, 0x1f, 0xac, 0x8c, 0xc6, 0x84, 0x05, 0x69, 0x62, + 0x45, 0x19, 0x9d, 0x90, 0xd0, 0xe7, 0xbe, 0xc7, 0x66, 0xfe, 0x94, 0x5a, 0xad, 0x41, 0x7f, 0x46, 0xb9, 0x6f, 0x05, + 0x91, 0x9f, 0xe5, 0x94, 0x93, 0x6f, 0xdf, 0x7f, 0xd9, 0x3c, 0x19, 0xf4, 0xf3, 0x20, 0x63, 0x73, 0x6e, 0x41, 0x97, + 0x64, 0x96, 0x86, 0x8b, 0x98, 0x0e, 0x5a, 0xad, 0x9b, 0x9b, 0x1b, 0xf7, 0xa7, 0xfc, 0xb3, 0x20, 0x4d, 0x72, 0x6e, + 0xbd, 0x24, 0x37, 0x2c, 0x09, 0xd3, 0x1b, 0xcc, 0x38, 0x79, 0xe9, 0x9e, 0x47, 0x7e, 0x98, 0xde, 0xbc, 0x4b, 0x53, + 0x7e, 0x70, 0xe0, 0xc8, 0xc7, 0xbb, 0xb3, 0xf3, 0x73, 0x42, 0xc8, 0x75, 0xca, 0x42, 0xab, 0xbd, 0x5a, 0x55, 0x85, + 0x6e, 0xe2, 0x73, 0x76, 0x4d, 0x65, 0x13, 0x74, 0x70, 0x60, 0xfb, 0x61, 0x3a, 0xe7, 0x34, 0x3c, 0xe7, 0x77, 0x31, + 0x3d, 0x8f, 0x28, 0xe5, 0xb9, 0xcd, 0x12, 0xeb, 0x59, 0x1a, 0x2c, 0x66, 0x34, 0xe1, 0xee, 0x3c, 0x4b, 0x79, 0x0a, + 0x90, 0x1c, 0x1c, 0xd8, 0x19, 0x9d, 0xc7, 0x7e, 0x40, 0xe1, 0xfd, 0xd9, 0xf9, 0x79, 0xd5, 0xa2, 0xaa, 0x84, 0x73, + 0x4e, 0xce, 0xef, 0x66, 0x57, 0x69, 0xec, 0x20, 0x1c, 0x73, 0x92, 0xd0, 0x1b, 0xeb, 0x7b, 0xea, 0x7f, 0x78, 0xe5, + 0xcf, 0x7b, 0x41, 0xec, 0xe7, 0xb9, 0x75, 0xcb, 0x97, 0x62, 0x0a, 0xd9, 0x22, 0xe0, 0x69, 0xe6, 0x70, 0x4c, 0x31, + 0x43, 0x4b, 0x36, 0x71, 0x78, 0xc4, 0x72, 0xf7, 0x72, 0x3f, 0xc8, 0xf3, 0x77, 0x34, 0x5f, 0xc4, 0x7c, 0x9f, 0xec, + 0xb5, 0x31, 0xdb, 0x23, 0x24, 0xe7, 0x88, 0x47, 0x59, 0x7a, 0x63, 0x3d, 0xcf, 0xb2, 0x34, 0x73, 0xec, 0xb3, 0xf3, + 0x73, 0x59, 0xc3, 0x62, 0xb9, 0x95, 0xa4, 0xdc, 0x2a, 0xfb, 0xf3, 0xaf, 0x62, 0xea, 0x5a, 0xdf, 0xe6, 0xd4, 0x1a, + 0x2f, 0x92, 0xdc, 0x9f, 0xd0, 0xb3, 0xf3, 0xf3, 0xb1, 0x95, 0x66, 0xd6, 0x38, 0xc8, 0xf3, 0xb1, 0xc5, 0x92, 0x9c, + 0x53, 0x3f, 0x74, 0x6d, 0xd4, 0x13, 0x83, 0x05, 0x79, 0xfe, 0x9e, 0xde, 0x72, 0xc2, 0xb1, 0x78, 0xe4, 0x84, 0x16, + 0x53, 0xca, 0xad, 0xbc, 0x9c, 0x97, 0x83, 0x96, 0x31, 0xe5, 0x16, 0x27, 0xe2, 0x7d, 0xda, 0x93, 0xb8, 0xa7, 0xf2, + 0x91, 0xf7, 0xd8, 0xc4, 0x61, 0xfc, 0xe0, 0x80, 0x97, 0x78, 0x46, 0x72, 0x6a, 0x16, 0x23, 0x74, 0x4f, 0x97, 0x1d, + 0x1c, 0x50, 0x37, 0xa6, 0xc9, 0x94, 0x47, 0x84, 0x90, 0x4e, 0x8f, 0x1d, 0x1c, 0x38, 0x9c, 0xc4, 0xdc, 0x9d, 0x52, + 0xee, 0x50, 0x84, 0x70, 0xd5, 0xfa, 0xe0, 0xc0, 0x91, 0x48, 0x48, 0x89, 0x44, 0x5c, 0x0d, 0xc7, 0xc8, 0x55, 0xd8, + 0x3f, 0xbf, 0x4b, 0x02, 0xc7, 0x84, 0x1f, 0x61, 0x76, 0x70, 0x10, 0x73, 0x37, 0x87, 0x1e, 0x31, 0x47, 0xa8, 0xc8, + 0x28, 0x5f, 0x64, 0x89, 0xc5, 0x0b, 0x9e, 0x9e, 0xf3, 0x8c, 0x25, 0x53, 0x07, 0x2d, 0x75, 0x99, 0xd1, 0xb0, 0x28, + 0x24, 0xb8, 0xaf, 0x39, 0x49, 0xc8, 0x00, 0x46, 0xbc, 0xe5, 0x0e, 0xac, 0x62, 0x3a, 0xb1, 0x12, 0x42, 0xec, 0x5c, + 0xb4, 0xb5, 0x87, 0x89, 0x97, 0x34, 0x6c, 0x1b, 0x4b, 0x28, 0x71, 0xce, 0x11, 0xfe, 0x40, 0x9c, 0x04, 0xbb, 0xae, + 0xcb, 0x11, 0x19, 0x2c, 0x35, 0x56, 0x12, 0x63, 0x9e, 0xc3, 0x64, 0xd4, 0xbe, 0xf0, 0xb8, 0x9b, 0xd1, 0x70, 0x11, + 0x50, 0xc7, 0x61, 0x38, 0xc7, 0x19, 0x22, 0x03, 0xd6, 0x70, 0x52, 0x32, 0x80, 0xe5, 0x4e, 0xeb, 0x6b, 0x4d, 0xc8, + 0x5e, 0x1b, 0x29, 0x18, 0x53, 0x0d, 0x20, 0x60, 0x58, 0xc1, 0x93, 0x12, 0x62, 0x27, 0x8b, 0xd9, 0x15, 0xcd, 0xec, + 0xb2, 0x5a, 0xaf, 0x46, 0x16, 0x8b, 0x9c, 0x5a, 0x41, 0x9e, 0x5b, 0x93, 0x45, 0x12, 0x70, 0x96, 0x26, 0x96, 0xdd, + 0x48, 0x1b, 0xb6, 0x24, 0x87, 0x92, 0x1a, 0x6c, 0x54, 0x20, 0x27, 0x47, 0x8d, 0x64, 0x94, 0x35, 0x3a, 0x17, 0x18, + 0xa0, 0x44, 0x3d, 0xd5, 0x9f, 0x42, 0x00, 0xc5, 0x09, 0xcc, 0xb1, 0xc0, 0xef, 0x38, 0xcc, 0x52, 0x4c, 0x91, 0xf1, + 0x61, 0xe2, 0x6e, 0x6e, 0x14, 0xc2, 0xdd, 0x99, 0x3f, 0x77, 0x28, 0x19, 0x50, 0x41, 0x5c, 0x7e, 0x12, 0x00, 0xac, + 0xb5, 0x75, 0x1b, 0x52, 0x8f, 0xba, 0x15, 0x49, 0x21, 0x8f, 0xbb, 0x93, 0x34, 0x7b, 0xee, 0x07, 0x11, 0xb4, 0x2b, + 0x09, 0x26, 0xd4, 0xfb, 0x2d, 0xc8, 0xa8, 0xcf, 0xe9, 0xf3, 0x98, 0xc2, 0x93, 0x63, 0x8b, 0x96, 0x36, 0xc2, 0x39, + 0x79, 0xe9, 0xc6, 0x8c, 0xbf, 0x4e, 0x93, 0x80, 0xf6, 0x72, 0x83, 0xba, 0x18, 0xac, 0xfb, 0x29, 0xe7, 0x19, 0xbb, + 0x5a, 0x70, 0xea, 0xd8, 0x09, 0xd4, 0xb0, 0x71, 0x8e, 0x30, 0x73, 0x39, 0xbd, 0xe5, 0x67, 0x69, 0xc2, 0x69, 0xc2, + 0x09, 0xd5, 0x48, 0xc5, 0x89, 0xeb, 0xcf, 0xe7, 0x34, 0x09, 0xcf, 0x22, 0x16, 0x87, 0x0e, 0x43, 0x05, 0x2a, 0x70, + 0xc4, 0x09, 0xcc, 0x91, 0x0c, 0x12, 0x0f, 0xfe, 0xec, 0x9e, 0x8d, 0xc3, 0xc9, 0x40, 0x6c, 0x0a, 0x4a, 0x6c, 0xbb, + 0x37, 0x49, 0x33, 0x47, 0xcd, 0xc0, 0x4a, 0x27, 0x16, 0x87, 0x31, 0xde, 0x2d, 0x62, 0x9a, 0x23, 0xda, 0x20, 0xac, + 0x5c, 0x46, 0x85, 0xe0, 0xd7, 0x40, 0xf1, 0x05, 0x72, 0x12, 0xe4, 0x25, 0xbd, 0x6b, 0x3f, 0xb3, 0xbe, 0x57, 0x3b, + 0xea, 0x99, 0xe6, 0x66, 0x01, 0x27, 0xcf, 0x5c, 0x9e, 0x2d, 0x72, 0x4e, 0xc3, 0xf7, 0x77, 0x73, 0x9a, 0xe3, 0xa7, + 0x9c, 0x04, 0x7c, 0x18, 0x70, 0x97, 0xce, 0xe6, 0xfc, 0xee, 0x5c, 0x30, 0x46, 0xcf, 0xb6, 0x71, 0x08, 0x35, 0x33, + 0xea, 0x07, 0xc0, 0xcc, 0x14, 0xb6, 0xde, 0xa6, 0xf1, 0xdd, 0x84, 0xc5, 0xf1, 0xf9, 0x62, 0x3e, 0x4f, 0x33, 0x8e, + 0xff, 0x4a, 0x96, 0x3c, 0xad, 0x50, 0x03, 0x6b, 0xb9, 0xcc, 0x6f, 0x18, 0x0f, 0x22, 0x87, 0xa3, 0x65, 0xe0, 0xe7, + 0xd4, 0x7a, 0x9a, 0xa6, 0x31, 0xf5, 0x61, 0xd2, 0xc9, 0xf0, 0x29, 0xf7, 0x92, 0x45, 0x1c, 0xf7, 0xae, 0x32, 0xea, + 0x7f, 0xe8, 0x89, 0xd7, 0x6f, 0xae, 0x7e, 0xa2, 0x01, 0xf7, 0xc4, 0xef, 0xd3, 0x2c, 0xf3, 0xef, 0xa0, 0x22, 0x21, + 0x50, 0x6d, 0x98, 0x78, 0x5f, 0x9f, 0xbf, 0x79, 0xed, 0xca, 0x4d, 0xc2, 0x26, 0x77, 0x4e, 0x52, 0x6e, 0xbc, 0xa4, + 0xc0, 0x93, 0x2c, 0x9d, 0xad, 0x0d, 0x2d, 0xb1, 0x96, 0xf4, 0x76, 0x80, 0x40, 0x49, 0xb2, 0x27, 0xbb, 0x36, 0x21, + 0x78, 0x2d, 0x68, 0x1e, 0x5e, 0x12, 0x3d, 0xee, 0x22, 0x8e, 0x3d, 0x59, 0xec, 0x24, 0xe8, 0x7e, 0x68, 0x79, 0x76, + 0xb7, 0xa4, 0x44, 0xc0, 0x39, 0x07, 0x09, 0x03, 0x30, 0x06, 0x3e, 0x0f, 0xa2, 0x25, 0x15, 0x9d, 0x15, 0x1a, 0x62, + 0x5a, 0x14, 0xf8, 0xb4, 0xa4, 0x77, 0x0e, 0x80, 0x08, 0x46, 0x45, 0xf8, 0x6a, 0x05, 0x13, 0x46, 0xf8, 0x6f, 0x64, + 0xe9, 0xeb, 0xf9, 0x78, 0x7b, 0x6d, 0x0c, 0xfb, 0xd2, 0x93, 0xdc, 0x05, 0x07, 0x69, 0x72, 0x4d, 0x33, 0x4e, 0x33, + 0xef, 0xaf, 0x38, 0xa3, 0x93, 0x18, 0xa0, 0xd8, 0xeb, 0xe0, 0xc8, 0xcf, 0xcf, 0x22, 0x3f, 0x99, 0xd2, 0xd0, 0x3b, + 0xe5, 0x05, 0xe6, 0x9c, 0xd8, 0x13, 0x96, 0xf8, 0x31, 0xfb, 0x85, 0x86, 0xb6, 0x12, 0x07, 0xcf, 0x2d, 0x7a, 0xcb, + 0x69, 0x12, 0xe6, 0xd6, 0x8b, 0xf7, 0xaf, 0x5e, 0xaa, 0x85, 0xac, 0x49, 0x08, 0xb4, 0xcc, 0x17, 0x73, 0x9a, 0x39, + 0x08, 0x2b, 0x09, 0xf1, 0x9c, 0x09, 0xee, 0xf8, 0xca, 0x9f, 0xcb, 0x12, 0x96, 0x7f, 0x3b, 0x0f, 0x7d, 0x4e, 0xdf, + 0xd2, 0x24, 0x64, 0xc9, 0x94, 0xec, 0x75, 0x64, 0x79, 0xe4, 0xab, 0x17, 0x61, 0x59, 0x74, 0xb9, 0xff, 0x3c, 0x16, + 0x13, 0x2f, 0x1f, 0x17, 0x0e, 0x2a, 0x72, 0xee, 0x73, 0x16, 0x58, 0x7e, 0x18, 0x7e, 0x95, 0x30, 0xce, 0x04, 0x80, + 0x19, 0xac, 0x0f, 0xd0, 0x28, 0x95, 0xb2, 0x42, 0x03, 0xee, 0x20, 0xec, 0x38, 0x4a, 0x02, 0x44, 0x48, 0x2d, 0xd8, + 0xc1, 0x41, 0xc5, 0xef, 0x87, 0xd4, 0x93, 0x2f, 0xc9, 0xe8, 0x02, 0xb9, 0xf3, 0x45, 0x0e, 0x2b, 0xad, 0x87, 0x00, + 0xf1, 0x92, 0x5e, 0xe5, 0x34, 0xbb, 0xa6, 0x61, 0x49, 0x1d, 0xb9, 0x83, 0x96, 0x6b, 0x63, 0xa8, 0x7d, 0xc1, 0xc9, + 0xe8, 0xa2, 0x67, 0x32, 0x6e, 0xaa, 0x08, 0x3d, 0x4b, 0xe7, 0x34, 0xe3, 0x8c, 0xe6, 0x25, 0x2f, 0x71, 0x40, 0x8c, + 0x96, 0xfc, 0x24, 0x27, 0x7a, 0x7e, 0x73, 0x87, 0x61, 0x8a, 0x6a, 0x1c, 0x43, 0x4b, 0xda, 0xe7, 0xd7, 0x42, 0x64, + 0xe4, 0x98, 0x21, 0xcc, 0x25, 0xa4, 0x39, 0x42, 0x05, 0xc2, 0x5c, 0x83, 0x2b, 0x79, 0x91, 0x1a, 0xed, 0x0e, 0x64, + 0x35, 0xf9, 0x9b, 0x90, 0xd5, 0xc0, 0xd1, 0x7c, 0x4e, 0x0f, 0x0e, 0x1c, 0xea, 0x96, 0x54, 0x41, 0xf6, 0x3a, 0x6a, + 0x8d, 0x0c, 0x64, 0xed, 0x00, 0x1b, 0x06, 0xe6, 0x98, 0x22, 0xbc, 0x47, 0xdd, 0x24, 0x3d, 0x0d, 0x02, 0x9a, 0xe7, + 0x69, 0x76, 0x70, 0xb0, 0x27, 0xea, 0x97, 0xea, 0x04, 0xac, 0xe1, 0x9b, 0x9b, 0xa4, 0x82, 0x00, 0x55, 0x22, 0x56, + 0x09, 0x06, 0x0e, 0x82, 0x4a, 0x68, 0x1c, 0xf6, 0x50, 0x6b, 0x1e, 0x9e, 0x7d, 0x79, 0x69, 0x37, 0x38, 0x56, 0x68, + 0x98, 0x52, 0x3d, 0xf4, 0xdd, 0x33, 0x2a, 0x75, 0x2b, 0xa1, 0x79, 0x6c, 0x60, 0x46, 0x6e, 0x20, 0x37, 0xa4, 0x13, + 0x96, 0x18, 0xd3, 0xae, 0x81, 0x84, 0x39, 0xce, 0x51, 0x61, 0x2c, 0xe8, 0xd6, 0xae, 0x85, 0x52, 0x23, 0x57, 0x6e, + 0x39, 0x15, 0x8a, 0x84, 0xb1, 0x8c, 0x23, 0x7a, 0x51, 0x60, 0x81, 0x7a, 0x3d, 0x9b, 0x4c, 0x00, 0x3a, 0xe2, 0x17, + 0x3d, 0xf5, 0x9e, 0xe4, 0x12, 0x73, 0x19, 0xfd, 0x79, 0x41, 0x73, 0x2e, 0xe9, 0xd8, 0xe1, 0x38, 0xc3, 0x0c, 0x15, + 0xb0, 0xdf, 0x26, 0x6c, 0xba, 0xc8, 0x40, 0xdf, 0x81, 0xbd, 0x48, 0x93, 0xc5, 0x8c, 0xea, 0xa7, 0x6d, 0xb0, 0xbd, + 0x99, 0x83, 0x44, 0xcc, 0x81, 0xa6, 0xef, 0x27, 0x27, 0x80, 0x95, 0xa3, 0xd5, 0xea, 0x6f, 0xba, 0x93, 0x6a, 0x29, + 0x4b, 0x1d, 0x6d, 0x7d, 0x4d, 0x38, 0x52, 0x12, 0x79, 0xaf, 0x23, 0xc1, 0xe7, 0xfc, 0x82, 0xec, 0xb5, 0x4b, 0x1a, + 0x56, 0x58, 0x95, 0xe0, 0x48, 0x24, 0xbe, 0x91, 0x5d, 0x21, 0x21, 0xe0, 0x6b, 0xe4, 0xe2, 0x46, 0x1b, 0x94, 0x1a, + 0x91, 0x11, 0xa8, 0x1a, 0x6e, 0x74, 0xb1, 0x8b, 0x9c, 0x34, 0x3f, 0x70, 0xf8, 0xe6, 0xbb, 0x8a, 0x6d, 0x5c, 0xd7, + 0xd9, 0xc6, 0xda, 0x34, 0xec, 0x79, 0xd9, 0xc4, 0x2e, 0xa9, 0x4c, 0x6d, 0xf4, 0xea, 0x15, 0x66, 0x02, 0x98, 0x6a, + 0x4a, 0x46, 0x17, 0xaf, 0xfd, 0x19, 0xcd, 0x1d, 0x8a, 0xf0, 0xae, 0x0a, 0x92, 0x3c, 0xa1, 0xca, 0x85, 0x21, 0x39, + 0x73, 0x90, 0x9c, 0x0c, 0x49, 0xc5, 0xac, 0xbe, 0xe1, 0x72, 0x4c, 0x47, 0xf9, 0x45, 0xa5, 0xcf, 0x19, 0x93, 0x17, + 0x22, 0x59, 0xd1, 0xb7, 0xc6, 0x9f, 0x2c, 0x93, 0x48, 0x13, 0x7a, 0x43, 0x8e, 0xf0, 0x5e, 0x7b, 0x7d, 0x25, 0x75, + 0xad, 0x6a, 0x8e, 0xa3, 0x0b, 0x58, 0x07, 0x21, 0x31, 0x5c, 0x96, 0x8b, 0x7f, 0x6b, 0x3b, 0x0d, 0xd0, 0x76, 0x0e, + 0x84, 0xe1, 0x4e, 0x62, 0x9f, 0x3b, 0x9d, 0x56, 0x1b, 0x94, 0xd1, 0x6b, 0x0a, 0x02, 0x05, 0xa1, 0xcd, 0xa9, 0x50, + 0x77, 0x91, 0xe4, 0x11, 0x9b, 0x70, 0x27, 0xe2, 0x82, 0xa5, 0xd0, 0x38, 0xa7, 0x16, 0xaf, 0xa9, 0xc4, 0x82, 0xdd, + 0x44, 0x40, 0x6c, 0xa5, 0xfe, 0x45, 0x35, 0xa4, 0x82, 0x6d, 0x01, 0x77, 0xa8, 0xd4, 0xe9, 0x8a, 0xcb, 0xe8, 0xda, + 0x0c, 0x54, 0xc6, 0xce, 0x50, 0xf6, 0xe8, 0x29, 0x66, 0xc0, 0x0c, 0xad, 0x95, 0x79, 0x26, 0x87, 0x50, 0x85, 0xdc, + 0xe5, 0xe9, 0xcb, 0xf4, 0x86, 0x66, 0x67, 0x3e, 0x00, 0xef, 0xc9, 0xe6, 0x85, 0x14, 0x04, 0x82, 0xdf, 0xf3, 0x9e, + 0xa6, 0x97, 0x4b, 0x31, 0xf1, 0xb7, 0x59, 0x3a, 0x63, 0x39, 0x05, 0x65, 0x4d, 0xe2, 0x3f, 0x81, 0x7d, 0x26, 0x36, + 0x24, 0x08, 0x1b, 0x5a, 0xd2, 0xd7, 0xe9, 0xcb, 0x3a, 0x7d, 0x5d, 0xee, 0x3f, 0x9f, 0x6a, 0x06, 0x58, 0xdf, 0xc6, + 0x08, 0x3b, 0xca, 0xa4, 0x30, 0xe4, 0x9c, 0x1b, 0x21, 0x25, 0xe1, 0x57, 0x2b, 0x6e, 0x58, 0x6e, 0x35, 0x75, 0x91, + 0xca, 0x6d, 0x83, 0x0a, 0x3f, 0x0c, 0x41, 0xb1, 0xcb, 0xd2, 0x38, 0x36, 0x44, 0x15, 0x66, 0xbd, 0x52, 0x38, 0x5d, + 0xee, 0x3f, 0x3f, 0xbf, 0x4f, 0x3e, 0xc1, 0x7b, 0x53, 0x44, 0x69, 0x40, 0x93, 0x90, 0x66, 0x60, 0x49, 0x1a, 0xab, + 0xa5, 0xa4, 0xec, 0x59, 0x9a, 0x24, 0x34, 0xe0, 0x34, 0x04, 0x43, 0x85, 0x11, 0xee, 0x46, 0x69, 0xce, 0xcb, 0xc2, + 0x0a, 0x7a, 0x66, 0x40, 0xcf, 0xdc, 0xc0, 0x8f, 0x63, 0x47, 0x1a, 0x25, 0xb3, 0xf4, 0x9a, 0x6e, 0x81, 0xba, 0x57, + 0x03, 0xb9, 0xec, 0x86, 0x1a, 0xdd, 0x50, 0x37, 0x9f, 0xc7, 0x2c, 0xa0, 0xa5, 0xe8, 0x3a, 0x77, 0x59, 0x12, 0xd2, + 0x5b, 0xe0, 0x23, 0x68, 0x30, 0x18, 0xb4, 0x71, 0x07, 0x15, 0x12, 0xe1, 0xcb, 0x0d, 0xc4, 0xde, 0x23, 0x34, 0x81, + 0xc8, 0xc8, 0x60, 0xb9, 0x8d, 0x1f, 0x50, 0x64, 0x48, 0x4a, 0xa6, 0x8d, 0x2b, 0xc9, 0x9d, 0x11, 0x0e, 0x69, 0x4c, + 0x39, 0xd5, 0xdc, 0x1c, 0x54, 0x68, 0xb9, 0x75, 0xdf, 0x95, 0xf8, 0x2b, 0xc9, 0x49, 0xef, 0x32, 0xbd, 0xe6, 0x79, + 0x69, 0xac, 0x57, 0xcb, 0x53, 0x61, 0x7b, 0xc8, 0xe5, 0xf2, 0xf8, 0x9c, 0xfb, 0x41, 0x24, 0xad, 0x74, 0x67, 0x63, + 0x4a, 0x55, 0x1f, 0x8a, 0xb3, 0x97, 0x9b, 0xe8, 0x9d, 0x06, 0x73, 0x1b, 0x0a, 0xce, 0x15, 0x53, 0xa0, 0x60, 0xf8, + 0xc9, 0x65, 0x3b, 0xf3, 0xe3, 0xf8, 0xca, 0x0f, 0x3e, 0xd4, 0xa9, 0xbf, 0x22, 0x03, 0xb2, 0xce, 0x8d, 0x8d, 0x57, + 0x06, 0xcb, 0x32, 0xe7, 0xad, 0xb9, 0x74, 0x6d, 0xa3, 0x38, 0x7b, 0xed, 0x8a, 0xec, 0xeb, 0x0b, 0xbd, 0x93, 0xda, + 0x05, 0x44, 0x4c, 0xcd, 0xcc, 0x01, 0x2e, 0xf0, 0x51, 0x8a, 0xd3, 0xfc, 0x40, 0xd1, 0x1d, 0x98, 0x1b, 0xc5, 0x1a, + 0x20, 0x1c, 0x2d, 0x8b, 0x90, 0xe5, 0xbb, 0x31, 0xf0, 0xbb, 0x40, 0xf9, 0xcc, 0x18, 0xe1, 0xa1, 0x80, 0x96, 0x3c, + 0x4e, 0x69, 0xcd, 0x25, 0x64, 0x4a, 0x9f, 0xd0, 0x8c, 0xe6, 0x2f, 0xa0, 0xbb, 0x08, 0x7a, 0x7f, 0x23, 0x5f, 0x81, + 0x56, 0x06, 0x50, 0xe4, 0x3d, 0x53, 0x9d, 0xa8, 0x51, 0x80, 0xe2, 0xa9, 0x4c, 0x88, 0xdc, 0xac, 0x66, 0x3f, 0x2a, + 0x8d, 0x5d, 0x9a, 0xe0, 0x8a, 0xe5, 0xa6, 0xc4, 0x71, 0x9c, 0x1c, 0x4c, 0x38, 0xad, 0xda, 0x57, 0x93, 0xc8, 0x37, + 0x26, 0x91, 0xbb, 0x86, 0x9d, 0x85, 0x2a, 0x5a, 0x36, 0x9a, 0x7b, 0x7f, 0x45, 0x66, 0x25, 0x50, 0x57, 0x5d, 0xe0, + 0xcf, 0xa8, 0x64, 0xb7, 0x31, 0xe1, 0x38, 0x55, 0x36, 0x8e, 0xa2, 0x34, 0x60, 0x18, 0x55, 0x93, 0x0c, 0xc9, 0xad, + 0x51, 0xb3, 0x77, 0x33, 0x9c, 0xa2, 0x35, 0xdd, 0xbe, 0x28, 0x14, 0x8e, 0x28, 0x52, 0x6b, 0x53, 0x53, 0x8a, 0x0d, + 0xac, 0xe0, 0x8c, 0x28, 0x45, 0x58, 0xea, 0x3d, 0xeb, 0xb8, 0x29, 0xfb, 0xdd, 0x23, 0x24, 0xab, 0x50, 0x53, 0xd3, + 0x28, 0xb5, 0x6a, 0x95, 0x21, 0x1c, 0x69, 0x9d, 0x34, 0xad, 0xe6, 0x4d, 0x88, 0xad, 0x1d, 0x12, 0xf6, 0x70, 0x59, + 0xb3, 0x0a, 0x3d, 0xa3, 0x5a, 0xe1, 0x01, 0x4b, 0x4d, 0xb7, 0xa1, 0x7b, 0x1b, 0xcd, 0xd4, 0xfa, 0x31, 0x10, 0x9e, + 0x9a, 0x08, 0x37, 0x30, 0x9b, 0x49, 0xce, 0x95, 0x5d, 0x90, 0xa8, 0xde, 0xd6, 0xa1, 0x38, 0x95, 0xeb, 0xb0, 0x81, + 0xc4, 0x75, 0xd5, 0x53, 0x90, 0x20, 0xd8, 0xb0, 0x39, 0x28, 0x77, 0xa6, 0x7c, 0x70, 0x00, 0x76, 0xb6, 0x5a, 0x6d, + 0x10, 0xdd, 0x56, 0x0d, 0x14, 0xb9, 0x95, 0x5d, 0xb8, 0x5a, 0x9d, 0x72, 0xe4, 0x28, 0xdd, 0x17, 0x53, 0x34, 0xd4, + 0x1c, 0xf7, 0xf4, 0x25, 0xd4, 0x12, 0xaa, 0x68, 0x55, 0x52, 0x1a, 0x0d, 0x75, 0x9a, 0xad, 0xaf, 0x13, 0x37, 0xd8, + 0xf6, 0xd9, 0x06, 0xf7, 0x12, 0x85, 0x4a, 0x4c, 0x57, 0x53, 0x3e, 0x53, 0x5d, 0x33, 0x84, 0x90, 0x97, 0x0b, 0x3b, + 0x66, 0x6f, 0x9b, 0x69, 0x79, 0x70, 0x90, 0x1b, 0x1d, 0x5d, 0x96, 0x6c, 0xe2, 0x27, 0x07, 0x44, 0x72, 0x7e, 0x97, + 0x08, 0xdd, 0xe5, 0x27, 0x2d, 0x84, 0x36, 0x0c, 0xd3, 0x76, 0x0f, 0x0c, 0x72, 0xff, 0xc6, 0x67, 0xdc, 0x2a, 0x7b, + 0x91, 0x06, 0xb9, 0x43, 0xd1, 0x52, 0xa9, 0x1a, 0x6e, 0x46, 0x41, 0x79, 0x04, 0x9e, 0xa0, 0x55, 0x68, 0x49, 0xf7, + 0x41, 0x44, 0xc1, 0x17, 0xac, 0xb5, 0x88, 0xd2, 0x32, 0xdc, 0x53, 0x52, 0x44, 0x75, 0xbc, 0x1d, 0xf6, 0x62, 0xbd, + 0x79, 0xcd, 0x12, 0x98, 0xd3, 0x6c, 0x92, 0x66, 0x33, 0xfd, 0xae, 0x58, 0x7b, 0x56, 0x9c, 0x91, 0x4d, 0x9c, 0xad, + 0x7d, 0x2b, 0xfd, 0xbf, 0xb7, 0x66, 0x76, 0x57, 0x06, 0x7b, 0x4d, 0x94, 0x96, 0xd2, 0x57, 0xba, 0x04, 0x35, 0x65, + 0xe6, 0xa6, 0x81, 0xaf, 0xfc, 0xa9, 0x3d, 0xe9, 0x33, 0xd9, 0xeb, 0xf4, 0x4a, 0xab, 0x4f, 0x53, 0x43, 0x4f, 0xfa, + 0x36, 0x94, 0x48, 0x4d, 0x17, 0x71, 0xa8, 0x80, 0x65, 0x08, 0x53, 0x45, 0x47, 0x37, 0x2c, 0x8e, 0xab, 0xd2, 0x4f, + 0xe1, 0xeb, 0xb9, 0xe2, 0xeb, 0x99, 0xe6, 0xeb, 0xc0, 0x29, 0x80, 0xaf, 0xcb, 0xee, 0xaa, 0xe6, 0xd9, 0xc6, 0xee, + 0xcc, 0x24, 0x47, 0xcf, 0x85, 0x25, 0x0d, 0xe3, 0x2d, 0x34, 0x04, 0xa8, 0xd4, 0xbc, 0x3e, 0x38, 0xca, 0x0f, 0x03, + 0x26, 0xa0, 0xf4, 0x62, 0x52, 0xd3, 0x49, 0xf1, 0xc1, 0x41, 0x38, 0x2f, 0x68, 0x49, 0xd9, 0xa7, 0xcf, 0xc1, 0x4f, + 0x67, 0x4c, 0x07, 0x84, 0x98, 0x28, 0xfe, 0x24, 0x25, 0x4a, 0xcf, 0x8e, 0xa9, 0xd9, 0xe5, 0x7a, 0x76, 0xc0, 0xe9, + 0xab, 0xd9, 0x85, 0xf7, 0xf3, 0x7a, 0x31, 0x3d, 0x56, 0x4e, 0xaf, 0x5a, 0xef, 0xd5, 0xca, 0x59, 0x2b, 0x01, 0x17, + 0xbe, 0x32, 0x51, 0xb2, 0xb2, 0x77, 0xe0, 0x01, 0x26, 0x66, 0xa0, 0xa0, 0x90, 0x93, 0x2e, 0x45, 0xdc, 0xcb, 0x8f, + 0xb9, 0x78, 0x84, 0xa7, 0x5e, 0xb6, 0x3f, 0x4b, 0x67, 0x73, 0xd0, 0xc6, 0xd6, 0x48, 0x7a, 0x4a, 0xd5, 0x80, 0xd5, + 0xfb, 0x62, 0x4b, 0x59, 0xad, 0x8d, 0xd8, 0x8f, 0x35, 0x6a, 0x2a, 0x2d, 0xe6, 0xbd, 0x76, 0xb1, 0x28, 0x8b, 0x4a, + 0xc6, 0xb1, 0xcd, 0xad, 0x72, 0xb6, 0xee, 0x94, 0xd1, 0x2f, 0xde, 0x38, 0x4c, 0xf2, 0x61, 0x06, 0xbc, 0xce, 0x60, + 0x3f, 0x9a, 0xdc, 0xcd, 0xf5, 0x2f, 0x2a, 0xe4, 0x2c, 0x8b, 0x35, 0xf4, 0x2d, 0x8b, 0xe2, 0xb9, 0xb2, 0xb2, 0xf1, + 0xf3, 0xdd, 0xe6, 0x70, 0xf5, 0x4e, 0x59, 0x8b, 0xa3, 0x0b, 0xfc, 0x7c, 0x53, 0x77, 0x24, 0xcb, 0x59, 0x1a, 0x52, + 0xcf, 0x4e, 0xe7, 0x34, 0xb1, 0x0b, 0xf0, 0xac, 0xaa, 0xc5, 0x0f, 0xb9, 0xb3, 0x7c, 0x57, 0x77, 0xb1, 0x7a, 0xcf, + 0x0b, 0x70, 0x80, 0x7d, 0xbf, 0xe9, 0x7c, 0xfd, 0x8e, 0x66, 0xb9, 0xd0, 0x44, 0x4b, 0xa5, 0xf6, 0xfb, 0x4a, 0x2e, + 0x7d, 0xef, 0xed, 0xac, 0x5f, 0xd9, 0x20, 0x76, 0xc7, 0x7d, 0xe4, 0x1e, 0xda, 0x48, 0xb8, 0x86, 0xbf, 0x56, 0x3b, + 0xfe, 0x27, 0xed, 0x1a, 0x3e, 0x27, 0x3f, 0xd5, 0x3d, 0xc3, 0x0b, 0x4e, 0xce, 0x87, 0xe7, 0xda, 0x64, 0x4e, 0x63, + 0x16, 0xdc, 0x39, 0x76, 0xcc, 0x78, 0x13, 0xc2, 0x6f, 0x36, 0x5e, 0xca, 0x17, 0xe0, 0x55, 0x14, 0x2e, 0xed, 0x42, + 0x1b, 0x7b, 0x98, 0x72, 0x62, 0xef, 0xc7, 0x8c, 0xef, 0xdb, 0x78, 0x42, 0xc6, 0xf0, 0x63, 0x7f, 0xe9, 0xbc, 0xf2, + 0x79, 0xe4, 0x66, 0x7e, 0x12, 0xa6, 0x33, 0x07, 0x35, 0x6c, 0x1b, 0xb9, 0xb9, 0x30, 0x38, 0x9e, 0xa0, 0x62, 0x7f, + 0x8c, 0x9f, 0x73, 0x62, 0x0f, 0xed, 0xc6, 0x04, 0xbf, 0xe0, 0x64, 0xdc, 0xdf, 0x5f, 0x3e, 0xe7, 0xc5, 0x60, 0x8c, + 0x6f, 0x4b, 0xaf, 0x3d, 0xfe, 0x96, 0x38, 0x88, 0x0c, 0x6e, 0x15, 0x34, 0x67, 0xe9, 0x4c, 0x7a, 0xef, 0x6d, 0x84, + 0xdf, 0x8b, 0xd8, 0x4a, 0xc5, 0x6e, 0x54, 0x78, 0x65, 0x8f, 0xd8, 0xa9, 0xf0, 0x11, 0xd8, 0x07, 0x07, 0x46, 0x59, + 0xa9, 0x2b, 0xe0, 0x73, 0x4e, 0x6a, 0x16, 0x39, 0x7e, 0x25, 0xa2, 0x34, 0xe7, 0xdc, 0x49, 0x90, 0xee, 0xc6, 0xd1, + 0xbe, 0x68, 0xb5, 0x37, 0x93, 0x91, 0x74, 0x31, 0xb8, 0x8c, 0xd3, 0xcc, 0xe7, 0x69, 0x76, 0x81, 0x4c, 0xfd, 0x03, + 0xff, 0x85, 0x8c, 0x47, 0xd6, 0x7f, 0xfa, 0xec, 0xc7, 0xc9, 0x8f, 0xd9, 0xc5, 0x18, 0xbf, 0x25, 0xad, 0xbe, 0x33, + 0xf4, 0x9c, 0xbd, 0x66, 0x73, 0xf5, 0x63, 0x6b, 0xf4, 0x77, 0xbf, 0xf9, 0xcb, 0x69, 0xf3, 0x6f, 0x17, 0x68, 0xe5, + 0xfc, 0xd8, 0x1a, 0x8e, 0xd4, 0xd3, 0xe8, 0xef, 0x83, 0x1f, 0xf3, 0x8b, 0x3f, 0xca, 0xc2, 0x7d, 0x84, 0x5a, 0x53, + 0x3c, 0xe7, 0xa4, 0xd5, 0x6c, 0x0e, 0x5a, 0x53, 0x3c, 0xe5, 0xa4, 0x05, 0xff, 0x5e, 0x91, 0x77, 0x74, 0xfa, 0xfc, + 0x76, 0xee, 0x8c, 0x07, 0xab, 0xfd, 0xe5, 0x5f, 0x0a, 0xe8, 0x75, 0xf4, 0xf7, 0x1f, 0x7f, 0xcc, 0xed, 0x2f, 0x06, + 0xa4, 0x75, 0xd1, 0x40, 0x0e, 0x94, 0xfe, 0x91, 0x88, 0xbf, 0xce, 0xd0, 0x1b, 0xfd, 0x5d, 0x41, 0x61, 0x7f, 0xf1, + 0xe3, 0xb8, 0x3f, 0x20, 0x17, 0x2b, 0xc7, 0x5e, 0x7d, 0x81, 0x56, 0x08, 0xad, 0xf6, 0xd1, 0x18, 0xdb, 0x53, 0x1b, + 0xe1, 0x4b, 0x4e, 0x5a, 0x5f, 0xb4, 0xa6, 0xf8, 0x9a, 0x93, 0x96, 0xdd, 0x9a, 0xe2, 0x33, 0x4e, 0x5a, 0x7f, 0x77, + 0x86, 0x9e, 0x74, 0xb2, 0xad, 0x84, 0x7f, 0x63, 0x05, 0x01, 0x0e, 0x3f, 0xa3, 0xfe, 0x8a, 0x33, 0x1e, 0x53, 0xb4, + 0xdf, 0x62, 0xf8, 0x8d, 0x40, 0x93, 0xc3, 0xc1, 0x0b, 0x03, 0xc6, 0x9d, 0xb3, 0xbc, 0x84, 0xc5, 0x06, 0x9a, 0xd9, + 0xf7, 0x20, 0xb2, 0x03, 0x8e, 0x80, 0xdc, 0xe3, 0xf8, 0xda, 0x8f, 0x17, 0x34, 0xf7, 0x68, 0x81, 0x70, 0x4c, 0xde, + 0x70, 0xa7, 0x83, 0xf0, 0x4b, 0x0e, 0x3f, 0xba, 0x08, 0x9f, 0xa9, 0x20, 0x26, 0xec, 0x64, 0x49, 0x54, 0x49, 0x2a, + 0x55, 0x16, 0x1b, 0xe1, 0xf9, 0x96, 0x97, 0x3c, 0x02, 0xf7, 0x02, 0xc2, 0xfb, 0xb5, 0x90, 0x27, 0xbe, 0x21, 0x9a, + 0x24, 0xde, 0x67, 0x94, 0x7e, 0xef, 0xc7, 0x1f, 0x68, 0xe6, 0xdc, 0xe2, 0x4e, 0xf7, 0x09, 0x16, 0x5e, 0xe8, 0xbd, + 0x0e, 0xea, 0x95, 0xf1, 0xaa, 0x0f, 0x5c, 0xc6, 0x09, 0x40, 0xca, 0xd6, 0x9d, 0x31, 0xb0, 0xe2, 0x7b, 0xc9, 0x86, + 0xc7, 0x2a, 0xf3, 0x6f, 0x6c, 0x54, 0x8f, 0x8d, 0xb2, 0xe4, 0xda, 0x8f, 0x59, 0x68, 0x71, 0x3a, 0x9b, 0xc7, 0x3e, + 0xa7, 0x96, 0x9a, 0xaf, 0xe5, 0x43, 0x47, 0x76, 0xa9, 0x33, 0x2c, 0x0c, 0x8b, 0x73, 0xa1, 0x83, 0x4e, 0xb0, 0x57, + 0x1c, 0x88, 0x50, 0x29, 0xbd, 0xe3, 0x59, 0x15, 0x00, 0x5b, 0x8f, 0xf1, 0x35, 0x3b, 0xe0, 0x09, 0xbb, 0x10, 0xf2, + 0x39, 0xc7, 0x19, 0x01, 0x29, 0xda, 0x1d, 0xda, 0xfd, 0xfc, 0x7a, 0x3a, 0xb0, 0x21, 0x3e, 0x93, 0x92, 0xb7, 0xc2, + 0x31, 0x04, 0x15, 0x22, 0xd2, 0xee, 0x45, 0x7d, 0xda, 0x8b, 0x1a, 0x0d, 0xad, 0x44, 0xfb, 0x24, 0x19, 0x45, 0xb2, + 0x79, 0x80, 0x43, 0xbc, 0x20, 0xcd, 0x0e, 0x9e, 0x92, 0xb6, 0x68, 0xd2, 0x9b, 0xf6, 0x7d, 0x35, 0xcc, 0xc1, 0x81, + 0x93, 0xba, 0xb1, 0x9f, 0xf3, 0xaf, 0xc0, 0xda, 0x27, 0x53, 0x1c, 0x92, 0xd4, 0xa5, 0xb7, 0x34, 0x70, 0x7c, 0x84, + 0x43, 0xc5, 0x69, 0x50, 0x0f, 0x4d, 0x89, 0x51, 0x0d, 0xac, 0x08, 0xf2, 0x76, 0x18, 0x8e, 0x3a, 0x17, 0x84, 0x10, + 0x7b, 0xaf, 0xd9, 0xb4, 0x87, 0x29, 0x99, 0x73, 0x0f, 0x4a, 0x0c, 0x5d, 0x99, 0x4c, 0xa1, 0xa8, 0x6b, 0x14, 0x39, + 0x67, 0xdc, 0xe5, 0x34, 0xe7, 0x0e, 0x14, 0x83, 0xfd, 0x9f, 0x6b, 0xc2, 0xb6, 0xfb, 0x2d, 0xbb, 0x01, 0xa5, 0x82, + 0x38, 0x11, 0x4e, 0xc9, 0x15, 0xf2, 0xc2, 0xd1, 0xe1, 0x85, 0x29, 0x00, 0x44, 0x21, 0x0c, 0x7e, 0x35, 0x0c, 0x47, + 0x6d, 0x31, 0xf8, 0xc0, 0x1e, 0x3a, 0x29, 0xc9, 0xa5, 0x86, 0x36, 0xcc, 0xbd, 0xb7, 0x62, 0xaa, 0xc8, 0x53, 0xc0, + 0xe9, 0x15, 0x20, 0xcd, 0xae, 0xe7, 0x2c, 0xcc, 0x49, 0x34, 0x61, 0x30, 0x85, 0x05, 0x1c, 0x10, 0xa8, 0x8f, 0x53, + 0x02, 0x23, 0x56, 0xcd, 0xae, 0x3c, 0xf5, 0xfc, 0x85, 0xfd, 0xc5, 0xf0, 0x9a, 0x7b, 0x97, 0x5c, 0x0e, 0x7f, 0xcd, + 0x57, 0x2b, 0xf8, 0xf7, 0x92, 0x0f, 0x53, 0x72, 0x25, 0x8a, 0xe6, 0xaa, 0x68, 0x0a, 0x45, 0x6f, 0x3d, 0x00, 0x15, + 0xe7, 0xa5, 0x96, 0x25, 0xd7, 0xe4, 0x92, 0x08, 0xd8, 0x0f, 0x0e, 0x92, 0x51, 0xd4, 0xe8, 0x5c, 0x80, 0x8b, 0x3f, + 0xe3, 0xf9, 0xf7, 0x8c, 0x47, 0x8e, 0xdd, 0x1a, 0xd8, 0x68, 0x68, 0x5b, 0xb0, 0xb4, 0xbd, 0xac, 0x41, 0x24, 0x86, + 0xfd, 0xc6, 0x0b, 0xee, 0x2d, 0x06, 0xa4, 0x3d, 0x74, 0x98, 0x64, 0xe1, 0x01, 0xc2, 0xbe, 0x62, 0x9c, 0x6d, 0xbc, + 0x40, 0x0d, 0xca, 0x1b, 0xfa, 0x79, 0x81, 0x1a, 0x93, 0xc6, 0x25, 0xf2, 0xfc, 0xc6, 0xa4, 0xe1, 0x2c, 0x08, 0x21, + 0xcd, 0x6e, 0xd9, 0x4c, 0x8b, 0xbf, 0x08, 0x79, 0x97, 0xda, 0xdb, 0x39, 0x12, 0xdb, 0x21, 0x6b, 0x38, 0xc9, 0x88, + 0x5e, 0xac, 0x56, 0x76, 0x7f, 0x38, 0xb0, 0x51, 0xc3, 0xd1, 0x84, 0xd6, 0xd2, 0x94, 0x86, 0x10, 0x66, 0x17, 0x85, + 0x8a, 0x26, 0xbd, 0xae, 0x45, 0x8e, 0x96, 0xd5, 0x66, 0x37, 0x78, 0x00, 0x2d, 0x4a, 0x43, 0x46, 0x2a, 0xac, 0x73, + 0x98, 0xa6, 0x26, 0xe6, 0x8c, 0xb4, 0x71, 0x4a, 0xb4, 0xf3, 0x3a, 0x22, 0xbc, 0x22, 0x78, 0x9f, 0x54, 0xd5, 0xf1, + 0x28, 0xc0, 0xe1, 0x05, 0x79, 0x26, 0x0d, 0x92, 0x9e, 0x76, 0x8d, 0xd3, 0x98, 0xbc, 0x5e, 0x8b, 0xe0, 0x06, 0x10, + 0x5e, 0xb9, 0x71, 0x83, 0x45, 0x96, 0xd1, 0x84, 0xbf, 0x4e, 0x43, 0xa5, 0xa7, 0xd1, 0x18, 0x4c, 0x25, 0x08, 0xcd, + 0x62, 0x50, 0xd2, 0xba, 0x7a, 0x67, 0x2c, 0x36, 0x5e, 0x4f, 0xc9, 0x42, 0xea, 0x4f, 0x22, 0x60, 0xdb, 0x9b, 0x2a, + 0xc3, 0xd8, 0x41, 0x78, 0xa1, 0x22, 0xb9, 0x8e, 0xeb, 0xba, 0x53, 0x37, 0x80, 0xd7, 0x30, 0x40, 0x8e, 0x0a, 0xb1, + 0x8f, 0x9c, 0x9c, 0xdc, 0xb8, 0x09, 0xbd, 0x15, 0xa3, 0x3a, 0xa8, 0x92, 0xcc, 0x7a, 0x7b, 0xf5, 0xa3, 0x9e, 0x60, + 0x37, 0xb9, 0x9b, 0xa4, 0x21, 0x05, 0xf4, 0x40, 0xec, 0x5e, 0x15, 0x45, 0x7e, 0x6e, 0x86, 0xa8, 0x2a, 0xf8, 0x46, + 0xa6, 0xf7, 0x7a, 0x0a, 0x2e, 0x5f, 0xa1, 0x6c, 0x95, 0x95, 0xa5, 0x1f, 0x1c, 0x21, 0x36, 0x71, 0xa6, 0x2e, 0x84, + 0xf6, 0x04, 0x09, 0x51, 0xb0, 0xe5, 0xa6, 0x26, 0x51, 0x4d, 0xca, 0x3e, 0x2f, 0x49, 0x38, 0x4a, 0x1b, 0x0d, 0xe1, + 0x86, 0x5e, 0x48, 0x92, 0x98, 0x22, 0x7c, 0x59, 0xee, 0x2d, 0x5d, 0xef, 0x3b, 0x52, 0x1f, 0xc9, 0xb9, 0xac, 0xbb, + 0x73, 0x1b, 0x90, 0x26, 0x01, 0x9e, 0x42, 0xee, 0x4c, 0x10, 0x3e, 0x25, 0x2d, 0x67, 0xe4, 0x0e, 0xff, 0x74, 0x81, + 0x86, 0x8e, 0xfb, 0x47, 0xd4, 0x92, 0x8c, 0xe3, 0x12, 0xf5, 0x7c, 0x39, 0xc4, 0x52, 0x84, 0x30, 0x3b, 0x58, 0x78, + 0x12, 0xbd, 0x0c, 0x27, 0xfe, 0x8c, 0x7a, 0xa7, 0xb0, 0xc7, 0x35, 0xdd, 0x7c, 0x87, 0x81, 0x8e, 0xbc, 0x53, 0xc5, + 0x49, 0x5c, 0x7b, 0xf8, 0x15, 0x2f, 0x9f, 0x86, 0xf6, 0xf0, 0x97, 0xea, 0xe9, 0x4f, 0xf6, 0xf0, 0x4b, 0xee, 0xfd, + 0x52, 0x28, 0x67, 0x77, 0x6d, 0x88, 0x47, 0x7a, 0x88, 0x42, 0x2e, 0x8c, 0x81, 0xb9, 0x05, 0xda, 0xf4, 0x73, 0x4c, + 0x51, 0xc1, 0x26, 0x25, 0x2b, 0xca, 0x5d, 0xee, 0x4f, 0x01, 0xa5, 0xc6, 0x0a, 0xe4, 0x66, 0x64, 0xbf, 0x9a, 0x30, + 0x10, 0x8a, 0xa6, 0x56, 0x40, 0xe5, 0x74, 0xd0, 0x46, 0xcb, 0x5a, 0x5d, 0xa1, 0x31, 0xd5, 0x23, 0xe9, 0x25, 0x97, + 0xbe, 0x24, 0xed, 0xde, 0x65, 0x7f, 0xda, 0xbb, 0x6c, 0x34, 0x50, 0xae, 0x09, 0x6b, 0x31, 0xba, 0xbc, 0xc0, 0xdf, + 0x82, 0x4f, 0xcf, 0xa4, 0x24, 0x5c, 0x9b, 0x5e, 0x57, 0x4d, 0xaf, 0xd1, 0xc8, 0x0a, 0xd4, 0x33, 0x9a, 0x4e, 0x65, + 0xd3, 0xa2, 0x90, 0x38, 0x59, 0x27, 0xb4, 0x13, 0x24, 0x4a, 0x20, 0x1d, 0x8a, 0x10, 0xf2, 0x9c, 0xa3, 0xad, 0xbd, + 0x42, 0x9f, 0xd0, 0x5c, 0xec, 0x58, 0x60, 0x9e, 0x52, 0x46, 0x38, 0x80, 0x05, 0x68, 0x5a, 0x3a, 0x82, 0x27, 0x78, + 0xd1, 0xe8, 0x08, 0x22, 0x6f, 0x76, 0x7a, 0xf5, 0xbe, 0x1e, 0x57, 0x7d, 0xe1, 0x45, 0x83, 0x4c, 0x4a, 0x2c, 0x15, + 0x59, 0xa3, 0x51, 0xd4, 0xa3, 0x9d, 0x7a, 0xdf, 0xd6, 0xe2, 0x0f, 0xb7, 0xeb, 0x69, 0x19, 0x5a, 0xbe, 0x56, 0x12, + 0x95, 0xb9, 0x2c, 0x49, 0x68, 0x06, 0x32, 0x94, 0x70, 0xcc, 0x8a, 0xa2, 0x94, 0xeb, 0x6f, 0x40, 0x88, 0x62, 0x4a, + 0x12, 0xe0, 0x3b, 0xc2, 0xec, 0xc2, 0x19, 0x4e, 0x71, 0x24, 0xb8, 0x06, 0x21, 0xe4, 0x4c, 0x27, 0xb4, 0x70, 0xc1, + 0x81, 0x7c, 0xc2, 0x0c, 0x89, 0x94, 0x13, 0xea, 0x5e, 0xee, 0x9f, 0xa5, 0xf7, 0x9a, 0x64, 0x23, 0x76, 0xe1, 0x89, + 0x6a, 0xb1, 0xe2, 0x5b, 0x01, 0x79, 0xef, 0x70, 0x54, 0x06, 0x47, 0x5c, 0xc1, 0xfe, 0x9e, 0xb1, 0x8c, 0x0a, 0x0d, + 0x7c, 0x5f, 0x9b, 0x7d, 0x7e, 0x5d, 0x7d, 0xf4, 0x4d, 0xe7, 0x0d, 0x20, 0x32, 0x00, 0xdf, 0x4e, 0x46, 0x36, 0xaa, + 0x5d, 0xee, 0x9f, 0xbe, 0xd9, 0x66, 0x02, 0xaf, 0x56, 0xca, 0xf8, 0xf5, 0x41, 0xb3, 0xc1, 0x41, 0x05, 0xa9, 0xaf, + 0x7e, 0x78, 0x8e, 0x2f, 0x14, 0xa4, 0xc0, 0x49, 0x80, 0x8a, 0x2e, 0xf7, 0x4f, 0xdf, 0x3b, 0x89, 0x70, 0x2d, 0x21, + 0x6c, 0x4e, 0xdb, 0x49, 0x89, 0x13, 0x11, 0x8a, 0xe4, 0xdc, 0x4b, 0xc6, 0x95, 0x1a, 0xe2, 0xdb, 0x8b, 0xc4, 0x4b, + 0xb0, 0x1f, 0x46, 0xec, 0x82, 0xf8, 0x0a, 0x03, 0xc4, 0x47, 0xd8, 0xaf, 0x99, 0x65, 0x04, 0x16, 0x40, 0x8c, 0x75, + 0x0e, 0x2b, 0xe1, 0x4a, 0xc5, 0x0f, 0x61, 0x5f, 0x8c, 0xca, 0x0b, 0x29, 0x3a, 0x7e, 0xda, 0xc8, 0x4b, 0xab, 0xac, + 0xd1, 0xef, 0xc0, 0x72, 0xd2, 0x0f, 0xaf, 0x55, 0xd7, 0x65, 0xc1, 0x33, 0x9d, 0x40, 0x76, 0xb9, 0x7f, 0xfa, 0x4a, + 0xe5, 0x90, 0xcd, 0x7d, 0xcd, 0xed, 0x37, 0x2c, 0xcc, 0xd3, 0x57, 0x6e, 0xf5, 0x56, 0x54, 0xbe, 0xdc, 0x3f, 0xfd, + 0x76, 0x5b, 0x35, 0x28, 0x2f, 0x16, 0x95, 0x89, 0x2f, 0xe0, 0x5b, 0xd2, 0xd8, 0x5b, 0x2a, 0xd1, 0xe0, 0xb1, 0x02, + 0x0b, 0x71, 0xe4, 0xe5, 0x45, 0xe9, 0x19, 0x79, 0x86, 0x33, 0x22, 0xa2, 0x40, 0xf5, 0x55, 0x53, 0x4a, 0x1e, 0x4b, + 0x93, 0xf3, 0x20, 0x9d, 0xd3, 0x1d, 0xa1, 0xa1, 0x5b, 0xe4, 0xb2, 0x19, 0x24, 0xcf, 0x08, 0xd0, 0x19, 0xde, 0x6b, + 0xa3, 0x5e, 0x5d, 0x78, 0x65, 0x82, 0x48, 0xd3, 0x9a, 0x64, 0xc1, 0x11, 0x69, 0x63, 0x9f, 0xb4, 0x71, 0x40, 0xf2, + 0x51, 0x5b, 0x8a, 0x87, 0x5e, 0x50, 0xf6, 0x2b, 0x85, 0x0c, 0xe4, 0x85, 0x05, 0x72, 0xb7, 0x4a, 0xf1, 0x1b, 0xf6, + 0x02, 0xe1, 0x7a, 0x14, 0x12, 0x3d, 0x14, 0x64, 0xf1, 0xd4, 0x49, 0x71, 0x2a, 0x3a, 0x3e, 0x67, 0x57, 0x31, 0xa4, + 0x96, 0xc0, 0xac, 0x30, 0x47, 0x5e, 0x59, 0xb5, 0xa3, 0xaa, 0x06, 0xae, 0x58, 0xa7, 0x14, 0x07, 0x2e, 0x30, 0x6e, + 0x1c, 0xa8, 0x4c, 0x9c, 0x7c, 0xb3, 0xc9, 0xa3, 0x83, 0x03, 0x47, 0x36, 0xfa, 0x8e, 0x3b, 0xa9, 0x7e, 0x5f, 0x05, + 0xee, 0xbe, 0x93, 0xbc, 0x22, 0x44, 0x02, 0xfe, 0x46, 0xc3, 0xbf, 0x28, 0x20, 0x0a, 0xed, 0x04, 0x75, 0x0c, 0x6a, + 0xe0, 0x85, 0xa6, 0x57, 0x9f, 0x7e, 0xa3, 0x51, 0x06, 0x69, 0xeb, 0xd8, 0xba, 0xc5, 0x59, 0x71, 0xed, 0x94, 0xc9, + 0x3f, 0xed, 0x8d, 0x8c, 0x29, 0x0d, 0x02, 0x62, 0x26, 0xcd, 0x32, 0x3d, 0x19, 0x63, 0x4b, 0x30, 0xa8, 0xf7, 0x95, + 0x4a, 0x5b, 0xc0, 0x22, 0xbf, 0x4a, 0x55, 0xd2, 0xec, 0xac, 0x8b, 0x3c, 0x5d, 0x09, 0x82, 0x52, 0x50, 0xa9, 0x51, + 0x28, 0xf2, 0x7e, 0xba, 0x99, 0x75, 0x89, 0x73, 0xa4, 0x7c, 0x5c, 0x02, 0x0a, 0x81, 0xac, 0x6e, 0x89, 0x94, 0x17, + 0x64, 0xbe, 0x9b, 0xe4, 0x4f, 0x0d, 0x92, 0x7f, 0x4a, 0xa8, 0x41, 0xfe, 0xd2, 0xc3, 0xe1, 0xa6, 0xca, 0xb5, 0x90, + 0xeb, 0x57, 0x67, 0x73, 0x02, 0x3e, 0xb4, 0x3a, 0x46, 0x6b, 0x51, 0xc5, 0x1d, 0x0c, 0xc5, 0xdc, 0x21, 0xc2, 0x0b, + 0x89, 0x75, 0x08, 0xd8, 0xa9, 0x62, 0x6a, 0x30, 0xf4, 0x36, 0x97, 0x9e, 0xc9, 0x01, 0x4f, 0xbf, 0xbd, 0x3f, 0x1c, + 0x7a, 0x36, 0xdf, 0xdc, 0xb9, 0x46, 0xf6, 0x27, 0xcc, 0xda, 0xd8, 0xb8, 0xf5, 0x5c, 0x50, 0x18, 0xbf, 0x0c, 0x63, + 0xd7, 0x99, 0xcf, 0xda, 0x26, 0xd4, 0xf2, 0x0f, 0xa0, 0xed, 0x74, 0x44, 0x0d, 0x6a, 0x74, 0x0b, 0xfc, 0x48, 0xe6, + 0xa0, 0xfa, 0xd9, 0x0e, 0xf6, 0x71, 0x2a, 0x2a, 0xd0, 0x24, 0xdc, 0xfe, 0xfa, 0x69, 0xa1, 0xc8, 0x44, 0x82, 0x86, + 0x96, 0xc0, 0xff, 0x24, 0xc9, 0x03, 0xdd, 0x08, 0xb9, 0x00, 0x08, 0x9a, 0x0b, 0x3c, 0x55, 0x08, 0xb3, 0xed, 0xca, + 0xf9, 0xfe, 0x62, 0x8f, 0x90, 0x79, 0xe5, 0x7c, 0x7c, 0x57, 0xe5, 0x5e, 0x01, 0x59, 0x20, 0x0f, 0x8c, 0xc7, 0xb2, + 0x40, 0x46, 0x2f, 0xcf, 0x74, 0x75, 0x61, 0x40, 0xba, 0x95, 0xbe, 0x6d, 0x44, 0x36, 0x85, 0x57, 0x4e, 0xbe, 0xd7, + 0x68, 0x58, 0x7b, 0xbb, 0x0f, 0x6f, 0x5f, 0x71, 0x01, 0x23, 0x3c, 0xbf, 0x17, 0xb5, 0x75, 0xbf, 0xc5, 0x87, 0xf5, + 0x04, 0x96, 0xb5, 0x45, 0x71, 0x59, 0x92, 0xd3, 0x8c, 0x3f, 0xa5, 0x93, 0x34, 0x83, 0x90, 0x45, 0x89, 0x13, 0x54, + 0xec, 0x1b, 0x6e, 0x3b, 0x31, 0x3f, 0x23, 0x4e, 0xb0, 0x36, 0x41, 0xf1, 0xeb, 0x83, 0x88, 0x59, 0x5f, 0xae, 0xb7, + 0x9a, 0x1f, 0x1c, 0xbc, 0xaf, 0xd0, 0xa4, 0xa0, 0x14, 0x50, 0x18, 0x4c, 0x4b, 0xaa, 0x34, 0x2a, 0x90, 0xbb, 0xef, + 0x94, 0x2e, 0x00, 0xcd, 0x30, 0x4c, 0xde, 0xf3, 0x82, 0xf0, 0x62, 0xba, 0xce, 0xe2, 0x95, 0x6b, 0x82, 0x99, 0x66, + 0x0b, 0x70, 0x78, 0x30, 0xb4, 0xa5, 0xaf, 0x28, 0xaf, 0xd2, 0x61, 0x4b, 0x18, 0xce, 0x00, 0x59, 0x8e, 0x30, 0x42, + 0x0c, 0x0a, 0xdc, 0x6a, 0x94, 0x7c, 0x00, 0xbd, 0x32, 0xc2, 0xb9, 0x1b, 0x41, 0x02, 0x6c, 0x6d, 0xcb, 0x22, 0x84, + 0x65, 0x5e, 0x8e, 0x91, 0x49, 0x70, 0xfa, 0x62, 0x9b, 0x47, 0x59, 0x13, 0x35, 0x15, 0x52, 0x07, 0x6a, 0x64, 0xa8, + 0x6c, 0xe0, 0x5e, 0x3b, 0x4c, 0x29, 0x6e, 0x3a, 0x6c, 0x06, 0x0c, 0xf8, 0x27, 0xee, 0xc8, 0x58, 0x14, 0xc8, 0x8c, + 0xd4, 0x5d, 0x38, 0xb5, 0xa1, 0x7b, 0xa9, 0x68, 0x86, 0x15, 0xe2, 0x22, 0x13, 0x4d, 0xa9, 0x08, 0xeb, 0x9d, 0x55, + 0xbc, 0x74, 0x5f, 0xe6, 0x50, 0x73, 0xcd, 0x05, 0xab, 0x3c, 0x12, 0x63, 0xfa, 0xfb, 0x32, 0x2d, 0xba, 0xac, 0x04, + 0x6a, 0x18, 0xbd, 0xb1, 0x5e, 0x8b, 0x35, 0xa0, 0x05, 0xd0, 0xd7, 0xf2, 0x9c, 0x1b, 0x2b, 0xaa, 0x7d, 0xd8, 0x62, + 0x4c, 0x43, 0xea, 0xbf, 0x83, 0x4c, 0x97, 0xf5, 0x3d, 0xff, 0x42, 0xc8, 0x42, 0x86, 0xf3, 0x1a, 0x63, 0xcf, 0x04, + 0x63, 0x47, 0xa0, 0xa7, 0xe9, 0xd4, 0xef, 0xa1, 0x4a, 0x78, 0x61, 0x4a, 0xca, 0x29, 0x12, 0xfb, 0xb6, 0x0c, 0x96, + 0x1b, 0xbf, 0xd7, 0x56, 0xc3, 0x63, 0x04, 0x92, 0x80, 0xb0, 0xe2, 0xec, 0x19, 0xc2, 0x79, 0xa3, 0xd1, 0xcb, 0xfb, + 0xb4, 0x72, 0x91, 0x54, 0x30, 0x32, 0x88, 0xe7, 0x02, 0xc1, 0xd7, 0x64, 0x28, 0x44, 0xfc, 0x75, 0x6e, 0x76, 0x0e, + 0xae, 0xf6, 0xd3, 0x77, 0x8e, 0xc9, 0xd5, 0xcc, 0xba, 0x65, 0xcc, 0x14, 0xe6, 0xe3, 0x54, 0xf1, 0x96, 0xb7, 0xf7, + 0xe7, 0x77, 0x00, 0xdc, 0x7b, 0x1d, 0x0c, 0xb9, 0x68, 0xa8, 0xc7, 0x25, 0x4b, 0x28, 0x77, 0x5f, 0x0f, 0x55, 0x69, + 0x89, 0xe6, 0x60, 0x3d, 0x5e, 0x99, 0xb2, 0x9c, 0xe4, 0x45, 0x91, 0xd3, 0x2a, 0xba, 0xbf, 0x96, 0x7f, 0x29, 0x84, + 0xcb, 0xa6, 0xb3, 0xfd, 0x6c, 0x4e, 0x38, 0x36, 0x08, 0xf5, 0xed, 0xae, 0xd0, 0x47, 0x05, 0x26, 0xec, 0x6b, 0x25, + 0x14, 0x7f, 0xd9, 0x26, 0x14, 0x71, 0xa6, 0xb6, 0xbc, 0x10, 0x88, 0x9d, 0x07, 0x08, 0x44, 0xe5, 0x64, 0xd7, 0x32, + 0x11, 0xd4, 0x91, 0x9a, 0x4c, 0xac, 0x2f, 0x29, 0xc9, 0x30, 0x53, 0xab, 0x31, 0xe8, 0xae, 0x56, 0x6c, 0xd4, 0x06, + 0x27, 0x92, 0x6d, 0xc3, 0xcf, 0x8e, 0xfc, 0x69, 0x70, 0x62, 0xe9, 0x04, 0x76, 0x58, 0x69, 0xb2, 0x20, 0x17, 0x52, + 0x9c, 0x1d, 0x91, 0x93, 0x25, 0x68, 0x5a, 0x51, 0x90, 0x22, 0x70, 0xc2, 0xca, 0x28, 0x13, 0x40, 0x2c, 0x64, 0x85, + 0x32, 0x20, 0x9d, 0xad, 0xc9, 0x7f, 0xda, 0xbc, 0xfc, 0xb8, 0x26, 0x5a, 0x93, 0x2b, 0x52, 0x7d, 0xa8, 0xa5, 0x1b, + 0x28, 0x08, 0x94, 0x7e, 0xb8, 0x27, 0x4c, 0xd0, 0x4a, 0x94, 0x23, 0x53, 0x0e, 0xe1, 0x36, 0xb8, 0xd0, 0xf6, 0xde, + 0xcb, 0x00, 0xef, 0x16, 0x69, 0x82, 0x53, 0x83, 0xae, 0x5f, 0x10, 0x5e, 0x63, 0x25, 0x11, 0x51, 0x96, 0x12, 0x0e, + 0x04, 0x99, 0x72, 0x92, 0x8d, 0xda, 0x17, 0xa0, 0x80, 0xf6, 0xfc, 0x7e, 0x56, 0x99, 0xc0, 0x7e, 0xa3, 0x81, 0x02, + 0x3d, 0x6a, 0x34, 0x62, 0x0d, 0xff, 0x02, 0x53, 0xec, 0x4b, 0xc3, 0xe4, 0xec, 0xe0, 0xc0, 0x09, 0xaa, 0x71, 0x47, + 0xfe, 0x05, 0xc2, 0xe9, 0x6a, 0xe5, 0x08, 0xb0, 0x02, 0xb4, 0x5a, 0x05, 0x26, 0x58, 0xe2, 0x35, 0x34, 0x9b, 0x0f, + 0x39, 0x99, 0x0b, 0x01, 0x38, 0x07, 0x08, 0x1b, 0xc4, 0x09, 0x94, 0x73, 0x2f, 0x00, 0x67, 0x54, 0x23, 0x1b, 0xf9, + 0x8d, 0xce, 0x85, 0xc1, 0xb8, 0x46, 0xfe, 0x05, 0x09, 0x8a, 0xf4, 0xe0, 0x60, 0x2f, 0x57, 0x22, 0xf2, 0x27, 0x10, + 0x65, 0x3f, 0x09, 0xc9, 0x22, 0x3b, 0x34, 0x57, 0x63, 0xdd, 0x19, 0x50, 0x52, 0x94, 0x5a, 0x56, 0x5d, 0xaf, 0x96, + 0x04, 0x51, 0x56, 0xc2, 0x2a, 0x16, 0x3c, 0x04, 0xcb, 0xbe, 0x24, 0xf3, 0xaf, 0x78, 0x99, 0x64, 0xfd, 0xcb, 0xd6, + 0xd4, 0x6a, 0xd7, 0x75, 0xfd, 0x6c, 0x2a, 0x22, 0x19, 0x3a, 0x0a, 0x2b, 0x88, 0xff, 0x50, 0x81, 0x69, 0x0c, 0x3c, + 0x2a, 0xc7, 0xba, 0x20, 0x12, 0x7c, 0xad, 0xda, 0xe8, 0xd3, 0x24, 0x3f, 0x6f, 0xf5, 0x32, 0xa8, 0x0d, 0xf7, 0x6b, + 0x21, 0x39, 0x52, 0x90, 0x48, 0xf2, 0x58, 0xc3, 0xd9, 0x0e, 0x5c, 0xfc, 0xcc, 0xd7, 0x70, 0xb6, 0x1b, 0xb7, 0x1a, + 0x53, 0x5f, 0xee, 0x82, 0xcf, 0xe0, 0x0d, 0x12, 0xd0, 0xaa, 0xc0, 0x80, 0xf2, 0x78, 0x5d, 0xf7, 0x92, 0xac, 0x14, + 0x84, 0x29, 0x27, 0x0e, 0xab, 0x6f, 0x80, 0x4a, 0x1b, 0x35, 0x0c, 0x5f, 0xe6, 0xcd, 0x91, 0xe1, 0x12, 0xa8, 0x67, + 0xae, 0x00, 0x39, 0x29, 0x5f, 0xfb, 0xfc, 0xe0, 0x00, 0x6c, 0x03, 0x50, 0xe2, 0xdc, 0xc0, 0x9f, 0xf3, 0x45, 0x06, + 0xaa, 0x54, 0xae, 0x7f, 0x43, 0x31, 0x9c, 0x03, 0x11, 0x65, 0xf0, 0x03, 0x0a, 0xe6, 0x7e, 0x9e, 0xb3, 0x6b, 0x59, + 0xa6, 0x7e, 0xe3, 0x94, 0x68, 0x52, 0xce, 0xa5, 0x4e, 0x98, 0xa1, 0x5e, 0xa6, 0xe8, 0xb4, 0x8e, 0xb6, 0xe7, 0xd7, + 0x34, 0xe1, 0x2f, 0x59, 0xce, 0x69, 0x02, 0xd3, 0xaf, 0x28, 0x0e, 0x66, 0x94, 0x23, 0xd8, 0xb0, 0xb5, 0x56, 0x7e, + 0x18, 0xde, 0xdb, 0x84, 0xd7, 0x75, 0xa0, 0xc8, 0x4f, 0xc2, 0x58, 0x0e, 0x62, 0xa6, 0x33, 0xea, 0x14, 0xce, 0xb2, + 0xa6, 0x99, 0x4e, 0x53, 0x29, 0x1b, 0x82, 0xbb, 0x3b, 0x8c, 0x68, 0x49, 0xa0, 0xa5, 0xe7, 0xbd, 0x5a, 0x0b, 0x04, + 0xbc, 0x77, 0x2c, 0x82, 0x39, 0x13, 0xcc, 0x0d, 0x8e, 0xea, 0xd6, 0xe1, 0xd4, 0x74, 0xf3, 0xdd, 0xd6, 0x43, 0x6d, + 0xdb, 0x84, 0x83, 0xa0, 0x93, 0x47, 0xbb, 0x2d, 0xab, 0x57, 0x5a, 0x72, 0x68, 0x69, 0xc1, 0x1e, 0xca, 0x98, 0xd1, + 0x52, 0x93, 0x17, 0xd2, 0x5b, 0x71, 0xc6, 0xc9, 0x4f, 0x70, 0x6a, 0xe8, 0x05, 0x9f, 0xc5, 0x6b, 0x87, 0x63, 0x7a, + 0xb3, 0x52, 0xfb, 0x9f, 0x71, 0xe7, 0x35, 0x7e, 0x0a, 0x61, 0xdd, 0xaf, 0xab, 0xea, 0x9b, 0xe1, 0xdc, 0xaf, 0x2b, + 0x04, 0x7d, 0xed, 0x6d, 0xd4, 0x33, 0xc2, 0xb8, 0x5d, 0xf7, 0xc4, 0x6d, 0xdb, 0x5a, 0x5b, 0xfa, 0x5e, 0x06, 0x91, + 0x64, 0xa2, 0xa5, 0xd8, 0x0f, 0xb8, 0x4a, 0x53, 0x83, 0x74, 0xb9, 0xba, 0x85, 0x44, 0x55, 0x82, 0xa1, 0xd4, 0xe1, + 0x77, 0x2d, 0x8f, 0x92, 0x31, 0x99, 0xb4, 0x33, 0xde, 0xfa, 0x19, 0xdf, 0x87, 0x5d, 0x96, 0x6e, 0x9c, 0xc4, 0x8b, + 0x08, 0x78, 0xd0, 0x1e, 0x36, 0x84, 0x61, 0x6c, 0xe7, 0xf2, 0x24, 0x90, 0xd9, 0x3f, 0x49, 0xb5, 0xee, 0x56, 0xb7, + 0x32, 0xbe, 0x05, 0xfb, 0x1f, 0xe1, 0x48, 0x1f, 0x8f, 0xa3, 0x8a, 0x03, 0x53, 0x6f, 0x59, 0x94, 0x4e, 0x81, 0x54, + 0x2a, 0x6f, 0x09, 0xc2, 0x69, 0x21, 0xc2, 0xdb, 0xdf, 0xe0, 0x1f, 0x14, 0x4b, 0xbc, 0x2e, 0x39, 0xce, 0xf3, 0x87, + 0x72, 0x44, 0x09, 0x7e, 0x19, 0xbd, 0x07, 0x3a, 0x16, 0x14, 0x5a, 0x68, 0x2a, 0x7a, 0x96, 0xaa, 0x89, 0xec, 0xcc, + 0x4a, 0xc5, 0xb4, 0xcc, 0xa8, 0x11, 0xc3, 0x6c, 0x49, 0xe3, 0xd4, 0x56, 0x36, 0x2f, 0x77, 0x55, 0x6d, 0x5c, 0xb4, + 0x03, 0x8b, 0x55, 0x60, 0x71, 0xb5, 0x72, 0xea, 0xa8, 0x26, 0xcc, 0x88, 0x63, 0x20, 0xcc, 0x8c, 0x84, 0x8a, 0x9a, + 0x66, 0x2d, 0xdb, 0x38, 0x68, 0x3d, 0x9f, 0x48, 0xeb, 0xe6, 0x35, 0x38, 0x4c, 0x17, 0x82, 0x6c, 0x6e, 0xfa, 0x14, + 0xb0, 0x9c, 0x5d, 0x39, 0x90, 0x81, 0xa1, 0xef, 0xcb, 0x4c, 0xd9, 0x2a, 0xa5, 0x75, 0x0b, 0x7e, 0xd1, 0x3d, 0xb9, + 0xb2, 0x0a, 0x75, 0x9b, 0xef, 0x8d, 0x5c, 0xa3, 0x67, 0xe9, 0xae, 0x5c, 0xa3, 0x9a, 0xb6, 0xbb, 0xd7, 0x46, 0xf7, + 0x67, 0xa5, 0xca, 0xb1, 0xb6, 0x57, 0xf9, 0x15, 0xc3, 0x75, 0x80, 0x36, 0x25, 0x9a, 0x35, 0x57, 0x39, 0x2b, 0x8a, + 0xeb, 0xf2, 0x2c, 0x81, 0x48, 0xdd, 0xb9, 0x96, 0xf4, 0xaf, 0xac, 0x46, 0x71, 0x20, 0xd7, 0xf9, 0x86, 0x4c, 0xe3, + 0xf4, 0xca, 0x8f, 0xdf, 0xc3, 0x78, 0xd5, 0xcb, 0x17, 0x77, 0x61, 0xe6, 0x73, 0xaa, 0xb8, 0x4b, 0x05, 0xc3, 0x37, + 0x06, 0x0c, 0xdf, 0x48, 0x3e, 0x5d, 0xb5, 0xc7, 0xcb, 0x97, 0x65, 0x07, 0xde, 0x75, 0xa1, 0x59, 0xc6, 0x84, 0x6f, + 0x1f, 0x63, 0x9d, 0x85, 0x4d, 0x4a, 0x16, 0x36, 0xe1, 0xce, 0x7a, 0x57, 0x8e, 0xf3, 0xc3, 0xf6, 0x5e, 0x36, 0x39, + 0xdb, 0x0f, 0xd5, 0xc6, 0xff, 0xc1, 0xbb, 0xb7, 0x8d, 0xc1, 0xe5, 0x0e, 0xdd, 0x43, 0x91, 0xac, 0x22, 0x41, 0x7e, + 0x01, 0x49, 0x07, 0x9c, 0x0c, 0x8c, 0x23, 0x07, 0x95, 0x9c, 0xd2, 0x79, 0x40, 0x4e, 0xb0, 0xc8, 0x79, 0x3a, 0x53, + 0x7d, 0xe6, 0xea, 0x9c, 0x91, 0x78, 0x09, 0xae, 0x68, 0x11, 0x6b, 0xf7, 0xea, 0x27, 0xb9, 0x96, 0x1f, 0x58, 0x12, + 0x7a, 0x39, 0x56, 0x52, 0x24, 0xf7, 0xb2, 0x82, 0xe8, 0x5c, 0xe3, 0xcd, 0x77, 0x78, 0xc2, 0x12, 0x96, 0x47, 0x34, + 0x73, 0x52, 0xb4, 0xdc, 0x35, 0x58, 0x0a, 0x01, 0x19, 0x39, 0x18, 0xfe, 0x5b, 0x75, 0xe4, 0xcf, 0x85, 0xde, 0xc0, + 0x0f, 0x34, 0xa3, 0x3c, 0x4a, 0x43, 0x48, 0x4b, 0x71, 0xc3, 0xf2, 0x48, 0xd3, 0xc1, 0xc1, 0x9e, 0x63, 0x0b, 0xb7, + 0x04, 0x1c, 0xfe, 0x36, 0xdf, 0xa0, 0xe1, 0x12, 0x4e, 0xe7, 0x54, 0x43, 0x53, 0xb4, 0xa4, 0xeb, 0x07, 0x59, 0xb8, + 0xfb, 0x81, 0xde, 0xe1, 0x04, 0x15, 0x85, 0x27, 0xa1, 0xb6, 0x27, 0x8c, 0xc6, 0xa1, 0x8d, 0x3f, 0xd0, 0x3b, 0xaf, + 0x3c, 0x2f, 0x2e, 0x8e, 0x37, 0x8b, 0x05, 0xb4, 0xd3, 0x9b, 0xc4, 0xc6, 0xd5, 0x20, 0xde, 0xb2, 0xc0, 0x69, 0xc6, + 0xa6, 0x40, 0x9c, 0x7f, 0xa1, 0x77, 0x9e, 0xec, 0x8f, 0x19, 0xa7, 0xf5, 0xd0, 0x52, 0xa3, 0xde, 0x35, 0x8a, 0xcd, + 0x65, 0x50, 0x06, 0xc5, 0x48, 0xb4, 0xbd, 0x20, 0xb5, 0x7a, 0x95, 0x79, 0x88, 0x50, 0xf1, 0xd0, 0xa9, 0xe0, 0xaf, + 0x4d, 0xd1, 0xc6, 0x6b, 0x99, 0xaf, 0x6b, 0x8d, 0x28, 0x34, 0xa8, 0x32, 0x3d, 0x66, 0x4e, 0xa2, 0x77, 0x9d, 0x3a, + 0x82, 0x60, 0x38, 0xc2, 0xbe, 0xe6, 0xaa, 0x53, 0xef, 0x6f, 0x32, 0x21, 0xa4, 0x8a, 0x24, 0xbd, 0xaa, 0xda, 0x59, + 0x33, 0x07, 0xf0, 0x0e, 0x09, 0x2d, 0xbe, 0x38, 0x90, 0x59, 0xe8, 0x6c, 0xd1, 0xbf, 0x70, 0xe2, 0x2c, 0xf5, 0x14, + 0xbc, 0xc4, 0xc4, 0x22, 0x2f, 0x80, 0x0a, 0x15, 0x7d, 0xc9, 0x04, 0x40, 0x38, 0xc3, 0xbe, 0x21, 0x35, 0x33, 0x21, + 0x35, 0x5d, 0x03, 0xe3, 0x3b, 0xa4, 0x24, 0x15, 0xc8, 0x10, 0x4a, 0xa4, 0x10, 0x7a, 0x6a, 0x71, 0x15, 0x09, 0x99, + 0x0b, 0x5a, 0x9e, 0x9f, 0x93, 0x6b, 0x9e, 0xd5, 0xc0, 0x72, 0x44, 0x3f, 0xa8, 0xf0, 0x60, 0x4a, 0x54, 0x56, 0x28, + 0xca, 0x63, 0xd9, 0x3a, 0xbd, 0xd5, 0x49, 0x5d, 0x3d, 0x2d, 0xa2, 0x51, 0xe2, 0x44, 0x68, 0x99, 0x38, 0x11, 0xce, + 0x20, 0x1d, 0x31, 0x2d, 0x4a, 0xf8, 0xa9, 0xb9, 0x1a, 0xb5, 0x64, 0xe5, 0xed, 0x67, 0xfc, 0x40, 0x99, 0xe7, 0x90, + 0xa2, 0x89, 0x13, 0xcd, 0x53, 0x12, 0x47, 0x1c, 0xb6, 0x33, 0x96, 0xed, 0x1b, 0x95, 0xa0, 0xa3, 0x00, 0xfb, 0x0b, + 0x77, 0x96, 0xc6, 0x2c, 0xcc, 0xd3, 0xdc, 0xea, 0xcc, 0x9f, 0x0a, 0xf6, 0x55, 0x39, 0xa4, 0x4e, 0x4e, 0xd6, 0x24, + 0xce, 0xfd, 0xa9, 0x96, 0x3f, 0x2f, 0x68, 0x76, 0x77, 0x4e, 0x21, 0xd5, 0x39, 0x85, 0xd3, 0xbe, 0xd5, 0x32, 0x54, + 0x69, 0xea, 0xc3, 0x4c, 0x28, 0x2b, 0x45, 0xfd, 0x14, 0xe0, 0xfa, 0x19, 0xc1, 0x42, 0x44, 0x1b, 0x0d, 0x47, 0x8c, + 0xdc, 0x2d, 0x74, 0xe7, 0xe9, 0x49, 0xda, 0x63, 0xe0, 0x5f, 0xab, 0x30, 0xad, 0x82, 0x05, 0x38, 0x35, 0x4f, 0xa4, + 0x8e, 0xf2, 0x8b, 0x75, 0xaf, 0x0c, 0x14, 0x41, 0xf8, 0x2e, 0xdb, 0x3d, 0xd5, 0x6d, 0x49, 0xb3, 0xbb, 0xa7, 0x5a, + 0x0b, 0xfa, 0x89, 0x84, 0x1f, 0xac, 0xc6, 0x29, 0x8f, 0x2f, 0xb3, 0xa2, 0x40, 0x05, 0x80, 0xf7, 0xe7, 0x9e, 0xe3, + 0xfc, 0x59, 0xa5, 0x0c, 0xba, 0x10, 0x8b, 0x3d, 0x8f, 0x53, 0xcd, 0xc4, 0xab, 0xf1, 0xff, 0xbc, 0x31, 0xfe, 0x9f, + 0x8d, 0x33, 0xa7, 0x60, 0x1a, 0x4d, 0x13, 0x1a, 0x6a, 0xd6, 0x89, 0x24, 0x01, 0x0a, 0xbd, 0x2d, 0xe1, 0xe4, 0xc3, + 0xd8, 0x03, 0x8d, 0x6b, 0x39, 0x49, 0x13, 0xde, 0x9c, 0xf8, 0x33, 0x16, 0xdf, 0x79, 0x0b, 0xd6, 0x9c, 0xa5, 0x49, + 0x9a, 0xcf, 0xfd, 0x80, 0xe2, 0xfc, 0x2e, 0xe7, 0x74, 0xd6, 0x5c, 0x30, 0xfc, 0x82, 0xc6, 0xd7, 0x94, 0xb3, 0xc0, + 0xc7, 0xf6, 0x69, 0xc6, 0xfc, 0xd8, 0x7a, 0xed, 0x67, 0x59, 0x7a, 0x63, 0xe3, 0x77, 0xe9, 0x55, 0xca, 0x53, 0xfc, + 0xe6, 0xf6, 0x6e, 0x4a, 0x13, 0xfc, 0xed, 0xd5, 0x22, 0xe1, 0x0b, 0x9c, 0xfb, 0x49, 0xde, 0xcc, 0x69, 0xc6, 0x26, + 0xbd, 0x20, 0x8d, 0xd3, 0xac, 0x09, 0x19, 0xdb, 0x33, 0xea, 0xc5, 0x6c, 0x1a, 0x71, 0x2b, 0xf4, 0xb3, 0x0f, 0xbd, + 0x66, 0x73, 0x9e, 0xb1, 0x99, 0x9f, 0xdd, 0x35, 0x45, 0x0d, 0xef, 0xf3, 0xf6, 0xa1, 0xff, 0x64, 0x72, 0xd4, 0xe3, + 0x99, 0x9f, 0xe4, 0x0c, 0x96, 0xc9, 0xf3, 0xe3, 0xd8, 0x3a, 0x3c, 0x6e, 0xcf, 0xf2, 0x3d, 0x19, 0xc8, 0xf3, 0x13, + 0x5e, 0x8c, 0xf1, 0x5b, 0x80, 0xdb, 0xbd, 0xe2, 0x09, 0xbe, 0x5a, 0x70, 0x9e, 0x26, 0xcb, 0x60, 0x91, 0xe5, 0x69, + 0xe6, 0xcd, 0x53, 0x96, 0x70, 0x9a, 0xf5, 0xae, 0xd2, 0x2c, 0xa4, 0x59, 0x33, 0xf3, 0x43, 0xb6, 0xc8, 0xbd, 0xa3, + 0xf9, 0x6d, 0x0f, 0x34, 0x8b, 0x69, 0x96, 0x2e, 0x92, 0x50, 0x8d, 0xc5, 0x92, 0x88, 0x66, 0x8c, 0x9b, 0x2f, 0xc4, + 0x25, 0x26, 0x5e, 0xcc, 0x12, 0xea, 0x67, 0xcd, 0x29, 0x34, 0x06, 0xb3, 0xa8, 0x1d, 0xd2, 0x29, 0xce, 0xa6, 0x57, + 0xbe, 0xd3, 0xe9, 0x3e, 0xc6, 0xfa, 0x7f, 0xf7, 0x18, 0x59, 0xed, 0xed, 0xc5, 0x9d, 0x76, 0xfb, 0x0f, 0xa8, 0xb7, + 0x36, 0x8a, 0x00, 0xc8, 0xeb, 0xcc, 0x6f, 0xad, 0x3c, 0x85, 0x8c, 0xb6, 0x6d, 0x2d, 0x7b, 0x73, 0x3f, 0x84, 0x7c, + 0x60, 0xaf, 0x3b, 0xbf, 0x2d, 0x60, 0x76, 0x9e, 0x4c, 0x31, 0x55, 0x93, 0x54, 0x4f, 0xcb, 0x5f, 0x0b, 0xf1, 0xc9, + 0x76, 0x88, 0xbb, 0x1a, 0xe2, 0x0a, 0xeb, 0xcd, 0x70, 0x91, 0x89, 0xd8, 0xaa, 0xd7, 0xc9, 0x25, 0x20, 0x51, 0x7a, + 0x4d, 0x33, 0x0d, 0x87, 0x78, 0xf8, 0xd5, 0x60, 0x74, 0xb7, 0x83, 0x71, 0xf2, 0x31, 0x30, 0xb2, 0x24, 0x5c, 0xd6, + 0xd7, 0xb5, 0x93, 0xd1, 0x59, 0x2f, 0xa2, 0x40, 0x4f, 0x5e, 0x17, 0x7e, 0xdf, 0xb0, 0x90, 0x47, 0xf2, 0xa7, 0x20, + 0xe7, 0x1b, 0xf9, 0xee, 0xb8, 0xdd, 0x96, 0xcf, 0x39, 0xfb, 0x85, 0x7a, 0x1d, 0x17, 0x2a, 0x14, 0x63, 0xfc, 0x43, + 0x79, 0x96, 0xb7, 0xce, 0x3d, 0xf1, 0x9f, 0xcd, 0x43, 0xbe, 0x46, 0x8a, 0x62, 0x75, 0x24, 0x1a, 0x67, 0x5a, 0x56, + 0x4a, 0xe1, 0x03, 0x6e, 0x3b, 0xc1, 0x1d, 0x09, 0x1b, 0x94, 0x87, 0x38, 0xd9, 0xf0, 0xcf, 0x32, 0xef, 0xc2, 0x83, + 0x48, 0x87, 0x91, 0x6a, 0x98, 0xf6, 0xb2, 0x01, 0x69, 0xf7, 0xb2, 0x66, 0x13, 0x39, 0x29, 0x49, 0x46, 0x99, 0x4a, + 0xce, 0x73, 0xd8, 0x30, 0x15, 0xc6, 0x76, 0x8e, 0xbc, 0x14, 0x4e, 0x9a, 0xae, 0x56, 0x55, 0x18, 0x80, 0x89, 0xd3, + 0x1a, 0x3f, 0x70, 0x55, 0x01, 0xe7, 0x06, 0x27, 0x4f, 0xf5, 0xd5, 0x2e, 0x89, 0xe6, 0x15, 0x71, 0x1a, 0x08, 0xcc, + 0xb9, 0x73, 0x9f, 0x47, 0xe0, 0xa5, 0x28, 0xc5, 0x4f, 0x95, 0xc2, 0x64, 0xb7, 0x6c, 0x34, 0x4c, 0xca, 0xfc, 0x36, + 0xc8, 0xe3, 0x4b, 0x0a, 0xe8, 0xe5, 0x8e, 0x13, 0x61, 0x31, 0x95, 0xfd, 0x7f, 0xcb, 0x0d, 0x49, 0x9d, 0xb8, 0x2c, + 0x09, 0xe2, 0x45, 0x48, 0x73, 0xd1, 0x43, 0x25, 0xce, 0xff, 0x6a, 0xd6, 0x12, 0x4d, 0xa0, 0x77, 0x91, 0xcd, 0x03, + 0x15, 0xe1, 0x06, 0x95, 0xf2, 0xb9, 0x29, 0x9e, 0xab, 0xb6, 0xfa, 0x52, 0x09, 0x36, 0x71, 0xa0, 0xa5, 0xbb, 0x48, + 0xd8, 0xcf, 0x0b, 0x7a, 0xc9, 0x42, 0xe3, 0xdc, 0x2e, 0x4d, 0x82, 0x34, 0xa4, 0xdf, 0xbe, 0xfb, 0x0a, 0xb2, 0xdd, + 0xd3, 0x04, 0x48, 0x2c, 0x91, 0xfe, 0x2e, 0x9c, 0x93, 0xc4, 0x0d, 0xe9, 0x35, 0x0b, 0xe8, 0x70, 0xbc, 0xbf, 0xdc, + 0x5a, 0x51, 0xbe, 0x46, 0x45, 0x6b, 0x2c, 0x92, 0xfe, 0x04, 0x94, 0xe3, 0xfd, 0xe5, 0x1d, 0x2f, 0x5a, 0xfb, 0xcb, + 0xc4, 0x0d, 0xd3, 0x99, 0xcf, 0x12, 0xf8, 0x9d, 0x17, 0xfb, 0x4b, 0x06, 0x3f, 0x78, 0x31, 0x2e, 0xaa, 0x44, 0xd1, + 0x12, 0x22, 0x63, 0x0a, 0x0a, 0x77, 0x1d, 0xe4, 0xfe, 0x94, 0xb2, 0x44, 0x14, 0xdd, 0xd7, 0x33, 0xd5, 0xbd, 0x02, + 0x92, 0xbf, 0x22, 0xd2, 0x60, 0xd6, 0xe6, 0xf2, 0xf5, 0x43, 0xcd, 0x65, 0x9a, 0x70, 0x26, 0xd2, 0xe2, 0x75, 0x38, + 0x27, 0xf2, 0xf3, 0xcb, 0x40, 0x9e, 0x43, 0xcd, 0xab, 0x53, 0x17, 0xbe, 0x40, 0xac, 0xb4, 0x80, 0x69, 0x26, 0x8c, + 0x7d, 0xba, 0xfb, 0xa0, 0x64, 0x72, 0x9f, 0xf1, 0x57, 0x52, 0x55, 0x9e, 0x2e, 0xb2, 0x00, 0x62, 0xbd, 0x4a, 0xa5, + 0xd8, 0xf4, 0x8a, 0xd9, 0x42, 0x7f, 0xb3, 0x31, 0x37, 0x92, 0x6c, 0x39, 0x66, 0xe6, 0x9d, 0x1d, 0x54, 0xc4, 0x13, + 0xe5, 0x59, 0x18, 0xa5, 0x3f, 0xe8, 0x29, 0x81, 0x42, 0x14, 0x8a, 0x7c, 0x51, 0x27, 0x23, 0x83, 0xac, 0xc2, 0x39, + 0x21, 0x84, 0xb9, 0x2c, 0x14, 0x81, 0x3c, 0x50, 0x2c, 0x9a, 0x1d, 0x88, 0x0c, 0xb1, 0xb0, 0xd2, 0xf0, 0x98, 0xc2, + 0xf3, 0x6a, 0xf5, 0x57, 0xee, 0xc8, 0xba, 0xd2, 0xa9, 0x02, 0x3a, 0x18, 0xc3, 0xf2, 0xa5, 0x97, 0xe1, 0xb2, 0x4b, + 0x0f, 0x2a, 0x15, 0xbd, 0x54, 0xa0, 0x4f, 0x22, 0x8b, 0x68, 0x74, 0x9e, 0x4a, 0x15, 0x21, 0x45, 0xd8, 0x7c, 0x5d, + 0x1e, 0xe0, 0xaf, 0xe1, 0xbb, 0xbd, 0xb6, 0x2c, 0xd2, 0x9e, 0x4a, 0xd7, 0x4b, 0xf3, 0x34, 0xe3, 0x8e, 0x13, 0x61, + 0x1f, 0x91, 0x41, 0x24, 0xa8, 0xb6, 0xef, 0x8b, 0x7f, 0x86, 0xcd, 0x8e, 0xd7, 0x29, 0x3d, 0x21, 0xb5, 0x73, 0xd5, + 0x32, 0xcf, 0x4c, 0x9d, 0xcd, 0x05, 0x70, 0x71, 0xf9, 0x5b, 0xce, 0xa7, 0x7a, 0x2e, 0xa7, 0x85, 0x15, 0xe7, 0x92, + 0x52, 0xdf, 0xa9, 0x01, 0x21, 0xe2, 0x6e, 0x3b, 0x86, 0x42, 0x45, 0x35, 0xef, 0x72, 0x17, 0x8f, 0xa5, 0xb6, 0x73, + 0x69, 0x90, 0xf1, 0x98, 0x69, 0x7f, 0x5d, 0x9d, 0xc0, 0x0a, 0x85, 0x11, 0x83, 0x05, 0x6c, 0xab, 0x26, 0x61, 0xb9, + 0x23, 0xc9, 0x56, 0x2a, 0x75, 0xe5, 0x23, 0x95, 0xba, 0xd6, 0xf6, 0x2a, 0x22, 0xeb, 0x71, 0x1b, 0x60, 0xe0, 0x01, + 0xc8, 0xb9, 0x9e, 0x02, 0x30, 0x93, 0x09, 0x15, 0x17, 0xd3, 0x48, 0xd6, 0x82, 0x97, 0x52, 0x8d, 0xf7, 0xec, 0xb7, + 0x6f, 0xce, 0xdf, 0xdb, 0x18, 0xee, 0x33, 0xa3, 0x59, 0xee, 0x2d, 0x6d, 0x95, 0x4c, 0xd8, 0x84, 0xc0, 0xb4, 0xed, + 0xd9, 0xfe, 0x1c, 0xce, 0x66, 0x0b, 0xee, 0xd9, 0xba, 0x6d, 0xde, 0xdc, 0xdc, 0x34, 0xe1, 0xe8, 0x58, 0x73, 0x91, + 0xc5, 0x92, 0xaf, 0x84, 0x76, 0x51, 0x20, 0x97, 0x47, 0x34, 0x29, 0x6f, 0x3c, 0x4a, 0x63, 0xea, 0xc6, 0xe9, 0x54, + 0x1e, 0x7b, 0x5d, 0xf7, 0x43, 0xc4, 0xe3, 0xbe, 0xb8, 0xc9, 0x6b, 0xd0, 0xe7, 0xf2, 0x0e, 0x35, 0x9e, 0xc1, 0xcf, + 0x01, 0x44, 0xa9, 0xfa, 0x2d, 0x1e, 0x89, 0x87, 0x73, 0xd8, 0x36, 0xe2, 0x69, 0x7f, 0xb9, 0x41, 0x64, 0x43, 0xe8, + 0x22, 0x1a, 0xc8, 0xa9, 0xe5, 0xa2, 0xd6, 0xd8, 0x8b, 0xc7, 0xe3, 0xa2, 0xdf, 0x82, 0xbe, 0x5a, 0xba, 0xdf, 0xab, + 0x34, 0xbc, 0xd3, 0xed, 0x4b, 0xc2, 0x83, 0x1b, 0x9d, 0x12, 0x32, 0x80, 0x2e, 0x60, 0xdc, 0x70, 0x20, 0x70, 0xa6, + 0x78, 0xe5, 0xa8, 0x7a, 0x28, 0x2e, 0x2c, 0xe0, 0x8c, 0x05, 0x94, 0x00, 0x5d, 0x42, 0xe7, 0x61, 0xd9, 0x40, 0x6c, + 0x6b, 0x59, 0xb4, 0x0b, 0x40, 0x59, 0xb1, 0xda, 0x2e, 0xd2, 0x9f, 0x5d, 0x91, 0x85, 0x86, 0x38, 0x34, 0x81, 0x1f, + 0x23, 0xf8, 0x57, 0x00, 0xde, 0x6f, 0x49, 0x34, 0x8d, 0xcd, 0xdb, 0x65, 0xe4, 0xbd, 0x0f, 0x25, 0x32, 0x47, 0x09, + 0xc7, 0x6f, 0x39, 0xfe, 0x30, 0x16, 0x55, 0xb5, 0x3a, 0x00, 0x7a, 0x2a, 0xa8, 0x4d, 0x6d, 0xad, 0xf7, 0x05, 0x69, + 0x1c, 0xfb, 0xf3, 0x9c, 0x7a, 0xfa, 0x87, 0xd2, 0x0c, 0x40, 0xc1, 0xd8, 0x54, 0xc5, 0x54, 0x82, 0xd3, 0x19, 0x28, + 0x6c, 0x9b, 0x7a, 0xe2, 0xb5, 0x9f, 0x39, 0xcd, 0x66, 0xd0, 0xbc, 0x9a, 0xa2, 0x82, 0x47, 0x4b, 0x53, 0xaf, 0x78, + 0xd4, 0x6e, 0xf7, 0x20, 0x1b, 0xb5, 0xe9, 0xc7, 0x6c, 0x9a, 0x78, 0x31, 0x9d, 0xf0, 0x82, 0xc3, 0x31, 0xc1, 0xa5, + 0x56, 0xe4, 0xdc, 0xee, 0x71, 0x46, 0x67, 0x96, 0x0b, 0x7f, 0xef, 0x1f, 0xb8, 0xe0, 0xa1, 0x97, 0xf0, 0xa8, 0x29, + 0xb2, 0x9e, 0xe1, 0xcc, 0x06, 0x8f, 0x6a, 0xcf, 0x4b, 0x63, 0xa0, 0x80, 0x82, 0x92, 0x5b, 0xf0, 0xcc, 0xe2, 0x11, + 0xe6, 0x99, 0x59, 0x2f, 0x41, 0xcb, 0x8d, 0x19, 0x6c, 0xea, 0x5a, 0x87, 0xa8, 0xc8, 0x85, 0x69, 0xb2, 0x59, 0x59, + 0x2b, 0xac, 0xf5, 0xa7, 0x0d, 0xf4, 0x19, 0xaa, 0x75, 0x21, 0x5d, 0xfb, 0x4b, 0xd9, 0xe2, 0x21, 0xc8, 0xac, 0x29, + 0xfd, 0xd8, 0x6c, 0x81, 0x0a, 0x96, 0xcc, 0x17, 0x7c, 0x24, 0xc2, 0x0a, 0x19, 0x1c, 0x50, 0xb9, 0xc0, 0x46, 0x09, + 0xe0, 0xe0, 0x62, 0x29, 0x81, 0x09, 0xfc, 0x38, 0x70, 0x00, 0x22, 0xab, 0x69, 0x9d, 0x64, 0x74, 0x86, 0x7a, 0x33, + 0x96, 0x34, 0xe5, 0xbb, 0x63, 0x43, 0x31, 0x74, 0x1f, 0xc3, 0x53, 0xe1, 0x8a, 0xde, 0xb0, 0xc8, 0x1e, 0xde, 0x82, + 0xcb, 0xf1, 0x45, 0x51, 0xf4, 0x32, 0xee, 0x8c, 0x5e, 0x39, 0xe8, 0x02, 0x7f, 0x65, 0xdc, 0x8f, 0x63, 0xeb, 0x9d, + 0x64, 0xe3, 0x2e, 0xda, 0x51, 0xc5, 0xdc, 0x0b, 0xa2, 0xda, 0x57, 0x04, 0x2a, 0xbe, 0x70, 0x6c, 0x9a, 0xcf, 0x9b, + 0x92, 0xe5, 0x35, 0x05, 0xc9, 0xda, 0xd0, 0x14, 0x29, 0x5f, 0x39, 0xa5, 0x4b, 0xc1, 0xcd, 0xd4, 0x21, 0x19, 0xe9, + 0xce, 0xb9, 0x28, 0x0f, 0x55, 0xa9, 0x67, 0xf3, 0x18, 0x15, 0xaa, 0xb1, 0x9b, 0xf1, 0x69, 0x9d, 0x35, 0x82, 0x72, + 0x51, 0x5e, 0x22, 0xe8, 0xc7, 0x31, 0x0c, 0x38, 0xd6, 0x1a, 0x89, 0x79, 0xeb, 0xca, 0x88, 0x5f, 0x38, 0xa8, 0x50, + 0xfb, 0xf4, 0xa9, 0x50, 0xea, 0x8d, 0x9b, 0x0b, 0xf7, 0xb8, 0x0e, 0xd7, 0x49, 0x11, 0xcd, 0x20, 0xe1, 0xa0, 0x96, + 0x98, 0xde, 0xab, 0x58, 0x9b, 0x34, 0x09, 0x2c, 0x31, 0x21, 0x62, 0x67, 0x49, 0x68, 0x5b, 0x7f, 0x0a, 0x62, 0x16, + 0x7c, 0x20, 0xf6, 0xfe, 0xd2, 0x41, 0x9b, 0xe7, 0x4e, 0x05, 0x57, 0xd0, 0x7c, 0x1e, 0xd5, 0x43, 0x19, 0x99, 0x6b, + 0xb0, 0x70, 0x79, 0x31, 0x91, 0x3d, 0x00, 0xbd, 0xa9, 0xdf, 0x92, 0xe3, 0x0c, 0xc6, 0xc5, 0x65, 0x75, 0xdf, 0x58, + 0x05, 0x05, 0xa0, 0x59, 0x96, 0x5b, 0x82, 0xa8, 0x88, 0xfd, 0x51, 0x4a, 0xb3, 0x2d, 0xc9, 0xd4, 0x00, 0x4e, 0xae, + 0xf8, 0x9b, 0x6d, 0xfd, 0xa9, 0x2c, 0xa3, 0xa5, 0x4f, 0x49, 0x24, 0xc5, 0x10, 0x1b, 0xc6, 0x02, 0x47, 0x82, 0x1b, + 0x52, 0xee, 0xb3, 0x58, 0x36, 0xe9, 0x69, 0x17, 0xc8, 0xda, 0x8c, 0x56, 0xab, 0xbc, 0x3e, 0x17, 0x56, 0xc7, 0xa0, + 0x98, 0x59, 0xbf, 0x55, 0xc1, 0x2d, 0x66, 0x26, 0xf6, 0xa4, 0x19, 0x9c, 0xad, 0x66, 0x28, 0xdf, 0x59, 0x7f, 0x0a, + 0xc4, 0xb1, 0x2d, 0x00, 0x30, 0x55, 0x00, 0x42, 0xda, 0x80, 0x3c, 0x96, 0xe4, 0xf8, 0x24, 0x75, 0xb9, 0x9f, 0x4d, + 0x29, 0x5f, 0x43, 0xac, 0x2f, 0xb3, 0x84, 0x7b, 0x3a, 0x45, 0x60, 0x03, 0xda, 0xa0, 0x0e, 0x2d, 0x28, 0xd1, 0xc5, + 0x10, 0xf4, 0x60, 0xb2, 0x55, 0x9d, 0x8e, 0x10, 0xc8, 0x5b, 0xb1, 0x38, 0x52, 0xc2, 0xa4, 0x42, 0xc2, 0x48, 0x4e, + 0x60, 0x89, 0xb1, 0x04, 0x88, 0x85, 0x6d, 0x0d, 0x25, 0xe4, 0x34, 0x94, 0x30, 0x93, 0x4c, 0xb4, 0x4a, 0x8b, 0x7e, + 0x4b, 0xd6, 0x96, 0x22, 0x40, 0x56, 0x02, 0x24, 0x88, 0x7d, 0x5a, 0xe1, 0x00, 0x32, 0xcb, 0x4d, 0x3c, 0x84, 0xec, + 0xba, 0x24, 0x36, 0x71, 0x80, 0x6d, 0xd0, 0x8f, 0xfd, 0x2b, 0x1a, 0x0f, 0xf6, 0x97, 0xd9, 0x6a, 0xd5, 0x2e, 0xfa, + 0x2d, 0xf9, 0x68, 0xf5, 0x05, 0xdf, 0x90, 0x97, 0x8e, 0x8a, 0x25, 0x86, 0x53, 0xa1, 0x90, 0x6f, 0xab, 0x13, 0xcd, + 0x3c, 0xd5, 0x41, 0x61, 0x5b, 0x22, 0xc5, 0x45, 0x54, 0x2a, 0xf5, 0xa8, 0xc2, 0xb6, 0x58, 0xb8, 0x59, 0x96, 0x73, + 0x3a, 0x87, 0xd2, 0x68, 0xb5, 0xea, 0x14, 0xb6, 0x35, 0x63, 0x09, 0x3c, 0x65, 0xab, 0x95, 0x38, 0x70, 0x39, 0x63, + 0x89, 0xd3, 0x06, 0xb2, 0xb5, 0xad, 0x99, 0x7f, 0x2b, 0x26, 0xac, 0xdf, 0xf8, 0xb7, 0x4e, 0x47, 0xbd, 0x72, 0x4b, + 0xfc, 0xe4, 0x40, 0x71, 0xd5, 0x8a, 0xfa, 0x6a, 0x45, 0x43, 0xbc, 0x90, 0x47, 0xc9, 0x88, 0x13, 0x12, 0x7f, 0xfb, + 0x8a, 0x86, 0x7a, 0x45, 0x17, 0x3b, 0x56, 0x74, 0x71, 0xcf, 0x8a, 0x06, 0x6a, 0xf5, 0xac, 0x12, 0x77, 0xe9, 0x6a, + 0xd5, 0x69, 0x57, 0xd8, 0xeb, 0xb7, 0x42, 0x76, 0x0d, 0xab, 0x01, 0xda, 0x21, 0x67, 0x33, 0xba, 0x9d, 0x28, 0xeb, + 0x28, 0xa6, 0x9f, 0x84, 0xc9, 0x0a, 0x0b, 0x59, 0x1d, 0x0b, 0x26, 0x5d, 0x97, 0x51, 0xcf, 0xdf, 0x93, 0xb2, 0x19, + 0xe0, 0x21, 0x07, 0x3c, 0x44, 0xfa, 0x12, 0x52, 0xc7, 0x7e, 0x6f, 0x63, 0xdb, 0xb2, 0x35, 0x59, 0x8f, 0x8b, 0x4b, + 0x90, 0x11, 0x62, 0x7e, 0x0f, 0xa2, 0x45, 0xa8, 0x6d, 0x0f, 0x76, 0xd3, 0x1c, 0x24, 0x28, 0xdc, 0xa4, 0x59, 0x68, + 0x7b, 0xb2, 0xea, 0x27, 0xa1, 0x6a, 0xc6, 0x12, 0x95, 0xee, 0xb6, 0x93, 0xd6, 0xaa, 0xf7, 0x26, 0xc5, 0x75, 0x8f, + 0x8f, 0x65, 0x8d, 0xb9, 0xcf, 0x39, 0xcd, 0x12, 0x45, 0xb9, 0xb6, 0xfd, 0x1f, 0x82, 0x0a, 0xb7, 0xf0, 0x95, 0x40, + 0x2f, 0x80, 0x26, 0x40, 0xa5, 0xe7, 0x2b, 0x9e, 0x2f, 0xc5, 0xd3, 0x5e, 0xa5, 0xe0, 0xde, 0x21, 0xd3, 0xd6, 0x90, + 0x45, 0x60, 0xfa, 0x2c, 0x66, 0x34, 0xbc, 0x14, 0x0c, 0x7a, 0x18, 0x8f, 0x95, 0xc2, 0xba, 0x26, 0xee, 0xaa, 0x06, + 0xd8, 0xfe, 0x71, 0xd1, 0x7d, 0x7c, 0x74, 0x66, 0x63, 0xc9, 0xe3, 0xd3, 0xc9, 0xc4, 0x46, 0x85, 0xf5, 0xb0, 0x66, + 0x9d, 0xa3, 0x1f, 0x17, 0x5f, 0x3e, 0x6f, 0x7f, 0x59, 0x36, 0x4e, 0x80, 0x88, 0x54, 0x86, 0x85, 0x16, 0x55, 0x06, + 0xbc, 0x7a, 0x46, 0x13, 0x3f, 0xd9, 0x3d, 0x9d, 0x91, 0x39, 0x9d, 0x7c, 0x4e, 0x69, 0x08, 0xc4, 0x89, 0x37, 0x4a, + 0x2f, 0x63, 0x7a, 0x4d, 0xf5, 0xe5, 0x8f, 0x5b, 0x06, 0xdb, 0xd2, 0x22, 0x48, 0x17, 0x09, 0x57, 0xa9, 0x26, 0x8a, + 0xd5, 0x1a, 0x53, 0x1a, 0x8b, 0x39, 0x98, 0x26, 0xc4, 0x9d, 0x94, 0x73, 0x75, 0xe9, 0x55, 0x8c, 0xb1, 0x6d, 0x00, + 0xb0, 0x13, 0xb2, 0xe1, 0x8e, 0x72, 0xaf, 0x8d, 0xdb, 0xbb, 0x60, 0xc3, 0x1d, 0xe4, 0xd9, 0xf6, 0x85, 0xc6, 0x93, + 0xf0, 0x16, 0xd7, 0x6e, 0xec, 0xd8, 0x89, 0xaf, 0x8f, 0x62, 0xe0, 0x2a, 0x83, 0xce, 0x12, 0x9a, 0xe7, 0x3b, 0x11, + 0x50, 0x2e, 0x22, 0xb6, 0xab, 0xda, 0xf6, 0x8e, 0x5e, 0x70, 0x1b, 0xc3, 0x0e, 0x13, 0x00, 0x97, 0x31, 0x6b, 0x55, + 0x8b, 0x4e, 0x26, 0x34, 0x28, 0x9d, 0xed, 0x10, 0x7d, 0x9c, 0xb0, 0x98, 0x43, 0x10, 0x4e, 0x44, 0xc7, 0xec, 0xd7, + 0x69, 0x42, 0x6d, 0xa4, 0xf3, 0x69, 0x15, 0xfc, 0x4a, 0xfe, 0x6f, 0x87, 0x47, 0xf6, 0x58, 0x87, 0x45, 0x8d, 0xb2, + 0x5a, 0x69, 0x5f, 0x50, 0xad, 0xbc, 0x8e, 0xc8, 0x54, 0x38, 0x7b, 0x76, 0x6d, 0xa0, 0x87, 0x6d, 0x93, 0x65, 0xe7, + 0xcb, 0xe3, 0x4e, 0xbb, 0xb0, 0xb1, 0x0d, 0xdd, 0x3d, 0x74, 0x97, 0x88, 0x56, 0x87, 0xd0, 0x6a, 0x91, 0x7c, 0x4a, + 0xbb, 0x6e, 0xe7, 0x49, 0xc7, 0xc6, 0xf2, 0x22, 0x07, 0x54, 0x94, 0xcc, 0x20, 0x00, 0xf7, 0xf3, 0x6f, 0x9e, 0x4a, + 0xbd, 0xf3, 0x87, 0xc1, 0xf3, 0xa8, 0xd3, 0xb6, 0xb1, 0x9d, 0xf3, 0x74, 0xfe, 0x09, 0x53, 0x38, 0xb4, 0xb1, 0x1d, + 0xc4, 0x69, 0x4e, 0xcd, 0x39, 0x48, 0x75, 0xf6, 0xb7, 0x4f, 0x42, 0x42, 0x34, 0xcf, 0x68, 0x9e, 0x5b, 0x66, 0xff, + 0x8a, 0x94, 0x3e, 0xc2, 0x30, 0xb7, 0x52, 0x5c, 0x4e, 0xb9, 0xc0, 0x8b, 0xbc, 0x63, 0xc1, 0xa4, 0x2a, 0x59, 0xb6, + 0x41, 0x6c, 0x42, 0x04, 0x94, 0x8c, 0x4d, 0x6a, 0x57, 0x1f, 0x1d, 0x79, 0xcb, 0xd6, 0x93, 0x03, 0xcb, 0xa8, 0xfc, + 0xe6, 0x00, 0xb5, 0x92, 0x19, 0x4b, 0x2e, 0xb7, 0x94, 0xfa, 0xb7, 0x5b, 0x4a, 0x41, 0x65, 0x2b, 0xa1, 0x53, 0xf7, + 0xff, 0x7c, 0x1c, 0xeb, 0x95, 0xe2, 0x63, 0x82, 0x18, 0x0a, 0xe7, 0xe6, 0x47, 0x20, 0x35, 0x96, 0x41, 0xf4, 0xf0, + 0xeb, 0x87, 0x83, 0x92, 0x4f, 0x19, 0xae, 0xec, 0xe5, 0xb7, 0xcd, 0x10, 0x4a, 0x9b, 0x10, 0x41, 0x88, 0x3f, 0x69, + 0xae, 0xf4, 0xf6, 0xe3, 0x04, 0x67, 0x68, 0x55, 0xbf, 0x61, 0xe9, 0xd5, 0x3d, 0x02, 0xeb, 0x6b, 0xbf, 0xa5, 0x58, + 0x29, 0x3e, 0xe5, 0xfa, 0x07, 0x31, 0x9b, 0x55, 0x24, 0xb0, 0x09, 0xa6, 0xd0, 0x78, 0x20, 0x9d, 0xcc, 0xec, 0x44, + 0xaa, 0x3e, 0x97, 0x70, 0x48, 0x16, 0xee, 0x21, 0x59, 0x64, 0xf4, 0x32, 0x4e, 0x6f, 0xd6, 0x2f, 0x56, 0xdb, 0x5d, + 0x39, 0x62, 0xd3, 0xc8, 0x38, 0xf9, 0x46, 0x49, 0xb9, 0x08, 0xf7, 0x0e, 0x50, 0xfc, 0xcb, 0x3f, 0xbb, 0xee, 0xbf, + 0xfc, 0xf3, 0x47, 0xab, 0x42, 0xf7, 0xc5, 0x18, 0xf3, 0xaa, 0xdb, 0xdd, 0xbb, 0x6b, 0xfb, 0x48, 0x75, 0x9c, 0x6f, + 0xaf, 0xb3, 0xb1, 0x08, 0xf0, 0x7e, 0x63, 0x09, 0x36, 0x0a, 0xe5, 0xee, 0xb3, 0x7e, 0x0d, 0x60, 0x30, 0xaf, 0x8f, + 0x42, 0x06, 0x95, 0x7e, 0x13, 0x68, 0x63, 0xe4, 0x3d, 0x68, 0x45, 0x7e, 0x3d, 0x86, 0x3f, 0x36, 0x87, 0xdf, 0x08, + 0xbe, 0xf2, 0x4f, 0xc4, 0xe3, 0x71, 0x99, 0xe2, 0x68, 0x36, 0x85, 0x0b, 0x14, 0x86, 0x1b, 0x25, 0x4a, 0xf1, 0xf0, + 0xda, 0x68, 0x20, 0x0e, 0x68, 0x92, 0x78, 0xfc, 0x0a, 0x6e, 0x4d, 0xea, 0x5f, 0x65, 0xda, 0xc1, 0x7b, 0x8f, 0x70, + 0x80, 0x2e, 0xea, 0xb3, 0x12, 0x9d, 0x6e, 0x48, 0x06, 0x28, 0x05, 0x73, 0x03, 0xc0, 0xc4, 0xf1, 0x58, 0x59, 0x9b, + 0x67, 0xd2, 0x0d, 0xe3, 0xad, 0x93, 0xb6, 0x72, 0xcf, 0xd4, 0x90, 0x8e, 0xad, 0xf7, 0x02, 0x5f, 0xa2, 0x32, 0xad, + 0xac, 0x7b, 0xe1, 0xea, 0x02, 0x3b, 0xa2, 0x64, 0x3f, 0xd7, 0x7e, 0x7c, 0xfd, 0x30, 0xc6, 0xb7, 0x5b, 0xa0, 0xae, + 0xac, 0xd5, 0x3f, 0x5a, 0x25, 0x58, 0x35, 0x57, 0xdb, 0xf4, 0x81, 0x1b, 0x9f, 0xd3, 0xec, 0x32, 0x82, 0x2c, 0xab, + 0xec, 0x23, 0xcc, 0x09, 0x56, 0x1a, 0x53, 0xf1, 0x97, 0x11, 0x75, 0x47, 0xf5, 0x3f, 0x88, 0x53, 0x31, 0x48, 0x91, + 0x84, 0xa1, 0x8c, 0x45, 0xf8, 0xff, 0x7c, 0xeb, 0x3f, 0x0c, 0xdf, 0xba, 0x7f, 0x88, 0xda, 0x01, 0xec, 0x4f, 0x5e, + 0xc8, 0xff, 0xd8, 0xec, 0x2e, 0x17, 0xec, 0xee, 0x57, 0x30, 0xba, 0xfc, 0x1f, 0xc3, 0xe8, 0x84, 0x8d, 0xac, 0x39, + 0x9d, 0xba, 0x78, 0xc7, 0x7c, 0xef, 0xdf, 0xf8, 0x77, 0xd5, 0xbe, 0x8a, 0xc7, 0xa7, 0x37, 0xfe, 0x5d, 0xb5, 0x08, + 0xbb, 0xd9, 0xc5, 0x7a, 0x1f, 0x43, 0xfb, 0xcd, 0x6b, 0xdb, 0xb3, 0xdf, 0x7c, 0xf9, 0xa5, 0x8d, 0xc7, 0x39, 0xe5, + 0x43, 0x28, 0x24, 0xfb, 0xcb, 0xbd, 0xf5, 0x8a, 0xe0, 0x46, 0x81, 0x29, 0x8a, 0x50, 0x1b, 0x64, 0x34, 0x1a, 0xef, + 0x59, 0x7e, 0x99, 0x26, 0x26, 0x34, 0x6f, 0xc1, 0xb2, 0xff, 0x54, 0x70, 0x44, 0x2f, 0x1b, 0xf0, 0x88, 0xd2, 0x75, + 0x80, 0x44, 0x61, 0x0d, 0xa2, 0xea, 0x3e, 0xa2, 0xfb, 0xf9, 0x7f, 0x75, 0xe7, 0x82, 0xbc, 0x4a, 0x24, 0x1a, 0xc6, + 0xe3, 0x4f, 0x11, 0x1f, 0x72, 0xb0, 0xca, 0x63, 0xa7, 0xdd, 0x9d, 0x7e, 0xb1, 0xbf, 0x8c, 0x0e, 0x0e, 0xd8, 0xd0, + 0xc6, 0xe2, 0x12, 0xa8, 0x62, 0x9b, 0x70, 0xc9, 0xe1, 0x4f, 0x06, 0x7f, 0xd2, 0x62, 0x5c, 0x88, 0x7c, 0x3c, 0x46, + 0x77, 0xa4, 0x0c, 0xe5, 0xf4, 0xa3, 0x29, 0x43, 0xfe, 0x83, 0x52, 0x86, 0x72, 0xfa, 0x7b, 0xa7, 0x0c, 0x31, 0x6a, + 0xa4, 0x0c, 0x01, 0x1a, 0x7f, 0x7e, 0x50, 0xe6, 0x89, 0xce, 0x13, 0x48, 0x6f, 0x72, 0xd2, 0x51, 0xde, 0x9a, 0x38, + 0x9d, 0x42, 0xda, 0xc9, 0x3f, 0x3e, 0x8b, 0x24, 0x4e, 0xa7, 0x66, 0x0e, 0x09, 0xdc, 0xce, 0x0e, 0x49, 0x23, 0x38, + 0x23, 0x4b, 0xfb, 0xc7, 0xdb, 0xce, 0xd3, 0x51, 0xa7, 0x77, 0xd8, 0x99, 0xd9, 0x9e, 0x0d, 0xe6, 0x91, 0x28, 0x68, + 0xf7, 0x0e, 0x0f, 0xa1, 0xe0, 0xc6, 0x28, 0xe8, 0x42, 0x01, 0x33, 0x0a, 0x8e, 0xa1, 0x20, 0x30, 0x0a, 0x1e, 0x41, + 0x41, 0x68, 0x14, 0x3c, 0x86, 0x82, 0x6b, 0xbb, 0x18, 0xb1, 0x32, 0x2f, 0xea, 0x31, 0x12, 0x17, 0x39, 0xed, 0x65, + 0xf5, 0x43, 0x6c, 0x11, 0xd1, 0x55, 0x1e, 0x97, 0x07, 0x60, 0x9b, 0x47, 0xfa, 0xbe, 0xa6, 0xf1, 0x67, 0x63, 0x84, + 0x7d, 0x02, 0xe7, 0xd1, 0x31, 0x78, 0x4f, 0x65, 0xcd, 0x43, 0xfd, 0xda, 0xf6, 0xca, 0xe4, 0xa1, 0x36, 0xee, 0xea, + 0xf4, 0x21, 0xcf, 0x46, 0x78, 0x51, 0x56, 0x3e, 0x6e, 0x84, 0xaa, 0x5b, 0xb8, 0x0a, 0xa9, 0xba, 0x87, 0xec, 0x10, + 0x61, 0x79, 0x95, 0xff, 0x33, 0x61, 0xc8, 0xb8, 0x3c, 0x7d, 0xcf, 0x66, 0x54, 0x7f, 0x18, 0x4b, 0x0f, 0x60, 0x89, + 0x04, 0xab, 0x5e, 0x54, 0x5d, 0xde, 0xf9, 0x1d, 0x3e, 0xad, 0xae, 0xbe, 0x7b, 0xcf, 0x89, 0xbc, 0x4b, 0x28, 0xc3, + 0xd2, 0x23, 0x37, 0xc5, 0xdc, 0x9f, 0x7a, 0x90, 0x61, 0x02, 0xc1, 0x2d, 0xef, 0x94, 0x10, 0xd2, 0x1e, 0x2e, 0xbc, + 0xef, 0xf0, 0x4d, 0x44, 0x13, 0xef, 0xb2, 0xe8, 0x95, 0x04, 0x20, 0x13, 0x5c, 0xde, 0xf3, 0xf2, 0xc6, 0x54, 0x41, + 0x15, 0xd5, 0x6b, 0x09, 0x67, 0xb3, 0xa4, 0x9e, 0x1d, 0x39, 0x11, 0x86, 0xf3, 0x7c, 0x12, 0xa7, 0x37, 0xcd, 0x5b, + 0x7b, 0xb0, 0x3d, 0x4f, 0x02, 0x66, 0x57, 0xe6, 0x49, 0xbc, 0x04, 0x60, 0xcb, 0xa7, 0xf7, 0xfe, 0xb4, 0xfc, 0xfd, + 0x8a, 0xe6, 0xb9, 0x3f, 0x55, 0x35, 0x77, 0xe7, 0x45, 0x08, 0x10, 0xcd, 0x9c, 0x08, 0x0d, 0x04, 0x24, 0x2f, 0x00, + 0x46, 0xc0, 0xf9, 0xac, 0x72, 0x19, 0x60, 0xea, 0xf5, 0x34, 0x08, 0x81, 0xab, 0x7a, 0x11, 0xf7, 0xa7, 0x55, 0x41, + 0x7f, 0x9e, 0x51, 0x95, 0x60, 0x01, 0x68, 0x2c, 0xfa, 0x2d, 0x28, 0x90, 0xaf, 0x77, 0xa4, 0x3b, 0x68, 0x4f, 0xf7, + 0xee, 0xa4, 0x07, 0x4b, 0xa7, 0x3b, 0x98, 0x29, 0xba, 0x65, 0x7e, 0xee, 0x66, 0x90, 0xfd, 0xf3, 0x4e, 0x00, 0xff, + 0xa9, 0x10, 0xfe, 0xe7, 0x93, 0xc9, 0xe4, 0xde, 0xf4, 0x87, 0xcf, 0xc3, 0x09, 0xed, 0xd2, 0xe3, 0x1e, 0xa4, 0x6f, + 0x36, 0x55, 0xd0, 0xbc, 0x53, 0x08, 0xdc, 0x2d, 0x1f, 0x56, 0x19, 0xe2, 0xeb, 0x3c, 0x5a, 0x3e, 0x3c, 0x15, 0xa2, + 0x98, 0x67, 0x74, 0x39, 0xf3, 0xb3, 0x29, 0x4b, 0xbc, 0x76, 0xe1, 0x5e, 0xab, 0xdc, 0x81, 0xcf, 0x4f, 0x4e, 0x4e, + 0x0a, 0x37, 0xd4, 0x4f, 0xed, 0x30, 0x2c, 0xdc, 0x60, 0x59, 0x4e, 0xa3, 0xdd, 0x9e, 0x4c, 0x0a, 0x97, 0xe9, 0x82, + 0xc3, 0x6e, 0x10, 0x1e, 0x76, 0x0b, 0xf7, 0xc6, 0xa8, 0x51, 0xb8, 0x54, 0x3d, 0x65, 0x34, 0xac, 0xe5, 0x80, 0x3e, + 0x6e, 0xb7, 0x0b, 0x57, 0x12, 0xda, 0x12, 0xfc, 0x87, 0xf2, 0xa7, 0xe7, 0x2f, 0xb8, 0x60, 0xee, 0x3d, 0x9f, 0x3b, + 0xa3, 0x99, 0xba, 0x5f, 0x4b, 0x7e, 0x8d, 0xaa, 0x40, 0x17, 0xf8, 0x67, 0x33, 0xca, 0x0f, 0xc4, 0x2c, 0xa2, 0xfb, + 0xbe, 0x4e, 0x02, 0xa8, 0xbd, 0x06, 0xca, 0x12, 0xaf, 0x7f, 0x26, 0x7e, 0x15, 0xfc, 0x07, 0x4e, 0x06, 0x35, 0xe5, + 0x35, 0xb0, 0xc9, 0x2e, 0xf9, 0x91, 0x7d, 0x5c, 0x7e, 0xdc, 0x3d, 0x44, 0x7c, 0x64, 0xbf, 0xbb, 0xf8, 0x48, 0x4c, + 0xf1, 0x21, 0x99, 0xc7, 0x15, 0x27, 0x76, 0x10, 0xd1, 0xe0, 0xc3, 0x55, 0x7a, 0xdb, 0x84, 0x2d, 0x91, 0xd9, 0x42, + 0xb0, 0xec, 0xff, 0xda, 0x94, 0x46, 0xdd, 0x99, 0xf1, 0x2d, 0x2b, 0x21, 0x8a, 0xdf, 0x24, 0xc4, 0x7e, 0xa3, 0x9d, + 0x90, 0xb2, 0x64, 0x32, 0x21, 0xf6, 0x9b, 0xc9, 0xc4, 0xd6, 0xb7, 0x04, 0xf8, 0x9c, 0x8a, 0x5a, 0xaf, 0x6b, 0x25, + 0xa2, 0x16, 0x28, 0x25, 0x55, 0x99, 0x59, 0xa0, 0x72, 0x04, 0xcc, 0x7c, 0x00, 0xf5, 0x26, 0x64, 0x39, 0x6c, 0x35, + 0xf8, 0xc4, 0x56, 0xfd, 0x96, 0xe2, 0xa4, 0xf6, 0x41, 0x89, 0x12, 0xe0, 0x2d, 0x5f, 0xc1, 0x58, 0xbf, 0x22, 0x67, + 0x4a, 0x75, 0xc6, 0xfe, 0xd3, 0xbb, 0xaf, 0x42, 0xe7, 0x8a, 0xa3, 0x82, 0xe5, 0x6f, 0x92, 0xb5, 0xe3, 0xaf, 0x12, + 0x46, 0x42, 0xcc, 0x69, 0x15, 0x3c, 0x9d, 0x4e, 0x63, 0xf8, 0xca, 0xd9, 0xb2, 0x76, 0x73, 0xba, 0x6c, 0x3e, 0xac, + 0xcd, 0xd7, 0x33, 0x1b, 0xaa, 0x7b, 0xc6, 0xc5, 0x47, 0x17, 0xe5, 0xb1, 0xa9, 0x6b, 0xf5, 0xf5, 0x3d, 0xe1, 0xbf, + 0x5c, 0x2a, 0x26, 0xbf, 0x94, 0x87, 0x6d, 0x38, 0x66, 0xa1, 0x6c, 0xce, 0xc2, 0xa2, 0x50, 0xc7, 0x14, 0x43, 0x96, + 0xcf, 0xe1, 0x46, 0x6f, 0xd9, 0x92, 0x7e, 0x8c, 0x85, 0xe7, 0x37, 0x46, 0x20, 0xbe, 0xb6, 0x5c, 0x85, 0x8e, 0xc4, + 0xcb, 0xc8, 0xe6, 0x15, 0x2f, 0x6c, 0x15, 0x20, 0xd5, 0x48, 0xb4, 0x2d, 0x89, 0x4f, 0x99, 0x22, 0x60, 0xcc, 0x10, + 0xa2, 0x94, 0xe5, 0x82, 0xe8, 0x57, 0xba, 0xa0, 0x30, 0x13, 0x4d, 0xc4, 0x1b, 0x89, 0x2d, 0x11, 0xd6, 0xce, 0xe7, + 0x7e, 0x22, 0xd9, 0x28, 0xb1, 0x25, 0x3f, 0xd8, 0x5f, 0x56, 0x2b, 0x5f, 0xd8, 0x1a, 0x6c, 0x49, 0xbc, 0x83, 0x7e, + 0x0b, 0x1a, 0x0c, 0xac, 0x1a, 0xe8, 0xc9, 0x46, 0x34, 0xfc, 0xfe, 0xbc, 0xb4, 0x0f, 0x63, 0x37, 0xbf, 0xc1, 0x6e, + 0x7e, 0x63, 0xfd, 0x71, 0xd9, 0xbc, 0xa1, 0x57, 0x1f, 0x18, 0x6f, 0x72, 0x7f, 0xde, 0x04, 0x4b, 0x4f, 0x44, 0xb1, + 0x14, 0x7b, 0x16, 0xf9, 0xed, 0xf2, 0x92, 0x9f, 0xde, 0x22, 0x87, 0xf4, 0x35, 0x61, 0x7e, 0x78, 0x49, 0x9a, 0xd0, + 0x5e, 0xfd, 0x1c, 0x83, 0x99, 0x0d, 0xa5, 0xb1, 0x75, 0xb1, 0x4c, 0x21, 0xdd, 0x8d, 0xdf, 0x79, 0x6d, 0xc5, 0xd6, + 0xdb, 0x3a, 0xd5, 0xa9, 0xbd, 0xb5, 0xbe, 0xa7, 0x90, 0xdb, 0x10, 0xd2, 0x2b, 0xdb, 0x4c, 0xf9, 0xda, 0x95, 0xb2, + 0xf5, 0xb1, 0xac, 0x7e, 0x88, 0x7d, 0xe9, 0xff, 0x8d, 0xe3, 0x10, 0xeb, 0xc5, 0x22, 0xab, 0xff, 0x21, 0x90, 0x79, + 0xfe, 0x84, 0xd3, 0x0c, 0x3f, 0xa4, 0xe6, 0x95, 0x38, 0x80, 0xbb, 0x04, 0x31, 0xe3, 0x75, 0x4e, 0xe6, 0xb7, 0x0f, + 0xef, 0xfe, 0xfe, 0xe9, 0x17, 0x0a, 0x47, 0xfa, 0x42, 0x3a, 0xdb, 0xee, 0xc1, 0x46, 0x88, 0xfd, 0x3b, 0x8f, 0x25, + 0x42, 0xe6, 0x5d, 0x41, 0x00, 0xab, 0x37, 0x4f, 0xd5, 0xf1, 0x94, 0x8c, 0xc6, 0xe2, 0xfb, 0xb3, 0x6a, 0x29, 0x0e, + 0x1f, 0xcd, 0x6f, 0xf5, 0x6a, 0x74, 0xd6, 0x8e, 0x9d, 0xfc, 0xae, 0xa7, 0x4b, 0x76, 0x1f, 0x67, 0xa9, 0x9f, 0x90, + 0x38, 0x9e, 0xdf, 0xf6, 0xa4, 0xa0, 0x6d, 0x66, 0x12, 0xaa, 0xf6, 0xfc, 0xd6, 0x3c, 0x5f, 0x53, 0x75, 0x64, 0xb9, + 0x87, 0xb9, 0x45, 0xfd, 0x9c, 0xf6, 0xe0, 0x8b, 0x1b, 0x2c, 0xf0, 0x63, 0x25, 0xcc, 0x67, 0x2c, 0x0c, 0x63, 0xda, + 0xd3, 0xf2, 0xda, 0xea, 0x3c, 0x82, 0xe3, 0x29, 0xe6, 0x92, 0xd5, 0x57, 0xc5, 0x40, 0x5e, 0x89, 0x27, 0xff, 0x2a, + 0x4f, 0x63, 0xf8, 0xdc, 0xd5, 0x56, 0x74, 0xaa, 0x73, 0x1b, 0xed, 0x0a, 0x79, 0xe2, 0x77, 0x7d, 0x2e, 0xc7, 0xed, + 0x3f, 0xf4, 0xc4, 0x82, 0xb7, 0x7b, 0x3c, 0x9d, 0x7b, 0xcd, 0xc3, 0xfa, 0x44, 0xe0, 0x55, 0x39, 0x05, 0xbc, 0x65, + 0x5a, 0x18, 0xa4, 0x95, 0xe4, 0xd3, 0x96, 0xdb, 0x51, 0x65, 0xa2, 0x03, 0xc8, 0xef, 0x2d, 0x8b, 0x8a, 0xfa, 0x64, + 0xfe, 0x31, 0xbb, 0xe5, 0xc9, 0xf6, 0xdd, 0xf2, 0x44, 0xef, 0x96, 0xfb, 0x29, 0xf6, 0xf3, 0x49, 0x07, 0xfe, 0xeb, + 0x55, 0x13, 0xf2, 0xda, 0xd6, 0xe1, 0xfc, 0xd6, 0x02, 0x3d, 0xad, 0xd9, 0x9d, 0xdf, 0xca, 0xd3, 0x45, 0x10, 0x64, + 0x6f, 0xc3, 0x79, 0x1b, 0xdc, 0xb6, 0xa0, 0x10, 0xfe, 0x6f, 0xd7, 0x5e, 0x75, 0x8e, 0xe0, 0x1d, 0xb4, 0x3a, 0xde, + 0x7c, 0xd7, 0xbd, 0x7f, 0xd3, 0x7a, 0x49, 0xca, 0x1d, 0x4f, 0x73, 0x63, 0xe4, 0x72, 0xff, 0xea, 0x8a, 0x86, 0xde, + 0x24, 0x0d, 0x16, 0xf9, 0x3f, 0x29, 0xf8, 0x15, 0x12, 0xef, 0xdd, 0xd2, 0x6b, 0xfd, 0xe8, 0xa6, 0xf2, 0xac, 0x93, + 0xee, 0x61, 0x59, 0xae, 0x93, 0x97, 0x07, 0x7e, 0x4c, 0x9d, 0xae, 0x7b, 0xb4, 0x65, 0x13, 0xfc, 0x9b, 0xac, 0xcd, + 0xd6, 0xc9, 0xfc, 0x56, 0x64, 0xdc, 0x8b, 0x84, 0x4f, 0xc2, 0x81, 0xb9, 0x86, 0xed, 0x93, 0xed, 0xe0, 0x8e, 0xf4, + 0x48, 0x17, 0x5a, 0x28, 0x28, 0xb9, 0x13, 0xd2, 0x89, 0xbf, 0x88, 0xf9, 0xfd, 0xbd, 0xee, 0xa2, 0x8c, 0x8d, 0x5e, + 0xef, 0x61, 0xe8, 0x55, 0xdd, 0x07, 0x72, 0xe9, 0xcf, 0x9f, 0x1c, 0xc1, 0x7f, 0x32, 0x51, 0xf7, 0xae, 0xd2, 0xd5, + 0xa5, 0xdd, 0x0b, 0xba, 0xfa, 0x7e, 0x4d, 0x19, 0x97, 0x22, 0x5c, 0xe8, 0xe3, 0x0f, 0xad, 0x0d, 0x5a, 0xe5, 0x83, + 0xaa, 0x2b, 0x2d, 0xeb, 0x93, 0x6a, 0x7f, 0x5a, 0xe7, 0x0f, 0xac, 0x1b, 0x48, 0xcd, 0xb5, 0x5e, 0x57, 0x7d, 0x80, + 0x77, 0xa3, 0xb2, 0xc6, 0xb8, 0xa8, 0xbf, 0x4f, 0xee, 0x4a, 0x13, 0x45, 0xa6, 0xcd, 0x80, 0x95, 0xb2, 0x2f, 0xad, + 0x94, 0x94, 0x92, 0x71, 0x7f, 0x78, 0x3b, 0x8b, 0xad, 0x6b, 0x79, 0x51, 0x00, 0xb1, 0x3b, 0x6e, 0xdb, 0xb6, 0x44, + 0xc2, 0x16, 0x7c, 0xaf, 0xc4, 0x16, 0x1f, 0x76, 0xb7, 0x87, 0xa0, 0x69, 0x5d, 0x4f, 0x85, 0x66, 0xf7, 0xd2, 0xbf, + 0xa3, 0xd9, 0x65, 0xd7, 0xb6, 0xc0, 0x4f, 0xd3, 0x94, 0xb9, 0x6d, 0xa2, 0xcc, 0xea, 0xda, 0xd6, 0xed, 0x2c, 0x4e, + 0x72, 0x62, 0x47, 0x9c, 0xcf, 0x3d, 0xf9, 0xe5, 0xf7, 0x9b, 0x43, 0x37, 0xcd, 0xa6, 0xad, 0x6e, 0xbb, 0xdd, 0x86, + 0xab, 0xcf, 0x6d, 0xeb, 0x9a, 0xd1, 0x9b, 0xa7, 0xe9, 0x2d, 0xb1, 0xdb, 0x56, 0xdb, 0xea, 0x74, 0x4f, 0xac, 0x4e, + 0xf7, 0xc8, 0x7d, 0x74, 0x62, 0x0f, 0x3e, 0xb3, 0xac, 0x7e, 0x48, 0x27, 0x39, 0xfc, 0xb0, 0xac, 0xbe, 0x50, 0xbc, + 0xe4, 0x6f, 0xcb, 0x72, 0x83, 0x38, 0x6f, 0x76, 0xac, 0xa5, 0x7a, 0xb4, 0x2c, 0xb8, 0x4e, 0xc1, 0xb3, 0x3e, 0x9f, + 0x74, 0x27, 0x47, 0x93, 0x27, 0x3d, 0x55, 0x5c, 0x7c, 0x56, 0xab, 0x8e, 0xe5, 0xbf, 0x5d, 0xa3, 0x59, 0xce, 0xb3, + 0xf4, 0x03, 0x55, 0xc9, 0xe3, 0x16, 0x88, 0x9e, 0xad, 0x4d, 0xbb, 0x9b, 0x23, 0x75, 0x4e, 0xae, 0x82, 0x49, 0xb7, + 0xaa, 0x2e, 0x60, 0x6c, 0x95, 0x40, 0xf6, 0x5b, 0x1a, 0xf4, 0xbe, 0x89, 0xa6, 0x4e, 0x73, 0x1b, 0xa2, 0x3a, 0xb6, + 0x9a, 0xe3, 0x54, 0xcf, 0xaf, 0x0f, 0xa7, 0xf7, 0xb4, 0xae, 0x2a, 0x20, 0xb0, 0xad, 0x90, 0xd8, 0xaf, 0x3a, 0xdd, + 0x13, 0xdc, 0xe9, 0x3c, 0x72, 0x1f, 0x9d, 0x04, 0x6d, 0x7c, 0xe4, 0x1e, 0x35, 0x0f, 0xdd, 0x47, 0xf8, 0xa4, 0x79, + 0x82, 0x4f, 0x5e, 0x9c, 0x04, 0xcd, 0x23, 0xf7, 0x08, 0xb7, 0x9b, 0x27, 0x50, 0xd8, 0x3c, 0x69, 0x9e, 0x5c, 0x37, + 0x8f, 0x4e, 0x82, 0xb6, 0x28, 0xed, 0xba, 0xc7, 0xc7, 0xcd, 0x4e, 0xdb, 0x3d, 0x3e, 0xc6, 0xc7, 0xee, 0xa3, 0x47, + 0xcd, 0xce, 0xa1, 0xfb, 0xe8, 0xd1, 0xcb, 0xe3, 0x13, 0xf7, 0x10, 0xde, 0x1d, 0x1e, 0x06, 0x87, 0x6e, 0xa7, 0xd3, + 0x84, 0x3f, 0xf8, 0xc4, 0xed, 0xca, 0x1f, 0x9d, 0x8e, 0x7b, 0xd8, 0xc1, 0xed, 0xf8, 0xb8, 0xeb, 0x3e, 0x7a, 0x82, + 0xc5, 0x5f, 0x51, 0x0d, 0x8b, 0x3f, 0xd0, 0x0d, 0x7e, 0xe2, 0x76, 0x1f, 0xc9, 0x5f, 0xa2, 0xc3, 0xeb, 0xa3, 0x93, + 0xbf, 0xd9, 0xad, 0x9d, 0x73, 0xe8, 0xc8, 0x39, 0x9c, 0x1c, 0xbb, 0x87, 0x87, 0xf8, 0xa8, 0xe3, 0x9e, 0x1c, 0x46, + 0xcd, 0xa3, 0xae, 0xfb, 0xe8, 0x71, 0xd0, 0xec, 0xb8, 0x8f, 0x1f, 0xe3, 0x76, 0xf3, 0xd0, 0xed, 0xe2, 0x8e, 0x7b, + 0x74, 0x28, 0x7e, 0x1c, 0xba, 0xdd, 0xeb, 0xc7, 0x4f, 0xdc, 0x47, 0xc7, 0xd1, 0x23, 0xf7, 0xe8, 0xbb, 0xa3, 0x13, + 0xb7, 0x7b, 0x18, 0x1d, 0x3e, 0x72, 0xbb, 0x8f, 0xaf, 0x1f, 0xb9, 0x47, 0x51, 0xb3, 0xfb, 0xe8, 0xde, 0x96, 0x9d, + 0xae, 0x0b, 0x38, 0x12, 0xaf, 0xe1, 0x05, 0x56, 0x2f, 0xe0, 0xff, 0x48, 0xb4, 0xfd, 0x37, 0xec, 0x26, 0xdf, 0x6c, + 0xfa, 0xc4, 0x3d, 0x79, 0x1c, 0xc8, 0xea, 0x50, 0xd0, 0xd4, 0x35, 0xa0, 0xc9, 0x75, 0x53, 0x0e, 0x2b, 0xba, 0x6b, + 0xea, 0x8e, 0xf4, 0xff, 0x6a, 0xb0, 0xeb, 0x26, 0x0c, 0x2c, 0xc7, 0xfd, 0x77, 0xed, 0xa7, 0x5c, 0xf2, 0x7e, 0x6b, + 0x2a, 0x49, 0x7f, 0x3a, 0xf8, 0x4c, 0x7e, 0xd7, 0xe0, 0xb3, 0x31, 0xf6, 0x77, 0x39, 0x3e, 0xe2, 0x8f, 0x3b, 0x3e, + 0x22, 0xfa, 0x10, 0xcf, 0x47, 0xfc, 0xbb, 0x7b, 0x3e, 0xfc, 0x75, 0xc7, 0xf9, 0x0d, 0xdf, 0x70, 0x70, 0xac, 0x5b, + 0xc5, 0x2f, 0xb9, 0x33, 0x4a, 0xe1, 0x0b, 0x9a, 0x45, 0xef, 0x86, 0x93, 0x88, 0x9a, 0x7e, 0xa0, 0x14, 0x58, 0xec, + 0x0d, 0x97, 0x3c, 0x36, 0xd8, 0x85, 0x90, 0xf0, 0xe3, 0x08, 0xf9, 0xf2, 0x21, 0xf8, 0x08, 0x7f, 0x77, 0x7c, 0x04, + 0x26, 0x3e, 0x6a, 0xbe, 0x7c, 0xe1, 0x69, 0x10, 0x9e, 0x82, 0x73, 0xf1, 0xec, 0xc0, 0xf1, 0xe1, 0x86, 0xdd, 0xa2, + 0x50, 0x94, 0xdb, 0x32, 0x22, 0xf6, 0xee, 0x53, 0xc2, 0x0e, 0xf2, 0xae, 0x00, 0x62, 0x2b, 0xb7, 0xcc, 0x5c, 0x48, + 0x1d, 0xf5, 0x50, 0x0a, 0xa5, 0xae, 0xdb, 0x76, 0xdb, 0xa5, 0x4b, 0x07, 0xee, 0x87, 0x20, 0xcb, 0x94, 0xfb, 0xf0, + 0xad, 0xf6, 0x38, 0x9d, 0x8a, 0xaf, 0xba, 0xc3, 0x77, 0x74, 0x20, 0x3b, 0x33, 0x90, 0x9f, 0x30, 0x82, 0x50, 0x8f, + 0x72, 0xf4, 0xf8, 0xd9, 0x87, 0x6f, 0xe0, 0x8e, 0x06, 0x1d, 0x95, 0x98, 0x81, 0xb7, 0xe3, 0x15, 0x0d, 0x99, 0xef, + 0xd8, 0xce, 0x3c, 0xa3, 0x13, 0x9a, 0xe5, 0xcd, 0xda, 0xc5, 0x05, 0xe2, 0xce, 0x02, 0x64, 0xeb, 0x8f, 0x82, 0x67, + 0xf0, 0x5d, 0x08, 0x32, 0x52, 0xfe, 0x46, 0x5b, 0x19, 0x60, 0x76, 0x81, 0x75, 0x49, 0x06, 0xb2, 0xb6, 0x52, 0xda, + 0x6c, 0xa9, 0xb5, 0x75, 0xdc, 0xee, 0x31, 0xb2, 0x44, 0x31, 0xdc, 0xb8, 0xff, 0x83, 0xd3, 0x3c, 0x6c, 0xff, 0x01, + 0x19, 0xcd, 0xca, 0x8e, 0x2e, 0x94, 0xbb, 0x2d, 0x29, 0xbf, 0xcb, 0xb4, 0x76, 0xab, 0x84, 0x2d, 0x29, 0xe2, 0x73, + 0x39, 0x77, 0x1b, 0xf5, 0x12, 0x15, 0xe0, 0x97, 0x77, 0x23, 0x4d, 0xd8, 0xd4, 0x31, 0xce, 0xdd, 0x26, 0xf2, 0x46, + 0x7f, 0xb8, 0xb6, 0x17, 0xa1, 0xa2, 0xaa, 0x92, 0xa0, 0xa5, 0x88, 0xb7, 0xb0, 0xc4, 0x4a, 0x56, 0x2b, 0x27, 0x01, + 0x17, 0x39, 0x31, 0x70, 0x0a, 0xcf, 0xa8, 0x86, 0xe4, 0x04, 0x97, 0x00, 0x09, 0x04, 0x93, 0x44, 0xfe, 0x5b, 0x15, + 0xeb, 0x1f, 0xca, 0xf1, 0xe5, 0xc6, 0x7e, 0x32, 0x05, 0x2a, 0xf4, 0x93, 0xe9, 0x86, 0x5b, 0x4d, 0x86, 0x8c, 0xd6, + 0x4a, 0xab, 0xae, 0x2a, 0xf7, 0x59, 0xfe, 0xf4, 0xee, 0xbd, 0xba, 0xfa, 0xd3, 0x06, 0xef, 0xb4, 0x88, 0x70, 0x54, + 0x9f, 0x29, 0x68, 0x90, 0x2f, 0xfa, 0x33, 0xca, 0x7d, 0x99, 0x58, 0x0f, 0xfa, 0x04, 0xdc, 0x17, 0x61, 0x29, 0x6b, + 0x94, 0xd8, 0x42, 0xba, 0x13, 0x79, 0xd8, 0x51, 0x8a, 0x7a, 0x6c, 0xa9, 0x3b, 0x73, 0x9a, 0x62, 0x69, 0x48, 0x07, + 0x4b, 0x7f, 0x4c, 0xe0, 0x8b, 0xa3, 0x53, 0x24, 0x49, 0xed, 0xc1, 0x17, 0xe5, 0x77, 0xbf, 0x77, 0x2d, 0x42, 0xcc, + 0x92, 0x0f, 0xa3, 0x8c, 0xc6, 0xff, 0x44, 0xbe, 0x60, 0x41, 0x9a, 0x7c, 0x71, 0x61, 0xa3, 0x1e, 0x77, 0xa3, 0x8c, + 0x4e, 0xc8, 0x17, 0x20, 0xe3, 0x3d, 0x61, 0x7d, 0x00, 0x23, 0x6c, 0xdc, 0xce, 0x62, 0x2c, 0x34, 0xa6, 0x07, 0x28, + 0x44, 0x12, 0x5c, 0xbb, 0x7b, 0x6c, 0x5b, 0xd2, 0x26, 0x16, 0xbf, 0x07, 0x52, 0x9c, 0x0a, 0x25, 0xc0, 0xea, 0x74, + 0xdd, 0xe3, 0xa8, 0xeb, 0x3e, 0xb9, 0x7e, 0xec, 0x9e, 0x44, 0x9d, 0xc7, 0xd7, 0x4d, 0xf8, 0xb7, 0xeb, 0x3e, 0x89, + 0x9b, 0x5d, 0xf7, 0x09, 0xfc, 0xff, 0xdd, 0x91, 0x7b, 0x1c, 0x35, 0x3b, 0xee, 0xc9, 0xf5, 0xa1, 0x7b, 0xf8, 0xb2, + 0xd3, 0x75, 0x0f, 0xad, 0x8e, 0x25, 0xdb, 0x01, 0xbb, 0x96, 0xdc, 0xf9, 0x8b, 0xb5, 0x0d, 0xb1, 0x25, 0x1c, 0x27, + 0x0f, 0x07, 0xd8, 0xd8, 0x29, 0xbf, 0x2e, 0xac, 0xf6, 0xa7, 0x72, 0xd6, 0x3d, 0xf3, 0x33, 0xf8, 0xc4, 0x5b, 0x7d, + 0xef, 0xd6, 0xde, 0xe1, 0x1a, 0xbf, 0xd8, 0x32, 0x04, 0xec, 0x70, 0x1b, 0x9b, 0x97, 0xce, 0xc0, 0x8d, 0x2d, 0xe2, + 0x8b, 0x18, 0xfa, 0x62, 0xe0, 0xdd, 0xa4, 0x2d, 0x2b, 0xea, 0xcb, 0x87, 0x05, 0xb3, 0x60, 0xe2, 0xdb, 0x43, 0x62, + 0x90, 0xaf, 0xc2, 0x62, 0x7d, 0x7c, 0x38, 0xa3, 0x90, 0xa5, 0xc6, 0xbd, 0x3b, 0xb4, 0x3a, 0x59, 0x17, 0x32, 0xb8, + 0x29, 0xa9, 0x28, 0x34, 0xe8, 0x35, 0x37, 0x6d, 0x85, 0x25, 0xc1, 0x2f, 0x68, 0x3e, 0xb4, 0xa1, 0xc8, 0xf6, 0x6c, + 0xe1, 0xe2, 0xb3, 0xcb, 0xcf, 0xdc, 0x95, 0x84, 0x5d, 0x15, 0x60, 0x71, 0x3a, 0x16, 0x76, 0x2d, 0xe0, 0xc7, 0x46, + 0x07, 0x07, 0x3b, 0xf7, 0x8b, 0x50, 0x20, 0x61, 0xae, 0xd5, 0xd7, 0xb1, 0x4c, 0x56, 0x64, 0x9b, 0x88, 0x2e, 0xfb, + 0x15, 0x28, 0x44, 0x0a, 0x4f, 0x57, 0xd4, 0xe7, 0xae, 0x9f, 0xc8, 0x6c, 0x47, 0x83, 0x61, 0xe1, 0x0e, 0x3d, 0x44, + 0x45, 0xca, 0x7d, 0x99, 0x65, 0x64, 0xee, 0xf3, 0x94, 0xfb, 0xfa, 0x16, 0x09, 0xe3, 0xc2, 0x3c, 0x70, 0xf4, 0x46, + 0xdd, 0xc1, 0x9b, 0xf7, 0xa7, 0x96, 0xdc, 0x9e, 0xfd, 0x56, 0xd4, 0x1d, 0xf4, 0x85, 0xcf, 0x44, 0x9e, 0xa8, 0x26, + 0xf2, 0x44, 0xb5, 0xa5, 0x0e, 0xd1, 0x43, 0x24, 0xad, 0x68, 0xc9, 0x69, 0x0b, 0x9b, 0x41, 0x7a, 0x7b, 0x67, 0x8b, + 0x98, 0x33, 0xf8, 0xba, 0x43, 0x4b, 0x1c, 0xa7, 0x86, 0x05, 0x2b, 0x0f, 0xcc, 0x28, 0xed, 0xf0, 0x8a, 0x27, 0xda, + 0x37, 0x3c, 0x61, 0x31, 0xd5, 0x47, 0x64, 0x54, 0x57, 0xe5, 0x91, 0xae, 0xcd, 0xda, 0xf9, 0xe2, 0x6a, 0xc6, 0xb8, + 0xad, 0x0f, 0x9e, 0x7d, 0xab, 0x1a, 0xf4, 0xc5, 0x50, 0x83, 0x71, 0xa1, 0x9c, 0xd7, 0xfa, 0x3b, 0x76, 0xf5, 0x25, + 0x55, 0xb3, 0x57, 0x12, 0x02, 0x8e, 0x32, 0x47, 0x87, 0x83, 0xd2, 0x5d, 0x6c, 0xbe, 0x2b, 0xfa, 0xad, 0xe8, 0x70, + 0x30, 0xf6, 0xe6, 0xaa, 0xbf, 0x97, 0xe9, 0x74, 0x7b, 0x5f, 0x71, 0x3a, 0x1d, 0x8a, 0x33, 0x7b, 0xf2, 0x72, 0x0b, + 0xad, 0xfc, 0xa6, 0xb1, 0x3d, 0xe8, 0x2b, 0x65, 0xc0, 0x12, 0x81, 0x75, 0xfb, 0xb8, 0xad, 0x8f, 0x01, 0xc6, 0xe9, + 0x14, 0x36, 0xa4, 0x6c, 0x62, 0x0c, 0x52, 0xf3, 0xb8, 0x47, 0x9d, 0x41, 0xdf, 0xb7, 0x04, 0x6f, 0x11, 0xcc, 0x23, + 0xf7, 0x5a, 0xd0, 0x38, 0x4a, 0x67, 0xd4, 0x65, 0x69, 0xeb, 0x86, 0x5e, 0x35, 0xfd, 0x39, 0xab, 0xdc, 0xdb, 0xa0, + 0x74, 0x94, 0x43, 0xa6, 0xda, 0x23, 0xae, 0x0e, 0xc9, 0x76, 0x2b, 0x77, 0xdb, 0x11, 0xd8, 0x3c, 0xda, 0x35, 0x27, + 0x7c, 0x72, 0x06, 0x58, 0xe9, 0xa0, 0xdf, 0xf2, 0xd7, 0x30, 0x22, 0xf8, 0x7d, 0xa1, 0x1c, 0xed, 0x60, 0xd8, 0x00, + 0xbd, 0xd9, 0x96, 0x14, 0x07, 0xda, 0x21, 0xaf, 0x04, 0x75, 0x61, 0x0f, 0xfe, 0xf5, 0x7f, 0xfc, 0x2f, 0xe5, 0x63, + 0xef, 0xb7, 0xa2, 0x8e, 0xee, 0x6b, 0x6d, 0x55, 0x8a, 0x3e, 0x1c, 0xe4, 0xaf, 0x82, 0xc2, 0xf4, 0xb6, 0x39, 0xcd, + 0x58, 0xd8, 0x8c, 0xfc, 0x78, 0x62, 0x0f, 0x76, 0x63, 0xd3, 0x3c, 0x5f, 0xab, 0xa0, 0xae, 0x17, 0x01, 0xbd, 0xfe, + 0xaa, 0x13, 0xa2, 0xfa, 0xa0, 0xa1, 0xd8, 0xda, 0xe6, 0x79, 0xd1, 0x6a, 0xf7, 0xd5, 0xce, 0x8c, 0x26, 0xea, 0xe3, + 0x98, 0x8a, 0x03, 0x26, 0xb5, 0xa3, 0xa2, 0x85, 0x6d, 0x95, 0x41, 0xad, 0xff, 0xfb, 0x3f, 0xff, 0xcb, 0x7f, 0xd3, + 0x8f, 0x10, 0xab, 0xfa, 0xd7, 0xff, 0xfe, 0x9f, 0xff, 0xcf, 0xff, 0xfe, 0xaf, 0x70, 0xbc, 0x50, 0xc5, 0xb3, 0x04, + 0x53, 0xb1, 0xaa, 0x60, 0x96, 0xe4, 0x2e, 0x16, 0x64, 0xe0, 0xcf, 0x58, 0xce, 0x59, 0x50, 0x3f, 0x3c, 0x7a, 0x2e, + 0x06, 0x14, 0x3b, 0x53, 0x41, 0x27, 0x76, 0x78, 0x51, 0x11, 0x54, 0x0d, 0xe5, 0x82, 0x70, 0x8b, 0x7e, 0x0b, 0xf0, + 0xfd, 0xb0, 0xf3, 0xf6, 0x6e, 0xb9, 0x1c, 0x4b, 0x4d, 0x26, 0x50, 0x52, 0x54, 0xe5, 0x16, 0xc4, 0x56, 0x96, 0xf0, + 0xe8, 0x75, 0x8d, 0x62, 0xb1, 0x7a, 0xb5, 0x36, 0xbd, 0x9f, 0x16, 0x39, 0x67, 0x13, 0x40, 0xb9, 0xf4, 0x13, 0x8b, + 0x30, 0x76, 0x13, 0x74, 0xc5, 0xf8, 0xae, 0x10, 0xbd, 0x48, 0x02, 0x3d, 0x3a, 0xf9, 0x43, 0xf1, 0xa7, 0x19, 0x68, + 0x64, 0x96, 0x33, 0xf3, 0x6f, 0x95, 0x79, 0xfe, 0xa8, 0xdd, 0x9e, 0xdf, 0xa2, 0x65, 0x35, 0x02, 0xde, 0x35, 0x98, + 0xa0, 0x63, 0xb3, 0x43, 0x11, 0xff, 0x2e, 0xdd, 0xd8, 0x6d, 0x0b, 0x7c, 0xe1, 0x56, 0xbb, 0x28, 0xfe, 0xb8, 0x14, + 0x9e, 0x54, 0xf6, 0x0b, 0xc4, 0xa9, 0x95, 0xd3, 0xf9, 0x2a, 0x35, 0x27, 0xb7, 0x34, 0x5a, 0x75, 0x65, 0xab, 0xa8, + 0xb3, 0x79, 0x8c, 0xdc, 0x8c, 0xb3, 0x9b, 0x11, 0xf2, 0x23, 0x88, 0x79, 0x47, 0x1d, 0x1c, 0x75, 0x97, 0x65, 0xf7, + 0x9c, 0xa7, 0x33, 0x33, 0xb0, 0x4e, 0x7d, 0x1a, 0xd0, 0x89, 0x76, 0xd6, 0xab, 0xf7, 0x32, 0x68, 0x5e, 0x44, 0x87, + 0x5b, 0xc6, 0x52, 0x20, 0x89, 0x80, 0xba, 0xd5, 0x2e, 0x3e, 0x87, 0x1d, 0xb8, 0x9c, 0xc4, 0xa9, 0xcf, 0x3d, 0x41, + 0xb0, 0x3d, 0x33, 0x3c, 0xef, 0x03, 0x4f, 0x4a, 0x97, 0x06, 0x3c, 0x3d, 0x59, 0x15, 0xdc, 0xe6, 0xf5, 0xc3, 0xfe, + 0x85, 0x2b, 0x9a, 0x9b, 0x5d, 0x49, 0xaf, 0xdb, 0x97, 0x2a, 0xea, 0xfd, 0xae, 0xe6, 0xae, 0x52, 0x02, 0xa9, 0x8b, + 0xb6, 0xbf, 0x97, 0x72, 0x5d, 0xbe, 0xfd, 0x86, 0x3b, 0xb6, 0x00, 0xd3, 0x5e, 0xaf, 0x25, 0x0a, 0xa1, 0xd6, 0x3b, + 0xf2, 0x65, 0x69, 0x32, 0xf9, 0xf3, 0xb9, 0xa8, 0x88, 0x7a, 0xfd, 0x96, 0xd4, 0x74, 0x81, 0x7b, 0x88, 0x94, 0x0e, + 0x99, 0x41, 0xa1, 0x2a, 0xa9, 0xad, 0x20, 0x7f, 0xa9, 0xdc, 0x0a, 0xf8, 0x68, 0xea, 0xe0, 0xff, 0x01, 0x0b, 0x95, + 0x29, 0x52, 0xc9, 0x90, 0x00, 0x00}; -} // namespace web_server -} // namespace esphome +#else // Brotli (default, smaller) +const uint8_t INDEX_BR[] PROGMEM = { + 0x1b, 0xc8, 0x90, 0x11, 0x55, 0xb5, 0x2b, 0x2a, 0x8a, 0xaa, 0x55, 0x0f, 0xd0, 0x7a, 0xc0, 0x36, 0x66, 0x21, 0xff, + 0x0b, 0x0b, 0xa3, 0x01, 0x85, 0xcd, 0xc9, 0x64, 0x39, 0xe4, 0x0c, 0x83, 0xa7, 0x5a, 0x3d, 0x8d, 0x21, 0xe6, 0xfa, + 0x1a, 0xa7, 0xef, 0x35, 0x45, 0xd8, 0xd0, 0xc6, 0x30, 0x0a, 0x7e, 0xe2, 0x70, 0x81, 0x7e, 0xd3, 0x30, 0x8a, 0xf7, + 0x08, 0x8d, 0x7d, 0x92, 0xbb, 0x98, 0x33, 0xdf, 0xfb, 0xfc, 0x16, 0x8f, 0x9c, 0x41, 0x68, 0x88, 0x3d, 0x00, 0xca, + 0xb9, 0x8e, 0x1a, 0xab, 0x60, 0xf5, 0x24, 0xe5, 0x28, 0x3b, 0xbf, 0xca, 0x5f, 0xd9, 0x7d, 0x2e, 0xa7, 0xea, 0x32, + 0x41, 0xe7, 0x1b, 0x9e, 0x44, 0xdb, 0xde, 0x6f, 0x16, 0xa9, 0xda, 0xef, 0xbf, 0xbd, 0x27, 0x46, 0x81, 0x70, 0x53, + 0x66, 0x14, 0x4b, 0xb4, 0x26, 0x10, 0xf3, 0x21, 0x4a, 0xe9, 0xc3, 0xed, 0xb0, 0xff, 0xab, 0x2d, 0xff, 0xfb, 0x34, + 0xd5, 0x70, 0xe3, 0xc3, 0xc8, 0x07, 0x19, 0x24, 0xc3, 0x64, 0x31, 0x73, 0x3d, 0x59, 0x96, 0x9d, 0xcc, 0xc3, 0xc2, + 0x08, 0xa3, 0xc4, 0xc8, 0x7e, 0xd2, 0x65, 0x49, 0x64, 0xf7, 0xcd, 0xab, 0xca, 0x5f, 0x54, 0xdf, 0x32, 0x4d, 0xad, + 0xda, 0xc8, 0xf9, 0x38, 0xb8, 0xd1, 0xa2, 0x3f, 0x6a, 0x2c, 0xa4, 0xa8, 0x53, 0x5e, 0x0e, 0x10, 0x01, 0xd2, 0x16, + 0xf3, 0x90, 0x7e, 0x26, 0xbf, 0xd6, 0x2f, 0xfd, 0xb1, 0xaa, 0x36, 0xe0, 0x19, 0x76, 0xdf, 0xac, 0xc4, 0x3e, 0xea, + 0xb5, 0xdc, 0x95, 0xb8, 0x04, 0x4e, 0xbe, 0x4e, 0xce, 0xe4, 0x7c, 0x20, 0x3a, 0x9d, 0x2e, 0xad, 0x20, 0x81, 0xd4, + 0x2a, 0xc0, 0x47, 0xd8, 0x60, 0x9b, 0x3b, 0x34, 0xb2, 0xff, 0xdf, 0x54, 0x3f, 0xdb, 0x01, 0x18, 0x36, 0xa5, 0xa2, + 0xb2, 0x73, 0x1b, 0x92, 0x3a, 0x88, 0x4b, 0x39, 0xe5, 0xa6, 0x70, 0x51, 0x72, 0xee, 0xbd, 0xef, 0xdd, 0xd5, 0x24, + 0x7c, 0x61, 0x06, 0xc4, 0x7e, 0x24, 0x7d, 0x01, 0x04, 0x69, 0xe6, 0x23, 0x26, 0x85, 0xf4, 0xde, 0x9b, 0x01, 0x38, + 0x03, 0x50, 0x5a, 0x80, 0x94, 0x56, 0x71, 0xcf, 0xa1, 0x48, 0xf9, 0x6f, 0x48, 0x49, 0x5e, 0x87, 0x94, 0xaa, 0x26, + 0xe4, 0xce, 0xa7, 0x8f, 0x55, 0xb9, 0xa9, 0x68, 0xdc, 0xb9, 0x74, 0x6b, 0xa7, 0x43, 0x13, 0xe7, 0xec, 0xce, 0x93, + 0x52, 0xd8, 0x63, 0xa8, 0xb5, 0x3a, 0x6f, 0xfe, 0xb7, 0xba, 0x71, 0x46, 0x11, 0x68, 0x7a, 0xdb, 0x50, 0xad, 0xbf, + 0x56, 0x7a, 0xd7, 0x60, 0x86, 0x10, 0x42, 0x13, 0x1c, 0x37, 0x5f, 0xd3, 0x1d, 0x77, 0x77, 0x16, 0xd9, 0xc4, 0x38, + 0xe3, 0x00, 0xf5, 0x14, 0xa4, 0xa1, 0x63, 0x36, 0xdd, 0xac, 0xc6, 0xb9, 0x8d, 0x3c, 0xdf, 0x08, 0x26, 0x6e, 0x0f, + 0x41, 0xb7, 0x86, 0x03, 0x27, 0xd0, 0xc8, 0xf3, 0x4c, 0xfe, 0xe8, 0x03, 0x9b, 0xe3, 0xd7, 0x37, 0x64, 0xd0, 0xcb, + 0x61, 0xad, 0x05, 0xdc, 0x42, 0xb4, 0xbd, 0x0a, 0xca, 0x46, 0x80, 0x9a, 0x55, 0x5f, 0x0f, 0xd9, 0xfb, 0xc6, 0xfb, + 0x6f, 0x77, 0x9f, 0x0d, 0xb1, 0xd2, 0xbf, 0xa7, 0x58, 0xfe, 0x6b, 0x78, 0x6d, 0xd6, 0x25, 0x6b, 0x1d, 0xac, 0x87, + 0x90, 0xf3, 0x30, 0xf1, 0x10, 0x05, 0x6f, 0x61, 0x8e, 0xe3, 0xc6, 0xe3, 0x99, 0x21, 0x63, 0xcb, 0x45, 0xad, 0x07, + 0x66, 0xce, 0xfe, 0xfd, 0xf8, 0x6c, 0x4b, 0x35, 0x38, 0xc7, 0xc5, 0x46, 0xb4, 0xe9, 0x72, 0x49, 0x39, 0xb5, 0xd9, + 0xe5, 0xc8, 0x52, 0xba, 0xfd, 0x77, 0xd7, 0xa6, 0x8a, 0xfb, 0xec, 0xad, 0x6b, 0x39, 0xfc, 0xbc, 0x93, 0x13, 0x25, + 0x10, 0x51, 0xf1, 0x81, 0x22, 0xe5, 0x4a, 0x1d, 0x95, 0xaf, 0x33, 0x95, 0xa5, 0x5f, 0x7e, 0x85, 0x03, 0x41, 0x3c, + 0x20, 0x7a, 0xe3, 0x76, 0xbd, 0x4e, 0x47, 0x66, 0xcc, 0x2b, 0x62, 0x7e, 0xfb, 0xf9, 0xf9, 0x72, 0x61, 0xc0, 0xd7, + 0x10, 0x77, 0x9a, 0xd6, 0x03, 0xd6, 0xed, 0x1b, 0xf7, 0x5f, 0xcb, 0xd9, 0xe5, 0x7e, 0xcb, 0xb6, 0xb6, 0xfc, 0xb9, + 0xe1, 0x31, 0xbd, 0x50, 0x9d, 0xf2, 0x75, 0x1e, 0x16, 0xdc, 0xb4, 0x5b, 0x83, 0xe4, 0xa1, 0x3d, 0x00, 0x9f, 0xab, + 0x66, 0xc4, 0xb7, 0x1b, 0x55, 0x3a, 0xcf, 0x0b, 0x05, 0x89, 0xdb, 0x39, 0x49, 0xeb, 0xf4, 0x0e, 0x55, 0x66, 0x22, + 0x1a, 0xe1, 0x1f, 0x81, 0xac, 0xdc, 0x38, 0xe2, 0x83, 0xa0, 0xd7, 0x43, 0x82, 0x75, 0x84, 0x6a, 0x6a, 0x1e, 0xa5, + 0x0f, 0xef, 0x8d, 0xf5, 0x73, 0xdd, 0x40, 0xfe, 0x9b, 0x19, 0xa2, 0x4c, 0xf8, 0x61, 0xc8, 0x54, 0xb5, 0xdb, 0x76, + 0x21, 0x01, 0x80, 0x2a, 0xd3, 0x11, 0xb3, 0x9e, 0x79, 0x02, 0x9e, 0x0e, 0xe7, 0x32, 0xda, 0x14, 0x78, 0xab, 0x8f, + 0x77, 0xaf, 0x2f, 0x12, 0x87, 0xcb, 0x41, 0xfc, 0x30, 0xac, 0xdd, 0x8a, 0xab, 0xeb, 0x54, 0xc0, 0x17, 0x3b, 0x05, + 0x59, 0x27, 0x05, 0xd2, 0xb2, 0x6b, 0x96, 0x23, 0xf7, 0x20, 0x6f, 0xd8, 0x20, 0xb3, 0xca, 0x7d, 0x2d, 0xdf, 0x7a, + 0xf3, 0x77, 0x2e, 0x0a, 0xa1, 0xc4, 0x5f, 0x1d, 0xb3, 0xad, 0xb1, 0x40, 0x64, 0x48, 0x7f, 0x0f, 0x14, 0x83, 0x13, + 0xb1, 0xc8, 0x2c, 0xcb, 0x14, 0x96, 0x57, 0xc8, 0xa4, 0x6d, 0x5c, 0xaf, 0xc9, 0xb1, 0x15, 0xbd, 0x6a, 0xf0, 0xf0, + 0xf8, 0x25, 0x99, 0x45, 0x0a, 0x19, 0x96, 0x8f, 0x85, 0x91, 0xf2, 0x24, 0xef, 0x43, 0x97, 0xd3, 0x18, 0x3e, 0xc2, + 0xe9, 0xd3, 0x05, 0xa1, 0x2c, 0xc2, 0x94, 0x23, 0x92, 0xbd, 0x8d, 0x8d, 0x66, 0xc7, 0xac, 0x21, 0x84, 0xc9, 0x9f, + 0xf5, 0xaf, 0x73, 0xfc, 0x2b, 0xa6, 0x34, 0x05, 0xb2, 0x04, 0x1f, 0x7e, 0xa9, 0x08, 0x87, 0x08, 0x36, 0xb6, 0x71, + 0x91, 0x3d, 0x43, 0xca, 0x35, 0x90, 0x00, 0x42, 0x15, 0x18, 0xe3, 0xd9, 0x72, 0xee, 0xf8, 0x8c, 0x97, 0xb7, 0x83, + 0xce, 0x36, 0x0a, 0xcb, 0x17, 0x32, 0x8a, 0xd9, 0xd4, 0x0a, 0x28, 0x2f, 0x73, 0xaa, 0xfb, 0x34, 0x9b, 0xb4, 0xbb, + 0x20, 0xeb, 0x0a, 0x21, 0xdf, 0x1b, 0xa1, 0x1a, 0xa3, 0xdd, 0xe1, 0x17, 0x82, 0x5d, 0x99, 0x69, 0x12, 0x35, 0x55, + 0xf8, 0xfd, 0x2f, 0x07, 0x50, 0xf4, 0x2a, 0x1e, 0x57, 0xfa, 0x07, 0xd9, 0x97, 0x32, 0x97, 0x1c, 0xd6, 0xc7, 0xe0, + 0xa5, 0x3a, 0xaf, 0xa6, 0x36, 0x02, 0x05, 0xc4, 0xa8, 0xad, 0x44, 0xf5, 0x37, 0x6b, 0x1a, 0xb0, 0xa3, 0x8c, 0xf5, + 0xd7, 0xe5, 0x08, 0x87, 0x33, 0xd8, 0xe2, 0xd9, 0x52, 0xfe, 0x5a, 0x6f, 0x24, 0x8c, 0x51, 0x9b, 0xa9, 0xf2, 0x82, + 0xf1, 0xed, 0x41, 0x10, 0xd3, 0x68, 0x77, 0x72, 0x06, 0xdf, 0xc8, 0x1d, 0xf5, 0x07, 0xf1, 0x2a, 0xd7, 0x50, 0x0a, + 0x1a, 0x5f, 0x25, 0x90, 0x5b, 0x1b, 0x24, 0x90, 0x06, 0x4b, 0x11, 0x1a, 0xc7, 0x36, 0xc2, 0x3f, 0x2c, 0x55, 0xc1, + 0xbf, 0x1a, 0xcd, 0xf2, 0xed, 0x47, 0xf1, 0xf9, 0xd2, 0x6f, 0x41, 0x8c, 0xcf, 0x2b, 0xf9, 0xc7, 0x56, 0x3a, 0x97, + 0x96, 0x25, 0x83, 0xda, 0x95, 0x25, 0x35, 0x91, 0x8d, 0xe9, 0xe8, 0x7d, 0xa9, 0xa2, 0x05, 0xc4, 0x5a, 0xff, 0xa2, + 0xfa, 0xde, 0xd0, 0x89, 0x26, 0xed, 0x02, 0x1e, 0x29, 0xe8, 0x0e, 0x1e, 0x4c, 0x4f, 0xaf, 0xd6, 0xe6, 0x2d, 0x51, + 0xd4, 0xfe, 0xde, 0x86, 0x3f, 0x4d, 0x53, 0x17, 0x15, 0xf1, 0x25, 0x7b, 0x7e, 0x60, 0x6d, 0xba, 0xdd, 0xd4, 0xa0, + 0xb8, 0xc9, 0x08, 0xb5, 0x1a, 0xae, 0xde, 0x47, 0xac, 0x56, 0x30, 0x5c, 0x12, 0x9e, 0xaa, 0xd9, 0x56, 0x12, 0xbb, + 0x54, 0x50, 0xd6, 0xc7, 0xcd, 0x5d, 0x62, 0xe1, 0xab, 0xc6, 0x64, 0x13, 0x62, 0xf2, 0x9e, 0x20, 0x77, 0x3b, 0x76, + 0x0c, 0x86, 0x05, 0x0f, 0x3c, 0x20, 0x1e, 0xa8, 0xa5, 0x04, 0x03, 0xe3, 0x72, 0xf2, 0x3f, 0xbc, 0xe1, 0xe9, 0xa1, + 0x37, 0x41, 0x22, 0x20, 0xde, 0xa4, 0x2f, 0xf7, 0x0a, 0x82, 0x15, 0x95, 0x15, 0x80, 0x13, 0x35, 0x90, 0xb0, 0x42, + 0x85, 0x81, 0x43, 0xbd, 0x0e, 0xe9, 0xfc, 0x4d, 0xf3, 0xce, 0x0d, 0x18, 0x64, 0x56, 0x2d, 0xa4, 0x5b, 0xd7, 0xe9, + 0x1d, 0xe0, 0x89, 0x82, 0xeb, 0x53, 0x2b, 0x4b, 0x04, 0xe7, 0xa6, 0x97, 0x71, 0x98, 0x22, 0xf7, 0x69, 0x76, 0x9c, + 0x1e, 0x51, 0xd9, 0xdd, 0xae, 0x89, 0x64, 0x98, 0xfc, 0x09, 0xfa, 0xae, 0xd4, 0xdf, 0xe4, 0x54, 0x1b, 0x28, 0x0d, + 0xab, 0xe3, 0xdf, 0xb2, 0x8e, 0x22, 0xc1, 0x1f, 0xc4, 0xdc, 0x40, 0xd8, 0x8b, 0xc1, 0xd4, 0xa5, 0x44, 0x98, 0xd6, + 0x77, 0x05, 0xf6, 0x65, 0x29, 0xc3, 0xc7, 0x86, 0x43, 0xd6, 0xd7, 0x55, 0x9b, 0x1a, 0xe1, 0xeb, 0x09, 0xf9, 0xbc, + 0x77, 0xb2, 0xbe, 0x6e, 0x03, 0x79, 0xac, 0x1c, 0xa6, 0x6f, 0xde, 0x3a, 0x99, 0x2a, 0x70, 0xeb, 0xce, 0x73, 0xd2, + 0xdc, 0x9e, 0xa5, 0xee, 0x3b, 0xb8, 0xc7, 0xcd, 0xa7, 0x95, 0xd3, 0x75, 0x27, 0xaa, 0xd8, 0x78, 0x20, 0x2f, 0x24, + 0xf0, 0x5b, 0x59, 0x4a, 0x6b, 0x41, 0x8d, 0xf8, 0x18, 0xb4, 0xa1, 0xf5, 0x90, 0xe7, 0xd7, 0x33, 0xd4, 0x0c, 0x73, + 0x5a, 0x9c, 0x43, 0x3f, 0x2a, 0x6a, 0x33, 0x20, 0x62, 0xa4, 0x36, 0xa3, 0x3c, 0xaf, 0x82, 0xfb, 0xa5, 0xf5, 0x8f, + 0x98, 0x1b, 0xbe, 0xbe, 0xaf, 0x1f, 0x1a, 0xf3, 0x16, 0xaa, 0x8b, 0xb2, 0x4e, 0x99, 0xf9, 0xc9, 0x21, 0x0b, 0x44, + 0x86, 0x3c, 0x2b, 0xea, 0xe3, 0x7b, 0x6d, 0x05, 0x09, 0xe0, 0x1a, 0x01, 0x3b, 0xee, 0x1e, 0xc4, 0xc6, 0x16, 0x21, + 0x82, 0x0a, 0xed, 0x4e, 0x01, 0x1c, 0x54, 0x90, 0x89, 0x1f, 0xc9, 0xb5, 0xd1, 0x20, 0xaf, 0x5f, 0xa2, 0x1f, 0x2e, + 0x5c, 0x0f, 0xc9, 0xfa, 0x70, 0xc8, 0x20, 0x28, 0xe3, 0x6d, 0xe2, 0x40, 0x82, 0x08, 0x4b, 0x00, 0x3a, 0x36, 0xfe, + 0x4a, 0x25, 0xac, 0x24, 0x3a, 0xe2, 0xae, 0xde, 0x2e, 0x4f, 0x6e, 0x3d, 0x1c, 0xfc, 0x61, 0x95, 0x02, 0xc6, 0xf1, + 0x9e, 0x7f, 0x7e, 0xbf, 0x42, 0x91, 0x82, 0x98, 0x15, 0xc2, 0x21, 0xa7, 0x74, 0x99, 0x88, 0x41, 0xd6, 0x3e, 0xae, + 0x51, 0x73, 0x58, 0xc2, 0x86, 0x88, 0x8a, 0x66, 0xbb, 0x50, 0x2d, 0xea, 0x23, 0xc4, 0xe0, 0x67, 0x33, 0x76, 0xe8, + 0x22, 0x51, 0x49, 0x2b, 0x55, 0x1a, 0xd6, 0xc1, 0x7a, 0x8f, 0x5c, 0x29, 0xf0, 0x41, 0x8d, 0xaf, 0xbe, 0x09, 0x44, + 0xb1, 0x7b, 0x44, 0x6d, 0x17, 0x92, 0xc1, 0xcb, 0x7b, 0xe8, 0x4e, 0xf4, 0xcb, 0x1e, 0x85, 0xac, 0x63, 0x0d, 0xed, + 0x29, 0x4f, 0x64, 0x1e, 0x7b, 0x42, 0x03, 0x25, 0x5a, 0xfe, 0xe8, 0x4c, 0xc9, 0x44, 0x38, 0xcf, 0x3c, 0x1f, 0xae, + 0x2e, 0xf3, 0xd1, 0x87, 0xc7, 0x3c, 0xa4, 0x94, 0x58, 0xf8, 0x84, 0x25, 0x07, 0x74, 0xdd, 0x05, 0x49, 0x01, 0xbc, + 0x6b, 0x8a, 0xa5, 0xfb, 0x51, 0x11, 0x0f, 0x27, 0xeb, 0x69, 0xe6, 0x71, 0xb1, 0x57, 0xaa, 0x38, 0x7a, 0x90, 0x5d, + 0xb8, 0x40, 0xdd, 0xdb, 0x40, 0xd0, 0xb1, 0xc0, 0x2c, 0x81, 0x44, 0x88, 0xf4, 0xde, 0x56, 0xe7, 0x42, 0xc8, 0xeb, + 0x24, 0x33, 0x12, 0x81, 0x5a, 0xe5, 0x6c, 0x02, 0x75, 0xe3, 0x91, 0x22, 0x74, 0x92, 0x92, 0x3c, 0xe1, 0x00, 0xd1, + 0xe3, 0x0a, 0xeb, 0x28, 0x38, 0xc4, 0x75, 0x25, 0x65, 0x4e, 0xfe, 0xcb, 0x94, 0x26, 0x26, 0xbb, 0x72, 0x38, 0x24, + 0x02, 0xa4, 0x74, 0x4b, 0xad, 0x06, 0x9f, 0x45, 0xc4, 0x47, 0x02, 0x30, 0x13, 0x91, 0x28, 0xfc, 0x4b, 0xf7, 0xd2, + 0x33, 0x2f, 0x21, 0xa2, 0x31, 0xd3, 0xa4, 0xb3, 0xe4, 0xed, 0x35, 0xe9, 0xf0, 0x71, 0xa3, 0x93, 0xa8, 0x66, 0xed, + 0x2f, 0xa5, 0x8f, 0x89, 0x2b, 0xf7, 0x8f, 0x02, 0x13, 0x31, 0x9a, 0x9c, 0x53, 0xe9, 0x67, 0x69, 0x71, 0x3e, 0x16, + 0x28, 0x35, 0xaa, 0x2d, 0xbe, 0xbe, 0xad, 0xcf, 0x36, 0x44, 0x9d, 0xb3, 0x4b, 0x1c, 0xf0, 0x74, 0xd5, 0x74, 0xca, + 0x6d, 0x81, 0x0f, 0x2d, 0x93, 0x03, 0x52, 0x74, 0xa7, 0x5d, 0xa2, 0xdb, 0xde, 0x87, 0xe4, 0x30, 0x98, 0xad, 0x80, + 0x03, 0x28, 0xa3, 0x6a, 0x31, 0xb2, 0x1c, 0xc8, 0x62, 0xa9, 0xe4, 0x72, 0x01, 0x40, 0x8b, 0xac, 0x2b, 0xa7, 0x0c, + 0x85, 0xca, 0x69, 0x64, 0x09, 0x07, 0xd5, 0xc6, 0x48, 0xe6, 0x5a, 0x7d, 0x65, 0x08, 0x69, 0xd4, 0x5c, 0x03, 0x73, + 0xa0, 0x50, 0xb3, 0x64, 0xdd, 0x45, 0xa9, 0x56, 0xe1, 0xb9, 0x30, 0x40, 0x9e, 0x3f, 0xae, 0x36, 0xeb, 0x2e, 0x3b, + 0x2f, 0x4e, 0xc5, 0x0b, 0x0a, 0x1b, 0x1e, 0x24, 0xbb, 0x12, 0x27, 0x25, 0x08, 0x9c, 0xa2, 0xa6, 0xb1, 0x57, 0xdc, + 0x7f, 0x25, 0x7f, 0x3f, 0xa4, 0x92, 0x74, 0x2a, 0xa3, 0x18, 0xf1, 0xf4, 0xab, 0x2a, 0xeb, 0x1a, 0x6d, 0x80, 0x94, + 0xbf, 0x77, 0xe9, 0xc8, 0x7a, 0xd3, 0x55, 0x46, 0xaf, 0x4c, 0x9d, 0x55, 0xf8, 0x71, 0x3e, 0x19, 0xd3, 0xe9, 0x8b, + 0xb8, 0xaa, 0x13, 0x47, 0x01, 0x45, 0x20, 0xec, 0xf1, 0xe3, 0x2b, 0xe5, 0xd1, 0x5e, 0x09, 0x58, 0xb2, 0x8d, 0xc1, + 0x9a, 0x54, 0x47, 0x4c, 0x48, 0x5a, 0xde, 0x7d, 0x04, 0xc6, 0x4a, 0x15, 0x45, 0x17, 0xe0, 0xc3, 0x07, 0x94, 0x1e, + 0x14, 0x1a, 0xc7, 0x3c, 0xe5, 0x36, 0x64, 0x98, 0x80, 0x81, 0x1e, 0x07, 0x79, 0x76, 0xfc, 0xc9, 0x55, 0x15, 0xea, + 0x40, 0x3f, 0x2c, 0xd9, 0xd9, 0xb2, 0xb0, 0xbc, 0xca, 0x8e, 0xfd, 0xa7, 0x28, 0xba, 0xae, 0x7b, 0x62, 0x09, 0x47, + 0x7a, 0xdf, 0x8a, 0xdc, 0xc4, 0x82, 0xf3, 0xd5, 0x4a, 0x88, 0xe5, 0x09, 0xc3, 0x00, 0x69, 0x39, 0x66, 0xca, 0x40, + 0x0e, 0x1d, 0x81, 0x91, 0x0c, 0x99, 0x56, 0x77, 0xf8, 0xf4, 0x2d, 0x7e, 0xc0, 0x21, 0x93, 0x94, 0x9c, 0x69, 0x72, + 0xdc, 0x8b, 0x62, 0xb0, 0x2b, 0x43, 0x54, 0x40, 0xe3, 0x6a, 0x3a, 0x85, 0x21, 0x59, 0xea, 0x7d, 0xa5, 0x5b, 0x6a, + 0x3d, 0x82, 0xbb, 0xf3, 0x44, 0x0a, 0x76, 0x40, 0xd5, 0xcb, 0xe8, 0x8c, 0x63, 0x01, 0x84, 0xf4, 0x24, 0xc9, 0x5d, + 0x52, 0x0c, 0xb2, 0x89, 0x14, 0x0a, 0xac, 0x2f, 0x3b, 0x8c, 0x69, 0x31, 0x7d, 0x3f, 0x08, 0x9c, 0x2c, 0x75, 0x49, + 0x04, 0xe9, 0xf3, 0x60, 0x77, 0x49, 0xf1, 0x08, 0x95, 0x8f, 0xbd, 0xfb, 0x59, 0x0a, 0x4a, 0x53, 0x9d, 0xe4, 0x09, + 0x82, 0xf6, 0x1c, 0x18, 0x1d, 0x13, 0x30, 0x1f, 0x48, 0x45, 0x7d, 0x38, 0xad, 0x1e, 0x0b, 0xbb, 0x0f, 0x29, 0xee, + 0xcb, 0xec, 0xe5, 0x2f, 0xe6, 0x73, 0xa4, 0x39, 0x33, 0x74, 0x52, 0xa7, 0x90, 0xcc, 0x66, 0xf9, 0xa5, 0x28, 0x91, + 0xe6, 0xbd, 0xb7, 0x87, 0x23, 0xfd, 0x80, 0xdf, 0x17, 0x82, 0x1b, 0xc0, 0x1c, 0x46, 0xf0, 0x55, 0x17, 0xc5, 0x6e, + 0x96, 0x6d, 0x48, 0xa1, 0xb5, 0xa3, 0x19, 0x2e, 0xd9, 0xee, 0x8d, 0x24, 0x66, 0xad, 0xc8, 0x84, 0x7a, 0xaf, 0x74, + 0x64, 0x6a, 0x3f, 0x2c, 0x61, 0x6b, 0xa5, 0x62, 0x7a, 0x1e, 0xc6, 0xb0, 0x0e, 0x32, 0xc8, 0x08, 0x2a, 0x2b, 0x5c, + 0x30, 0xd4, 0x50, 0x9c, 0x94, 0xf3, 0x06, 0x91, 0xec, 0x1c, 0x4c, 0x27, 0xa6, 0xa1, 0x14, 0x8b, 0x18, 0xd3, 0x43, + 0xb7, 0x79, 0x8f, 0x62, 0x12, 0xf4, 0xfb, 0xb2, 0x3b, 0x9e, 0x3c, 0xc0, 0xcc, 0x04, 0x4e, 0x2d, 0x0d, 0xb2, 0x94, + 0xe1, 0x5c, 0xdb, 0x5f, 0xf1, 0xc3, 0x75, 0x1f, 0x7a, 0x40, 0x74, 0xbf, 0x0f, 0xf2, 0xa1, 0x5e, 0xad, 0x31, 0x18, + 0xe4, 0xb0, 0x50, 0xfa, 0xde, 0x34, 0x84, 0x87, 0x1d, 0x18, 0xcd, 0xc1, 0x32, 0x4c, 0x67, 0x59, 0x4b, 0xae, 0x6c, + 0x55, 0x4d, 0xec, 0x82, 0x6e, 0xd4, 0xba, 0x74, 0xa4, 0x9f, 0x44, 0x2a, 0x76, 0x3d, 0xc7, 0xbd, 0x16, 0xb8, 0xdb, + 0xb6, 0xbc, 0x1c, 0x8b, 0x71, 0x32, 0x23, 0xd2, 0xa8, 0x7e, 0x5a, 0x40, 0x76, 0xac, 0x23, 0x0a, 0x9e, 0x26, 0x23, + 0x1c, 0x06, 0xff, 0x83, 0xcd, 0xad, 0x23, 0xbc, 0x78, 0x2d, 0x74, 0x08, 0xad, 0x6d, 0xb8, 0x6d, 0xe9, 0xee, 0x13, + 0x44, 0xff, 0x5d, 0x27, 0x20, 0x33, 0x81, 0x0a, 0x39, 0x51, 0x1d, 0xe5, 0x54, 0xc5, 0x70, 0xd0, 0xe9, 0xd6, 0x34, + 0x56, 0x69, 0x62, 0xdd, 0xc5, 0x87, 0xe8, 0xd3, 0x0e, 0x48, 0x51, 0x5d, 0x01, 0x93, 0x45, 0xf5, 0x9b, 0x10, 0x80, + 0x8a, 0x2d, 0x33, 0x30, 0x31, 0x2f, 0x67, 0x5a, 0xda, 0xff, 0x2a, 0x2e, 0x59, 0xc5, 0xf2, 0x2f, 0x49, 0xe1, 0xf1, + 0x31, 0x4a, 0x19, 0x58, 0x6b, 0x40, 0xd4, 0xb5, 0x5e, 0xee, 0xd5, 0x45, 0xce, 0xb8, 0xa6, 0xe0, 0xc1, 0xd7, 0xee, + 0x57, 0x75, 0xc3, 0x1f, 0x3f, 0x6a, 0x38, 0xf0, 0x05, 0xa9, 0x46, 0x63, 0x01, 0xe9, 0x7e, 0x17, 0xa3, 0xc2, 0x6c, + 0xbf, 0xac, 0x07, 0x49, 0x22, 0x2a, 0x4f, 0x00, 0x7f, 0x5f, 0xaf, 0x42, 0xb7, 0x06, 0x44, 0xdc, 0x42, 0x1e, 0xcf, + 0x7a, 0x9a, 0xe4, 0x8e, 0x30, 0x45, 0xe2, 0xeb, 0xf7, 0x11, 0xa2, 0x32, 0x99, 0xde, 0xfc, 0x33, 0x6b, 0xc4, 0x29, + 0x4b, 0x60, 0x72, 0x0b, 0x4a, 0xea, 0x1c, 0xf2, 0x28, 0x23, 0x7d, 0xaa, 0x5e, 0xf2, 0x9b, 0x34, 0x07, 0x32, 0x69, + 0x83, 0x4c, 0x11, 0x88, 0x4e, 0x0f, 0xd2, 0x28, 0xfa, 0x20, 0x00, 0xee, 0x20, 0x08, 0xb4, 0x04, 0x81, 0x00, 0x3e, + 0xd2, 0x3b, 0x09, 0x34, 0x21, 0x93, 0xfc, 0x1a, 0x96, 0xa7, 0x2a, 0x95, 0xdb, 0xd4, 0x6e, 0x9c, 0x2d, 0x11, 0x9d, + 0x0a, 0x3a, 0x28, 0x66, 0xa1, 0xdf, 0x1b, 0xbf, 0x25, 0x1d, 0x7a, 0x5f, 0xa2, 0xc2, 0xd8, 0xd3, 0x34, 0xf3, 0x2c, + 0x50, 0xb2, 0x50, 0xf7, 0x7b, 0xb8, 0xff, 0x58, 0x10, 0xde, 0x25, 0x14, 0x64, 0x78, 0xa8, 0x67, 0x8a, 0x14, 0xf7, + 0x70, 0x02, 0x27, 0xe5, 0xc7, 0x8a, 0xcc, 0xde, 0xd7, 0xfc, 0x9e, 0xfe, 0x4c, 0xa0, 0x36, 0xe6, 0x0f, 0x5e, 0x3a, + 0xe6, 0x7c, 0x19, 0x17, 0x18, 0x27, 0x11, 0x07, 0x9a, 0xa2, 0xc0, 0x0e, 0x27, 0x7a, 0xde, 0xf1, 0x62, 0x1d, 0xe2, + 0xae, 0xdc, 0x3c, 0xe8, 0xad, 0xa7, 0x3a, 0x64, 0x5c, 0xcf, 0x44, 0xd6, 0xb6, 0xa8, 0xdf, 0x7f, 0xbf, 0xfa, 0x9e, + 0x1e, 0x5f, 0x8e, 0xe7, 0xa4, 0x4e, 0x51, 0x81, 0x66, 0x5a, 0x78, 0x10, 0xfe, 0x31, 0x99, 0x99, 0xc7, 0xc0, 0x07, + 0x26, 0x63, 0x74, 0xcc, 0xc8, 0x83, 0xf5, 0xb7, 0x82, 0xbc, 0xd8, 0x41, 0x7e, 0xa7, 0x90, 0xfc, 0xe4, 0xc3, 0x0c, + 0x69, 0x44, 0x41, 0x50, 0xa5, 0x3e, 0xa0, 0x50, 0x26, 0x96, 0xfd, 0xf7, 0x96, 0xf6, 0x6d, 0x72, 0x60, 0x12, 0xcb, + 0xe3, 0x6c, 0x31, 0x1e, 0xb3, 0x38, 0xe7, 0x40, 0x7f, 0x2c, 0x59, 0x78, 0x2f, 0x3c, 0x1d, 0xf3, 0xb0, 0x36, 0xf3, + 0xce, 0xc6, 0x04, 0xf0, 0x36, 0xd6, 0x96, 0x7e, 0xc7, 0xcd, 0xfa, 0x71, 0xe8, 0xa0, 0x07, 0xad, 0x3d, 0x74, 0xc0, + 0xca, 0xd3, 0x13, 0x28, 0x8a, 0x6d, 0xc1, 0xd7, 0xdb, 0x3b, 0xac, 0x65, 0x14, 0x3b, 0x64, 0xab, 0xf9, 0xba, 0x2d, + 0xad, 0xc7, 0x28, 0xa9, 0xbf, 0x32, 0xeb, 0x83, 0xb1, 0x7b, 0xf8, 0x01, 0xbb, 0xfa, 0xf5, 0xd5, 0xea, 0x1a, 0x1f, + 0xcf, 0xbb, 0x3a, 0x28, 0x7d, 0xf0, 0x2e, 0x2e, 0x99, 0xcb, 0x4a, 0xcd, 0xe3, 0x2e, 0x45, 0xec, 0x0f, 0x82, 0x67, + 0x11, 0xdd, 0xda, 0x41, 0x1a, 0x7c, 0xbf, 0x14, 0xc1, 0x06, 0xab, 0x7a, 0xa5, 0x15, 0x70, 0xa4, 0xe2, 0xce, 0x3e, + 0x51, 0x88, 0x62, 0xb6, 0x37, 0x10, 0xf1, 0xfc, 0x53, 0xfd, 0xf9, 0x3e, 0xc3, 0x57, 0x05, 0x1f, 0x90, 0x41, 0x86, + 0xcd, 0x86, 0x4b, 0xb6, 0xe2, 0xf2, 0x69, 0x18, 0x90, 0x77, 0x03, 0xbf, 0xf5, 0xdc, 0x5f, 0xc3, 0x7d, 0x64, 0xa9, + 0xe5, 0x5f, 0x20, 0x12, 0x51, 0xf8, 0x8d, 0x62, 0x22, 0xb8, 0x27, 0x08, 0x78, 0x52, 0xc9, 0x10, 0x8b, 0x75, 0xa0, + 0x6b, 0x9c, 0x1e, 0x5e, 0x0f, 0xd3, 0x59, 0x5f, 0x78, 0x9c, 0xbb, 0x36, 0xab, 0xbc, 0xca, 0x75, 0xd7, 0xb6, 0xf6, + 0x6c, 0x89, 0xf8, 0x19, 0x49, 0xac, 0x5a, 0xce, 0xcf, 0x28, 0xb6, 0x0f, 0x98, 0xca, 0xbf, 0x0d, 0xba, 0xb8, 0x23, + 0x4d, 0xae, 0x87, 0xdd, 0xfe, 0xc2, 0x56, 0x55, 0x2c, 0x1e, 0x3f, 0x7a, 0xf3, 0x6e, 0xf3, 0xc5, 0xf5, 0x81, 0x6f, + 0x69, 0x72, 0x69, 0x7f, 0x66, 0x36, 0x49, 0x92, 0xee, 0xe7, 0x64, 0x71, 0xa1, 0x8e, 0x7f, 0x3f, 0xf1, 0x49, 0xc7, + 0xc2, 0x98, 0x97, 0x5d, 0x2d, 0x66, 0x4a, 0x2b, 0xf9, 0x3b, 0xdf, 0xdf, 0x7e, 0xff, 0xec, 0x51, 0x8c, 0x6d, 0xc5, + 0xb9, 0x6d, 0x92, 0x24, 0x79, 0x96, 0x6b, 0xe1, 0x7b, 0x34, 0xd4, 0x47, 0xda, 0xe3, 0xc6, 0xcb, 0x7c, 0x89, 0xc2, + 0x6a, 0xd6, 0x3c, 0x63, 0xb6, 0x7e, 0x5e, 0x7e, 0xf7, 0xd8, 0xd9, 0xe4, 0x7a, 0x13, 0xcb, 0x91, 0xf0, 0x2d, 0xbe, + 0x65, 0x0b, 0x66, 0xba, 0x80, 0xe4, 0x2f, 0xe5, 0xee, 0xf1, 0x5d, 0x71, 0xf9, 0xc3, 0xae, 0x17, 0x66, 0x96, 0x73, + 0x2c, 0x31, 0x96, 0x28, 0x1e, 0xb8, 0x39, 0xdf, 0x81, 0x35, 0x69, 0x72, 0xbe, 0xad, 0x78, 0x8d, 0x73, 0x90, 0xfb, + 0x7b, 0xfa, 0x1f, 0x3f, 0x39, 0xda, 0xdc, 0xc5, 0xc7, 0x11, 0x74, 0x41, 0x62, 0xb7, 0x0b, 0xd6, 0x6e, 0x8a, 0x57, + 0x13, 0xc3, 0x4d, 0x54, 0x95, 0x44, 0x3d, 0x31, 0x1b, 0x01, 0x96, 0x4b, 0x5f, 0x0f, 0x38, 0xd0, 0x4d, 0x27, 0xca, + 0x22, 0xff, 0x6b, 0xec, 0x22, 0x65, 0x40, 0xf8, 0x97, 0x52, 0x48, 0x70, 0xaa, 0xb7, 0x24, 0x25, 0xb8, 0xe6, 0x3b, + 0x56, 0xe5, 0xff, 0x0d, 0x64, 0xc2, 0x6c, 0x26, 0xc2, 0x8a, 0xec, 0x7e, 0xf5, 0xdb, 0x33, 0x82, 0xa9, 0xe7, 0x22, + 0x66, 0x6d, 0xf7, 0xec, 0xd3, 0xb0, 0x67, 0x93, 0x37, 0x22, 0x0b, 0x38, 0x67, 0x08, 0x9c, 0x3c, 0xe3, 0xca, 0xe2, + 0xe4, 0x96, 0xe5, 0x37, 0x77, 0xb9, 0x27, 0x7a, 0x21, 0x91, 0x48, 0x31, 0xab, 0x68, 0xe2, 0x58, 0x01, 0x48, 0x5a, + 0x7d, 0xf8, 0x71, 0xd4, 0xf7, 0x1f, 0x58, 0xaf, 0xd5, 0xdb, 0xb8, 0x4e, 0xc7, 0x88, 0xf8, 0x68, 0x38, 0x6e, 0x21, + 0x78, 0xb9, 0x4a, 0x4d, 0xf4, 0x19, 0xb7, 0xe4, 0x15, 0xa3, 0xde, 0x90, 0xee, 0xac, 0xce, 0x7b, 0x3a, 0xb7, 0xf3, + 0x65, 0x9b, 0x28, 0x54, 0x33, 0x34, 0x83, 0x41, 0x81, 0xf8, 0x38, 0x5f, 0xab, 0x91, 0x44, 0xdf, 0x11, 0xea, 0x84, + 0xbf, 0x5d, 0xb2, 0x48, 0x88, 0x69, 0x36, 0x3b, 0xf9, 0x75, 0xee, 0xa2, 0x9a, 0x48, 0xd2, 0x3b, 0xe7, 0x10, 0x64, + 0xfa, 0x39, 0x83, 0x44, 0x0a, 0x27, 0x18, 0x43, 0x19, 0xc5, 0xb5, 0x59, 0x2e, 0x6a, 0xa1, 0x8a, 0xcf, 0xeb, 0xd9, + 0xb0, 0x53, 0x22, 0xd9, 0x4a, 0x14, 0xeb, 0x7c, 0x6d, 0x4f, 0xae, 0xa9, 0xb7, 0x5c, 0x0f, 0x19, 0xc5, 0x75, 0xf2, + 0xfc, 0xaa, 0x56, 0xb1, 0x51, 0x13, 0xf0, 0xa7, 0x94, 0xe2, 0xc0, 0x86, 0xdb, 0xbc, 0x25, 0x52, 0x7a, 0x56, 0xd6, + 0xdf, 0x3a, 0x49, 0x88, 0xef, 0xce, 0x4d, 0x2b, 0x7b, 0x43, 0x84, 0x87, 0x24, 0x81, 0xdc, 0xa8, 0xb5, 0xa6, 0x72, + 0xd7, 0xed, 0x33, 0x5f, 0x15, 0x56, 0x75, 0xfb, 0xd7, 0x6b, 0x37, 0xe0, 0x10, 0xf8, 0x00, 0x02, 0x21, 0xee, 0x25, + 0xb3, 0xcb, 0x61, 0x73, 0x04, 0x09, 0xf4, 0x19, 0xf0, 0x07, 0x2f, 0x5a, 0xa1, 0xe0, 0xad, 0xed, 0xc0, 0x03, 0x00, + 0x40, 0x6e, 0x35, 0x56, 0x3c, 0xdb, 0xc5, 0x7c, 0x47, 0xfe, 0x30, 0x55, 0x6b, 0xbe, 0x6e, 0x75, 0xd7, 0x05, 0x38, + 0x2f, 0x30, 0x9b, 0x7f, 0x2c, 0x55, 0xdb, 0x75, 0xc4, 0x69, 0x1a, 0x14, 0x98, 0xa8, 0xd2, 0xab, 0x4c, 0x95, 0xa4, + 0x56, 0x95, 0xa2, 0xe5, 0x61, 0x4d, 0xbb, 0xfa, 0x3c, 0x3e, 0xe6, 0x83, 0xb5, 0x26, 0x10, 0x5a, 0x07, 0x2f, 0xdd, + 0xdb, 0x9b, 0x96, 0x74, 0x6e, 0xb4, 0x4a, 0x9e, 0x68, 0xf3, 0x55, 0xe9, 0x5d, 0x05, 0xb7, 0x4c, 0xf9, 0x22, 0x23, + 0x0e, 0x61, 0x8d, 0xf0, 0x5f, 0xf5, 0x61, 0x68, 0x5b, 0x28, 0xb2, 0x7f, 0x1e, 0x88, 0xe0, 0xa9, 0x92, 0xbd, 0xcc, + 0x6c, 0xf3, 0x88, 0x0c, 0x61, 0x78, 0xe7, 0x12, 0x17, 0xc4, 0xf2, 0x53, 0x5d, 0x79, 0x66, 0xed, 0xee, 0x4d, 0x64, + 0x8b, 0x6c, 0xbc, 0xe3, 0x41, 0xc7, 0x3c, 0xaf, 0x2b, 0xd8, 0x61, 0xa3, 0xbd, 0x49, 0xb3, 0xc7, 0x79, 0x9b, 0xb2, + 0x8c, 0xd5, 0xc1, 0x0b, 0x59, 0x27, 0xc4, 0x24, 0x2d, 0x2a, 0x45, 0xe0, 0x7c, 0x80, 0xd6, 0x1e, 0x4f, 0x77, 0x3f, + 0x25, 0x7e, 0x6d, 0x2e, 0x2c, 0x36, 0xe8, 0x4b, 0x07, 0xbb, 0x9f, 0x79, 0xc4, 0x86, 0x11, 0x5b, 0x90, 0x7f, 0xdb, + 0x06, 0x10, 0xbb, 0x47, 0x94, 0xb2, 0x1b, 0xd1, 0xa3, 0x54, 0x3f, 0xe1, 0x55, 0x3e, 0x50, 0x81, 0x34, 0x66, 0x48, + 0x69, 0xc5, 0x15, 0x27, 0x92, 0xa0, 0x77, 0xb0, 0x04, 0x49, 0x0e, 0xec, 0x7b, 0x82, 0x88, 0xe8, 0x87, 0x9c, 0x8a, + 0x5c, 0xc5, 0x53, 0xcf, 0xa6, 0xdb, 0x23, 0xa3, 0x24, 0x48, 0xee, 0xf1, 0xa3, 0xe0, 0xbb, 0xbd, 0x89, 0xe2, 0x6e, + 0xf3, 0x44, 0xf0, 0xe6, 0x09, 0x26, 0x82, 0xf3, 0x02, 0xf9, 0x98, 0x5d, 0x3c, 0x81, 0x44, 0x25, 0x42, 0xbf, 0xe4, + 0xec, 0xe8, 0xdf, 0x65, 0xa9, 0xb7, 0xda, 0xeb, 0x50, 0x18, 0xb4, 0x2d, 0xa7, 0xd6, 0x38, 0xee, 0xb0, 0x94, 0x5d, + 0x34, 0x77, 0x59, 0xb2, 0x2c, 0x6b, 0x2f, 0xa3, 0x11, 0x1a, 0xa9, 0x79, 0xf8, 0x5c, 0x6a, 0x59, 0xd1, 0xf1, 0xa2, + 0x16, 0xe1, 0xc0, 0x10, 0x4b, 0xa5, 0xb3, 0x9c, 0x62, 0xb5, 0x5d, 0x18, 0x59, 0x8e, 0x23, 0x97, 0x2b, 0x09, 0x28, + 0x9a, 0x43, 0x44, 0x99, 0x0c, 0x1c, 0x47, 0x0b, 0x53, 0x29, 0x30, 0x66, 0x16, 0x1e, 0xec, 0x7c, 0x9b, 0x91, 0xeb, + 0x68, 0x24, 0x5f, 0xf8, 0x86, 0x14, 0xe3, 0x83, 0xb4, 0xd7, 0xd9, 0x88, 0x64, 0xe4, 0x80, 0x3d, 0x97, 0xa9, 0x08, + 0xab, 0x38, 0xaa, 0x77, 0x73, 0x8d, 0xeb, 0x46, 0x65, 0xe4, 0x8a, 0xf6, 0x3a, 0xb2, 0x58, 0x68, 0xc1, 0x7a, 0x7e, + 0xc9, 0xbc, 0xfb, 0x1c, 0x60, 0x6b, 0xa3, 0xac, 0x76, 0xe9, 0x02, 0xcd, 0xf9, 0x58, 0x53, 0xf8, 0x3b, 0x19, 0x81, + 0xbf, 0x71, 0xc2, 0x4e, 0xb3, 0x9f, 0xf7, 0xae, 0x91, 0x59, 0xf6, 0x32, 0xde, 0x32, 0x8f, 0x4e, 0x1d, 0x27, 0xb7, + 0x4e, 0x13, 0xc3, 0x98, 0x61, 0xc9, 0xf7, 0x07, 0xb8, 0xc4, 0x7c, 0x47, 0x80, 0x9d, 0xdf, 0xf3, 0x5c, 0xd2, 0x4e, + 0xe9, 0x80, 0xb4, 0x64, 0xf3, 0xc4, 0x4d, 0xd6, 0x5b, 0x93, 0x9f, 0x53, 0x8a, 0x95, 0x0b, 0xfe, 0x5a, 0xbf, 0xb2, + 0x9e, 0x94, 0xef, 0xd3, 0xe1, 0xad, 0x37, 0x33, 0xf9, 0xc1, 0x56, 0x98, 0xb0, 0x77, 0x40, 0x07, 0x5f, 0xc7, 0x7a, + 0x0b, 0x1f, 0x1f, 0xd8, 0xd1, 0x92, 0x84, 0x4e, 0x15, 0x5a, 0xd5, 0x51, 0xc8, 0xf8, 0xe8, 0x3e, 0x77, 0x1a, 0x89, + 0x45, 0x52, 0x2c, 0xa0, 0xf3, 0xad, 0xda, 0xfb, 0x27, 0xd4, 0xfc, 0xa8, 0x35, 0x99, 0xa2, 0xe9, 0xcc, 0xa9, 0x73, + 0xf5, 0xd7, 0xd4, 0x97, 0xed, 0x58, 0x07, 0x33, 0x71, 0xfd, 0x2d, 0xba, 0x56, 0x5f, 0x73, 0x9f, 0xa5, 0x20, 0x35, + 0x65, 0xd9, 0xc5, 0x89, 0xea, 0xcc, 0xab, 0x51, 0xa4, 0x53, 0xf1, 0xf1, 0x36, 0x72, 0xc7, 0x8b, 0x91, 0xd8, 0xc2, + 0x3b, 0xf8, 0x0a, 0x4b, 0xa2, 0xde, 0x6f, 0x44, 0x7c, 0x4c, 0xa8, 0x19, 0xbe, 0x43, 0x15, 0x38, 0x6b, 0x81, 0x81, + 0x92, 0x9c, 0xa8, 0xdb, 0x4e, 0xc5, 0xd9, 0x19, 0xbc, 0x34, 0x62, 0x57, 0x24, 0xa1, 0x43, 0x3e, 0x43, 0x7d, 0x05, + 0x1f, 0xed, 0xb3, 0x82, 0x1b, 0x4c, 0x2d, 0xf8, 0xb5, 0x8c, 0x62, 0x66, 0xfa, 0xdc, 0x35, 0xc4, 0x20, 0xfa, 0x9e, + 0x96, 0x66, 0x48, 0x19, 0xab, 0x48, 0xd5, 0x34, 0x9e, 0x83, 0xf7, 0x02, 0x23, 0x62, 0xba, 0x6c, 0x77, 0x36, 0xbf, + 0x6e, 0x23, 0xc8, 0x12, 0x5d, 0x75, 0x9b, 0xc8, 0x24, 0x06, 0xce, 0xb1, 0x26, 0xc4, 0x87, 0x30, 0x43, 0x90, 0x0b, + 0xdd, 0xeb, 0x64, 0xa4, 0xd2, 0x27, 0x5c, 0xa4, 0xa3, 0x8e, 0x1e, 0xee, 0x66, 0x59, 0xbb, 0x61, 0xd7, 0xac, 0xfe, + 0x77, 0xde, 0xb5, 0xc3, 0xeb, 0xd3, 0xa0, 0xc8, 0x13, 0x6a, 0xb0, 0x40, 0x7a, 0x84, 0xbf, 0xf9, 0xf1, 0x50, 0x22, + 0xd3, 0xaf, 0x8f, 0x53, 0x21, 0x33, 0xce, 0x81, 0x03, 0xc8, 0x74, 0x4a, 0xff, 0xce, 0xbe, 0xac, 0xac, 0x60, 0xa4, + 0x0d, 0x7c, 0x6b, 0x06, 0xdb, 0x39, 0x59, 0x88, 0x8e, 0xd3, 0x6e, 0x86, 0xe8, 0xa1, 0x2d, 0x3b, 0xfd, 0x08, 0xad, + 0x53, 0x92, 0x96, 0xfd, 0x10, 0xc1, 0xca, 0x6a, 0x9f, 0x24, 0xba, 0x97, 0x46, 0xc0, 0xae, 0x5f, 0x65, 0x49, 0x2c, + 0x48, 0xa3, 0xff, 0xcc, 0xbc, 0xd2, 0x8d, 0x08, 0x0f, 0x6b, 0xc8, 0x00, 0x36, 0xc4, 0xea, 0x1e, 0xbb, 0x29, 0x2c, + 0xbc, 0xb5, 0x29, 0xd0, 0x99, 0x66, 0x8e, 0x06, 0x01, 0xdb, 0xcb, 0x7d, 0x8c, 0x34, 0x94, 0x3f, 0x5b, 0x89, 0x2d, + 0x47, 0x9a, 0xe2, 0xa3, 0xff, 0x78, 0x6d, 0x9a, 0x62, 0x91, 0x3a, 0x5f, 0xac, 0x27, 0x99, 0xe0, 0xbf, 0xa9, 0x9e, + 0x13, 0xcf, 0x3e, 0x32, 0xba, 0xef, 0xbf, 0x5e, 0x47, 0x56, 0x9e, 0x4f, 0x1c, 0x5f, 0xb5, 0x32, 0x39, 0x5f, 0xb1, + 0xcd, 0x41, 0x05, 0x6c, 0x7e, 0xe0, 0xe7, 0xac, 0x70, 0x6c, 0xc8, 0x3f, 0x9a, 0xf9, 0x08, 0xe3, 0x6a, 0x85, 0xef, + 0xda, 0x8b, 0x87, 0xb6, 0x62, 0x52, 0x24, 0x2d, 0xa8, 0xa4, 0x81, 0x54, 0x23, 0x2b, 0x12, 0x64, 0x18, 0xb9, 0x40, + 0xaf, 0xf8, 0x14, 0x4f, 0xcc, 0x96, 0x24, 0x3c, 0x14, 0xdd, 0xe2, 0x19, 0xa1, 0x42, 0xf0, 0xdf, 0x07, 0x64, 0x22, + 0x90, 0x04, 0x98, 0xeb, 0x08, 0x75, 0x94, 0xc4, 0x13, 0x9e, 0x1e, 0x24, 0xe8, 0x24, 0xe8, 0xe5, 0x5b, 0x21, 0x22, + 0x2a, 0x5f, 0x7d, 0xdd, 0x24, 0x68, 0x56, 0x82, 0x10, 0xcb, 0x19, 0x4b, 0x22, 0xb8, 0xd6, 0x8c, 0xde, 0xfd, 0x88, + 0x52, 0xdd, 0x6d, 0x48, 0x63, 0xf9, 0x0f, 0x23, 0x52, 0xe5, 0x9b, 0x9f, 0x71, 0x75, 0x19, 0xed, 0x19, 0xb9, 0x0b, + 0x54, 0x68, 0xf6, 0x09, 0x19, 0xfa, 0x18, 0xab, 0x16, 0xd1, 0x07, 0xf3, 0xb6, 0xa1, 0xb8, 0xc5, 0x14, 0x9b, 0xbb, + 0xb5, 0x7e, 0xce, 0x7e, 0xcc, 0x1f, 0x90, 0x50, 0x9e, 0x0a, 0x38, 0xc4, 0xc4, 0x2f, 0xc2, 0x06, 0x7d, 0x38, 0xef, + 0xd5, 0x48, 0x75, 0x7f, 0xbf, 0xed, 0xc3, 0x3c, 0x16, 0xe5, 0x27, 0xd4, 0xf6, 0x0c, 0x28, 0xa8, 0x94, 0x51, 0xa0, + 0xd6, 0x80, 0xb1, 0x0b, 0x4e, 0xe4, 0xfc, 0x34, 0x2c, 0x00, 0xea, 0x2b, 0x56, 0x4d, 0x31, 0x18, 0x23, 0x18, 0xe9, + 0x36, 0xc0, 0x0e, 0xa9, 0xd3, 0xc2, 0xc1, 0xbc, 0xfe, 0xf3, 0xa5, 0x0b, 0x0b, 0x84, 0x82, 0x37, 0xc9, 0xd2, 0x9a, + 0x29, 0xf4, 0x13, 0x04, 0xa6, 0xe0, 0x53, 0x79, 0xd4, 0x93, 0x78, 0xdf, 0x3f, 0x5f, 0x57, 0x89, 0x14, 0xf8, 0xb9, + 0xf0, 0x86, 0x08, 0x2b, 0xa6, 0x88, 0x79, 0xc4, 0x67, 0xd6, 0x92, 0x7d, 0xe1, 0x7d, 0xeb, 0x81, 0x3a, 0x05, 0x3c, + 0x73, 0xdf, 0x27, 0xf7, 0x42, 0xe0, 0x0f, 0x57, 0x1f, 0x3e, 0x99, 0x1f, 0xcd, 0x80, 0x55, 0x3d, 0xb2, 0x98, 0x84, + 0xa9, 0x28, 0xb3, 0x84, 0x20, 0x6e, 0x2a, 0x81, 0x85, 0x61, 0x5c, 0x8d, 0x9b, 0x8f, 0x75, 0xeb, 0x29, 0x13, 0x00, + 0x6a, 0x49, 0x42, 0xf7, 0x0c, 0x65, 0xcc, 0xec, 0xa5, 0x15, 0xa0, 0xdc, 0x73, 0x75, 0xf2, 0x72, 0x68, 0x8f, 0x61, + 0xe0, 0x50, 0xb6, 0x36, 0x98, 0xc6, 0x89, 0xc8, 0x32, 0x10, 0x20, 0x0e, 0xe5, 0xd7, 0xb9, 0xd2, 0xba, 0xea, 0x46, + 0x4c, 0x5d, 0x87, 0x7a, 0x3b, 0x47, 0xc7, 0x42, 0xdc, 0xfb, 0x99, 0x1e, 0x01, 0xb6, 0xd3, 0xf7, 0xf2, 0x03, 0x27, + 0x15, 0x02, 0xaf, 0x84, 0xca, 0x03, 0x89, 0xec, 0x81, 0x76, 0x50, 0x36, 0x00, 0x92, 0xdc, 0x16, 0x57, 0x0a, 0xd2, + 0x16, 0x20, 0x66, 0x36, 0xfe, 0xa7, 0x1f, 0x34, 0x8a, 0x03, 0xf2, 0xbe, 0x0f, 0x92, 0x92, 0xc6, 0xb3, 0x50, 0x6d, + 0x9c, 0x48, 0x11, 0xcc, 0xe0, 0x61, 0x11, 0x34, 0x22, 0x53, 0xa1, 0x73, 0x2b, 0xd8, 0xc6, 0xef, 0x5e, 0x9b, 0x47, + 0xa2, 0xc2, 0xf4, 0x37, 0xff, 0x74, 0x65, 0xdd, 0xa2, 0x48, 0xcb, 0xef, 0x71, 0xdd, 0xc7, 0xf8, 0xff, 0x06, 0x45, + 0x49, 0x92, 0xcd, 0x5e, 0x2a, 0x99, 0xce, 0xd8, 0xf3, 0x2b, 0xad, 0x8f, 0x16, 0xed, 0x81, 0x7d, 0xc3, 0x7b, 0xd0, + 0xdc, 0x03, 0xe1, 0x87, 0x92, 0x56, 0x9b, 0xfa, 0x84, 0xaa, 0x0a, 0x32, 0x24, 0x1a, 0xe3, 0x22, 0xb5, 0xa6, 0x4c, + 0xb5, 0x5b, 0xec, 0x06, 0x90, 0x4c, 0x63, 0x54, 0xd1, 0xe4, 0xbe, 0x79, 0x66, 0xf3, 0xc2, 0x3d, 0x91, 0x46, 0xd3, + 0x05, 0xb9, 0xfc, 0x2c, 0x39, 0xca, 0x94, 0x92, 0x25, 0xb1, 0x0c, 0x87, 0xf3, 0x10, 0x61, 0xae, 0x35, 0x44, 0x54, + 0x2a, 0x8c, 0x37, 0x4c, 0x4a, 0x13, 0x2b, 0xf9, 0x2d, 0x4a, 0x84, 0x45, 0xb0, 0xfa, 0xb4, 0xad, 0x03, 0x5c, 0x9b, + 0x83, 0x72, 0x84, 0x7b, 0xcb, 0x13, 0x6c, 0x6a, 0x9d, 0x07, 0xe7, 0x51, 0xae, 0x8a, 0x43, 0xa1, 0x4e, 0xdb, 0x07, + 0x54, 0xde, 0x44, 0xe8, 0x0c, 0x99, 0xb0, 0x35, 0x1e, 0x63, 0x90, 0x1b, 0x8f, 0x37, 0xd1, 0x0d, 0xcd, 0x87, 0x89, + 0x8a, 0xfa, 0x44, 0x5e, 0x26, 0xa0, 0xaa, 0xde, 0xa4, 0xf7, 0x53, 0xf2, 0xd3, 0x28, 0xa2, 0xc8, 0x9d, 0xe4, 0x84, + 0xca, 0x5a, 0x12, 0x15, 0x05, 0xb6, 0xf0, 0x24, 0x09, 0x09, 0xa0, 0xb8, 0x1b, 0xe3, 0x50, 0x85, 0xfc, 0xae, 0xca, + 0xe1, 0x5d, 0x8f, 0xea, 0xd0, 0xd2, 0x25, 0x90, 0xe4, 0x67, 0x32, 0xe9, 0x8f, 0x79, 0xef, 0x3e, 0x93, 0x87, 0xf7, + 0x23, 0x85, 0xb9, 0x8f, 0xf1, 0x1a, 0x66, 0x21, 0x2e, 0xff, 0xf6, 0xf3, 0xa2, 0x17, 0x1f, 0x25, 0x9d, 0x20, 0x33, + 0x54, 0xae, 0x8d, 0xf7, 0x4d, 0x23, 0x52, 0x55, 0xd4, 0x42, 0x20, 0x9f, 0xdc, 0xfd, 0x7a, 0x06, 0xa5, 0x8c, 0xe6, + 0x72, 0xaa, 0x5e, 0x27, 0xe5, 0x36, 0x7e, 0x18, 0x1a, 0xa2, 0xd7, 0xe5, 0x68, 0x53, 0xc0, 0x12, 0x6d, 0xf3, 0xf0, + 0xcf, 0x07, 0xab, 0xc8, 0x97, 0xe3, 0xa6, 0xd8, 0xa7, 0x8a, 0xc5, 0x9c, 0x7b, 0x7f, 0xb7, 0xc6, 0x03, 0x61, 0x7f, + 0x4a, 0x22, 0x99, 0x08, 0x02, 0x92, 0xb2, 0x05, 0x9e, 0x81, 0x43, 0x29, 0x5d, 0x9a, 0xa8, 0x35, 0x38, 0x94, 0xd4, + 0x9c, 0x7d, 0x4f, 0x2a, 0x9f, 0x3e, 0x2f, 0x11, 0x7e, 0x65, 0x5e, 0x30, 0x97, 0xd5, 0x48, 0x5b, 0x11, 0x2d, 0xc7, + 0x12, 0x2d, 0x80, 0x5a, 0xa8, 0x0b, 0x75, 0x53, 0x01, 0xc1, 0xf8, 0x5a, 0xef, 0x0f, 0x91, 0x51, 0x85, 0xe2, 0x29, + 0x4a, 0xe9, 0x68, 0x97, 0xaf, 0xb8, 0x7d, 0xe9, 0x84, 0x29, 0x7c, 0xc3, 0x11, 0x47, 0xe4, 0x99, 0xee, 0x9b, 0x76, + 0xb9, 0x69, 0x45, 0xff, 0x80, 0x08, 0xf1, 0x6e, 0xbe, 0xd4, 0xb9, 0x31, 0x39, 0x82, 0x2b, 0x82, 0x66, 0x8b, 0x83, + 0xa7, 0xf2, 0x0f, 0xd7, 0x65, 0x21, 0x5d, 0x13, 0xe5, 0x48, 0x22, 0x3f, 0x64, 0x06, 0x5a, 0x01, 0x89, 0x35, 0x61, + 0x44, 0x0e, 0x66, 0x0b, 0x00, 0xbd, 0x36, 0x47, 0xb7, 0xda, 0xa8, 0x2e, 0x5b, 0x00, 0x5b, 0xfa, 0x0a, 0x46, 0x86, + 0x42, 0xe8, 0x88, 0xe1, 0x40, 0x46, 0xd4, 0x27, 0x95, 0x81, 0x2c, 0x3a, 0xc7, 0x02, 0x94, 0x79, 0x9f, 0x82, 0xbc, + 0x71, 0x12, 0x25, 0x24, 0x8d, 0x33, 0xf3, 0x49, 0x9d, 0x9d, 0x94, 0x83, 0xac, 0xa5, 0x90, 0x9e, 0x54, 0x37, 0xa8, + 0x5c, 0x2b, 0x7a, 0x25, 0xf4, 0x50, 0x72, 0x27, 0x36, 0x83, 0xd7, 0x5c, 0x19, 0xc5, 0x2f, 0x2d, 0xff, 0x32, 0xa1, + 0x61, 0x51, 0x9d, 0x40, 0x07, 0x7a, 0x09, 0xad, 0x21, 0xe6, 0xff, 0x5d, 0xb9, 0x77, 0x1d, 0xa4, 0x5b, 0xc7, 0x40, + 0xcb, 0x79, 0xab, 0xd4, 0xb3, 0x50, 0xd1, 0xad, 0xed, 0x99, 0xd4, 0x56, 0x15, 0x07, 0xdb, 0x98, 0x16, 0x64, 0xde, + 0xc1, 0xe7, 0x94, 0x0e, 0xa9, 0x8f, 0xb6, 0x71, 0xcd, 0x15, 0xd9, 0x83, 0xa5, 0xaf, 0xb1, 0x32, 0xa7, 0x0f, 0xc3, + 0x81, 0x17, 0x93, 0xb9, 0xb1, 0xe3, 0x6f, 0x84, 0x55, 0x2b, 0xd5, 0x46, 0xc4, 0xec, 0x10, 0x60, 0xaa, 0x1a, 0x8d, + 0x35, 0xef, 0xc3, 0x64, 0xa1, 0x9f, 0x65, 0xae, 0x00, 0xe5, 0x88, 0x49, 0xbd, 0xb2, 0xec, 0x85, 0xd6, 0x83, 0xef, + 0x97, 0x57, 0x57, 0xb2, 0xec, 0x5a, 0xa7, 0x87, 0xc0, 0x09, 0xe0, 0x4d, 0x41, 0xd5, 0x1a, 0x2f, 0xee, 0xdb, 0x0b, + 0xaf, 0xad, 0x0b, 0x52, 0x12, 0xf0, 0x8e, 0x92, 0xc1, 0x57, 0x9e, 0x06, 0x82, 0xe6, 0x7b, 0xe5, 0x7e, 0x62, 0x46, + 0x24, 0x72, 0xc7, 0xed, 0x19, 0x1f, 0xcf, 0xc3, 0x95, 0xa1, 0xf8, 0x65, 0x6c, 0x4d, 0x6b, 0x81, 0xf9, 0x83, 0x04, + 0x96, 0x13, 0xb5, 0x5b, 0x9f, 0x8b, 0x79, 0x22, 0xd8, 0x29, 0x0a, 0xd4, 0x23, 0x54, 0x0c, 0x27, 0x81, 0xa2, 0x91, + 0x16, 0x98, 0xc3, 0x58, 0xe7, 0x70, 0x0b, 0x08, 0xa9, 0x53, 0x20, 0xe8, 0x6f, 0x47, 0x02, 0x8c, 0xfc, 0x41, 0x91, + 0x13, 0x4f, 0x9a, 0x9b, 0x35, 0x08, 0xfc, 0x7d, 0x38, 0x54, 0xa7, 0xed, 0xe5, 0x27, 0xdc, 0x81, 0x9b, 0xd8, 0x33, + 0x8e, 0x9f, 0xc5, 0xfd, 0x26, 0x27, 0x91, 0x73, 0x28, 0xaa, 0xf2, 0x39, 0x87, 0xc4, 0x4c, 0x1c, 0xea, 0x70, 0xfb, + 0x20, 0x5d, 0x5b, 0xc0, 0x70, 0x71, 0x98, 0xc6, 0x5e, 0x1d, 0x25, 0x20, 0x95, 0x7c, 0x74, 0x37, 0x9f, 0x7e, 0xf6, + 0x51, 0x3d, 0x88, 0xf6, 0x21, 0xe2, 0x6f, 0x6d, 0x49, 0xa3, 0x50, 0x79, 0x38, 0xb7, 0xbe, 0xa4, 0x86, 0x8f, 0x10, + 0x87, 0x7f, 0x2f, 0x16, 0xc5, 0x40, 0xec, 0x36, 0xb9, 0xe6, 0x82, 0x41, 0xef, 0x24, 0x03, 0xa1, 0xf5, 0x66, 0x98, + 0xca, 0x55, 0xb3, 0x2d, 0xac, 0x4c, 0x3b, 0x83, 0x0f, 0x36, 0xb6, 0xc5, 0x09, 0x08, 0xa2, 0x95, 0x41, 0xb7, 0x84, + 0x09, 0x4b, 0x8c, 0x29, 0xf4, 0x2d, 0x31, 0xcc, 0x79, 0x16, 0x95, 0xc4, 0x02, 0x4c, 0x47, 0x6b, 0xb6, 0xf4, 0x2b, + 0xa6, 0x2b, 0x9d, 0x89, 0xde, 0xb4, 0x41, 0xa6, 0x92, 0x66, 0x16, 0xc0, 0xdf, 0x64, 0x67, 0xd9, 0xe0, 0x1f, 0xd0, + 0xda, 0x95, 0x22, 0x31, 0x21, 0xdd, 0x82, 0x17, 0x95, 0x95, 0x9a, 0x37, 0x2e, 0x94, 0xfb, 0x35, 0xdf, 0xb4, 0xad, + 0x15, 0x82, 0xc3, 0x3a, 0x24, 0x1f, 0x58, 0x80, 0xe5, 0x72, 0x29, 0x2e, 0x55, 0x3b, 0x82, 0xa1, 0xb4, 0x95, 0xe4, + 0xc3, 0x22, 0x43, 0xd2, 0xe3, 0x13, 0x0d, 0x91, 0x90, 0x33, 0x9e, 0xb3, 0x35, 0xe0, 0xe4, 0xee, 0xce, 0x6a, 0xa6, + 0xf5, 0xa7, 0xdd, 0x78, 0xcf, 0x4b, 0x10, 0x93, 0x66, 0x0a, 0xbc, 0x27, 0xbb, 0x81, 0xb4, 0xdb, 0x2c, 0x36, 0xfa, + 0x9b, 0x6e, 0x69, 0x80, 0xee, 0xb6, 0x83, 0x01, 0x0c, 0x8c, 0x30, 0x0b, 0x2e, 0xbc, 0xa0, 0x6b, 0xff, 0xe0, 0x18, + 0xf0, 0x48, 0x81, 0xb3, 0x62, 0x48, 0x19, 0xe2, 0x6e, 0x6c, 0xf3, 0x63, 0xb6, 0x58, 0xbc, 0xfe, 0xfa, 0x3d, 0x32, + 0xa0, 0x2e, 0x70, 0x04, 0x1f, 0x68, 0x19, 0xa9, 0x34, 0x70, 0x4a, 0x2a, 0x3f, 0xda, 0x3b, 0x93, 0x6c, 0x65, 0x6a, + 0x85, 0xb0, 0xaa, 0x06, 0x9b, 0x1a, 0xd0, 0x66, 0x96, 0x96, 0xb6, 0xa5, 0x16, 0x98, 0xdb, 0xde, 0x0b, 0x30, 0xf9, + 0x02, 0x7a, 0xc0, 0xf2, 0x9f, 0xa1, 0x7b, 0x18, 0x4d, 0x2c, 0xe3, 0x5e, 0x10, 0x17, 0x55, 0x10, 0x40, 0x7a, 0x5b, + 0x8f, 0x20, 0x69, 0x5a, 0x63, 0xa2, 0xc3, 0xa2, 0xbb, 0x11, 0xb0, 0x0a, 0x2d, 0x31, 0x02, 0x7b, 0xc8, 0x8d, 0xa9, + 0x58, 0x3a, 0xf2, 0xab, 0x05, 0x16, 0x3e, 0x0f, 0x62, 0x5f, 0x93, 0xae, 0x97, 0xa5, 0x06, 0x62, 0xf2, 0x68, 0xeb, + 0x8d, 0x7e, 0x48, 0x8f, 0x76, 0x5d, 0xe3, 0x7d, 0xd4, 0x44, 0x60, 0x65, 0x2a, 0xb7, 0x87, 0xd9, 0x76, 0xfd, 0xd5, + 0x92, 0x02, 0xd5, 0xcc, 0x59, 0x16, 0xfe, 0xf6, 0xfe, 0x69, 0x3f, 0x01, 0x2e, 0xdf, 0xf1, 0xae, 0xe7, 0x80, 0xb0, + 0x1c, 0x9d, 0x55, 0x72, 0xdd, 0x6e, 0x6b, 0xff, 0x33, 0x3e, 0x34, 0x86, 0x92, 0x61, 0xfb, 0x9f, 0xee, 0x4e, 0xd7, + 0x23, 0x15, 0x0e, 0xa3, 0xc0, 0x51, 0xf8, 0xde, 0x7b, 0xcf, 0xab, 0x95, 0x3a, 0xce, 0xb2, 0x5f, 0xfb, 0xd6, 0xd4, + 0xeb, 0x64, 0x1b, 0xd6, 0x28, 0xbe, 0x1d, 0x23, 0x1b, 0x7b, 0xc1, 0xc8, 0xda, 0x18, 0xab, 0x7b, 0x04, 0x6b, 0x8f, + 0x6b, 0x8a, 0xe1, 0x6e, 0x05, 0xdf, 0x6f, 0xf7, 0xb8, 0x96, 0xd3, 0x39, 0xdd, 0x99, 0x6f, 0xdb, 0x2f, 0x7f, 0x72, + 0x96, 0x16, 0x1e, 0x34, 0x6f, 0xca, 0x65, 0x96, 0x2e, 0xab, 0xe4, 0x5a, 0x20, 0x4f, 0x36, 0x9d, 0x8b, 0x7a, 0xfd, + 0x79, 0xaf, 0x11, 0x66, 0xb0, 0x77, 0x17, 0xf1, 0xf6, 0x3e, 0xba, 0x9b, 0xcb, 0xa9, 0xcf, 0xbb, 0x45, 0x43, 0x08, + 0xe5, 0xe6, 0x95, 0xd3, 0xd6, 0x1b, 0xc7, 0x1c, 0x0f, 0xf8, 0xb0, 0x78, 0xef, 0x90, 0x13, 0x42, 0x6d, 0xf0, 0xeb, + 0x09, 0xee, 0x3e, 0xc4, 0x93, 0x6d, 0x7f, 0x4e, 0xdc, 0xe6, 0x0c, 0x11, 0xb6, 0xc8, 0xc3, 0xd2, 0x94, 0x74, 0x5c, + 0x03, 0x1b, 0xee, 0xce, 0x0a, 0x99, 0xb9, 0xf8, 0x95, 0xfb, 0xc6, 0x2d, 0x1c, 0x7d, 0x4f, 0xc8, 0x21, 0xcb, 0x32, + 0x6d, 0xde, 0x82, 0xbe, 0xb0, 0x99, 0xe5, 0x69, 0x1a, 0x93, 0xe5, 0x0f, 0x23, 0xdc, 0x15, 0x72, 0xd7, 0x5c, 0x45, + 0xcb, 0x69, 0x96, 0x8a, 0xba, 0x67, 0x1c, 0xb7, 0x38, 0xe3, 0x20, 0xbe, 0x07, 0x33, 0xfd, 0x7e, 0x8d, 0x6c, 0x68, + 0x5e, 0xfb, 0x07, 0x9e, 0x65, 0xe0, 0xf4, 0x5f, 0x6d, 0x54, 0xa7, 0x72, 0x9e, 0x03, 0xa0, 0x64, 0x89, 0xfe, 0x74, + 0x1c, 0xd2, 0x86, 0x42, 0x18, 0x15, 0xee, 0xbe, 0xfc, 0x68, 0x6f, 0x79, 0x15, 0x13, 0xd1, 0x1e, 0x3d, 0xe9, 0xce, + 0x08, 0x57, 0xc4, 0x5b, 0x46, 0x03, 0x28, 0xc6, 0x82, 0x0e, 0x14, 0x52, 0x56, 0x7b, 0x34, 0x67, 0x43, 0x9c, 0x79, + 0x9e, 0x54, 0x91, 0x2e, 0x02, 0xd6, 0x77, 0xc5, 0xa1, 0x9e, 0xdc, 0xab, 0xc0, 0xcb, 0xbe, 0x60, 0x1d, 0xea, 0x01, + 0xdc, 0x6f, 0x8a, 0x14, 0x1f, 0x69, 0xeb, 0x97, 0x5c, 0x31, 0xba, 0xb6, 0x4a, 0xc6, 0xfa, 0x6e, 0x8c, 0x68, 0xc4, + 0xe4, 0xaa, 0x26, 0x2c, 0xa7, 0x31, 0x8a, 0x46, 0x81, 0xe4, 0x9c, 0x1a, 0xc7, 0x38, 0x1d, 0x58, 0x4f, 0x22, 0x29, + 0x5d, 0x40, 0xc8, 0x2c, 0xc9, 0xf4, 0xa0, 0x01, 0x96, 0x64, 0xa4, 0x0d, 0x2a, 0xef, 0xa1, 0xa3, 0x71, 0xcf, 0x32, + 0x68, 0xee, 0x50, 0x57, 0x15, 0xae, 0xdd, 0xf2, 0x20, 0x53, 0x31, 0xb7, 0x66, 0x53, 0xfd, 0xb8, 0x1c, 0x44, 0x76, + 0x4d, 0xbb, 0x76, 0xdb, 0x67, 0x03, 0x2a, 0xb8, 0x81, 0x0c, 0x07, 0x29, 0xfb, 0x90, 0xd1, 0x23, 0x72, 0x67, 0x49, + 0xf7, 0xf9, 0x81, 0x42, 0xbf, 0x53, 0x07, 0x04, 0x18, 0xf9, 0x4a, 0x68, 0x87, 0x0d, 0x77, 0xea, 0xd0, 0x79, 0xdb, + 0x63, 0x22, 0x47, 0xe1, 0xf0, 0x2a, 0x49, 0xdf, 0x13, 0x6d, 0x47, 0x37, 0xee, 0xfb, 0xe3, 0x80, 0x9f, 0x94, 0xa6, + 0x88, 0x5a, 0x93, 0xd4, 0xe9, 0x62, 0xb9, 0x25, 0x9a, 0x0c, 0xfd, 0x8d, 0xe2, 0xb3, 0xe0, 0xc2, 0xc3, 0x12, 0xb7, + 0x1b, 0x0a, 0x5c, 0x89, 0xab, 0x72, 0x1f, 0x5f, 0x09, 0x68, 0x5c, 0x27, 0xe8, 0xfa, 0x8c, 0xa3, 0x3a, 0x18, 0x43, + 0x25, 0x66, 0x6f, 0xb0, 0x82, 0xb2, 0xaa, 0x47, 0x1c, 0x63, 0xeb, 0x67, 0x34, 0x37, 0x1e, 0x63, 0xd2, 0xb8, 0x9c, + 0x71, 0x44, 0xfa, 0xc8, 0x8c, 0x54, 0x86, 0x29, 0xbc, 0x3d, 0x72, 0x74, 0xa7, 0x76, 0xd3, 0xe5, 0x82, 0x66, 0x8c, + 0xca, 0xa0, 0x5f, 0xbc, 0x84, 0xd9, 0xc2, 0x12, 0x3c, 0x88, 0xab, 0x8b, 0x73, 0x6b, 0x17, 0x1f, 0x1d, 0x2a, 0xcc, + 0xc7, 0x36, 0x9f, 0x2f, 0x53, 0x45, 0xae, 0x84, 0x61, 0xea, 0xa7, 0xe9, 0xc5, 0x75, 0xa7, 0x92, 0xf6, 0x8b, 0xb0, + 0xfa, 0x9a, 0x19, 0x0f, 0xd8, 0x77, 0xdb, 0x10, 0x6d, 0xbf, 0x2f, 0x59, 0xaf, 0x2b, 0x53, 0x69, 0x7f, 0x6c, 0xee, + 0xd6, 0x64, 0xb7, 0xdb, 0x69, 0xdf, 0xa1, 0x13, 0x65, 0x90, 0xff, 0xfe, 0xcd, 0x7e, 0xe5, 0x4b, 0x4b, 0xfd, 0xa9, + 0xd0, 0x58, 0x1e, 0xb9, 0xf9, 0xb5, 0x1b, 0x55, 0x1d, 0x36, 0x94, 0xbb, 0x4e, 0xc7, 0xc8, 0x9d, 0x7d, 0x35, 0xd1, + 0xc4, 0x37, 0x4f, 0xf7, 0x73, 0xb1, 0xdc, 0x5f, 0x7d, 0xb4, 0xb7, 0x8f, 0xa4, 0x5c, 0xa4, 0x7c, 0xc9, 0x5e, 0xf3, + 0x94, 0xed, 0x22, 0x95, 0x11, 0xa0, 0x8c, 0xde, 0x48, 0x6f, 0x12, 0x9a, 0x26, 0xa9, 0x46, 0xfe, 0xe4, 0xf7, 0xf0, + 0xad, 0xba, 0x7b, 0xfd, 0x93, 0xa5, 0xbd, 0x13, 0x22, 0x1e, 0x00, 0xfe, 0x5b, 0xbe, 0xfd, 0xa9, 0xd8, 0xcd, 0x5c, + 0x56, 0x5b, 0x3e, 0xf0, 0xad, 0xb0, 0xaf, 0x8c, 0x87, 0x7e, 0x25, 0x55, 0x94, 0x7e, 0x95, 0x70, 0x89, 0x91, 0xb1, + 0xc9, 0x47, 0x75, 0xd3, 0x7a, 0xdc, 0x7b, 0x7d, 0x47, 0x00, 0x45, 0xf8, 0xc7, 0xc2, 0x18, 0xef, 0x21, 0x51, 0x38, + 0x15, 0x62, 0x98, 0x96, 0x9a, 0xca, 0x01, 0xd8, 0x34, 0xfa, 0x35, 0x8a, 0x53, 0xa9, 0xc8, 0x8f, 0xdf, 0x1f, 0x59, + 0xf9, 0xcd, 0x6d, 0xfe, 0xff, 0xdb, 0xcf, 0xde, 0x23, 0x14, 0x5a, 0x40, 0xd2, 0x7d, 0x14, 0xc9, 0x27, 0x11, 0xa8, + 0xb4, 0x72, 0x58, 0x8d, 0xb5, 0x1d, 0x24, 0x5a, 0x35, 0xad, 0xea, 0xc3, 0x17, 0x8f, 0xa1, 0xa7, 0x68, 0xa6, 0x14, + 0x51, 0xa9, 0xf2, 0x06, 0x09, 0xa1, 0x9e, 0xc6, 0xa7, 0x89, 0x4a, 0x85, 0xfc, 0x72, 0xb3, 0xfe, 0xe9, 0x8e, 0x49, + 0x10, 0x96, 0x73, 0x60, 0x88, 0xf4, 0x90, 0x3c, 0xa0, 0xc2, 0xee, 0x7c, 0x4c, 0xa3, 0x3a, 0xa5, 0x4f, 0x47, 0xf0, + 0x76, 0xaa, 0x69, 0xbb, 0x56, 0x5e, 0xcb, 0x2e, 0x91, 0x50, 0x82, 0xfb, 0x48, 0x33, 0x87, 0x0e, 0x1a, 0xa5, 0xa3, + 0xfb, 0xde, 0x3f, 0x09, 0x19, 0xba, 0xc0, 0xd0, 0xfb, 0xed, 0x03, 0x3f, 0xb6, 0xd5, 0xa0, 0xc7, 0x70, 0xd1, 0xb6, + 0xe8, 0x55, 0x5f, 0x16, 0x5d, 0x26, 0xb7, 0x97, 0xa2, 0x01, 0x92, 0x64, 0x7a, 0x16, 0x44, 0xe6, 0x7e, 0x21, 0x67, + 0x1d, 0xcf, 0x4f, 0x71, 0x2f, 0x1e, 0x53, 0x25, 0xa3, 0x1b, 0xfe, 0xea, 0x34, 0xf2, 0xbd, 0x89, 0xc4, 0xc7, 0x24, + 0x16, 0x92, 0x6d, 0x8c, 0xa0, 0xd7, 0x62, 0x78, 0x6e, 0x2c, 0xfd, 0x8d, 0x28, 0x50, 0x1a, 0x04, 0x58, 0x3a, 0x8f, + 0x94, 0x81, 0x2b, 0x36, 0xca, 0xdf, 0x6d, 0x68, 0x98, 0x52, 0x9b, 0x07, 0x44, 0xde, 0x65, 0xbf, 0x28, 0x32, 0xde, + 0x40, 0x24, 0x08, 0x46, 0x2a, 0xb8, 0x09, 0x52, 0xd0, 0x98, 0x2e, 0x30, 0x5b, 0x40, 0xb6, 0x38, 0x6e, 0x80, 0xcb, + 0x57, 0x8a, 0xe9, 0x52, 0xf5, 0xce, 0xe6, 0xaa, 0xe2, 0xc7, 0xf0, 0xbe, 0x5b, 0xeb, 0x20, 0x3e, 0x38, 0x11, 0x74, + 0x45, 0x69, 0xd2, 0xd3, 0x47, 0x26, 0xc9, 0x5a, 0x94, 0x2d, 0x46, 0x0f, 0x1a, 0x06, 0x2a, 0x2a, 0xac, 0x36, 0xd6, + 0x28, 0xf6, 0x2b, 0xba, 0x20, 0x8c, 0xc2, 0x17, 0xed, 0xc6, 0xc8, 0x7b, 0x97, 0x66, 0x5e, 0xbb, 0x1b, 0x47, 0xad, + 0xc8, 0x8b, 0x63, 0x5e, 0x73, 0x14, 0xd9, 0x1d, 0x26, 0x79, 0xfc, 0x55, 0x11, 0x05, 0xc3, 0x64, 0x64, 0x7b, 0xec, + 0xdb, 0x22, 0x33, 0x11, 0x22, 0xb6, 0x7e, 0xeb, 0x9d, 0x7d, 0x1b, 0x85, 0xb8, 0xe7, 0x23, 0xe1, 0xfe, 0x92, 0xe4, + 0x2a, 0xe0, 0x65, 0x5e, 0x73, 0xe8, 0x37, 0xe6, 0xd4, 0x50, 0xa9, 0xd1, 0x10, 0xf0, 0x2b, 0x95, 0x78, 0x40, 0x26, + 0xa8, 0x9c, 0xd8, 0x75, 0x1f, 0x5c, 0x46, 0x40, 0x87, 0xcb, 0xda, 0x68, 0xe6, 0xd3, 0xf7, 0xc8, 0xb5, 0x9d, 0xd6, + 0x47, 0x1a, 0x51, 0xe0, 0xe5, 0x56, 0x65, 0x70, 0xa0, 0x4f, 0xa5, 0xac, 0xbc, 0x20, 0x8a, 0x4e, 0xb4, 0x15, 0x1c, + 0x16, 0xb7, 0xc1, 0xbf, 0x47, 0x58, 0x2c, 0xb9, 0xe7, 0xb8, 0x01, 0xc8, 0x39, 0x8b, 0xc8, 0x46, 0x05, 0xf1, 0xef, + 0x00, 0x3b, 0x32, 0xe6, 0x1a, 0xc9, 0xb2, 0x86, 0xa9, 0x88, 0xb6, 0xf7, 0x11, 0x91, 0x6e, 0x87, 0x0b, 0x73, 0x8a, + 0x5e, 0x8c, 0x6f, 0x9b, 0x55, 0xb4, 0xe0, 0x01, 0xaa, 0xe0, 0xf3, 0x59, 0x70, 0x88, 0x95, 0x5f, 0xe1, 0xbe, 0xd9, + 0x10, 0x4d, 0xe0, 0x3c, 0x99, 0x07, 0x9d, 0x8b, 0x76, 0x22, 0xd7, 0xcf, 0x15, 0xb9, 0x97, 0xe4, 0x1b, 0x58, 0xaf, + 0x2c, 0xdd, 0x37, 0x8b, 0x79, 0x1a, 0x58, 0x81, 0x7e, 0x58, 0x84, 0x34, 0x70, 0x56, 0x99, 0xce, 0x3a, 0x7a, 0xaa, + 0x79, 0x22, 0x10, 0x12, 0xc0, 0x02, 0x03, 0x29, 0xfd, 0x35, 0xec, 0xde, 0x47, 0xa0, 0x11, 0xec, 0x14, 0x98, 0x61, + 0xde, 0x4f, 0x24, 0x0d, 0x6d, 0x53, 0xf5, 0x23, 0x1d, 0xd8, 0xbd, 0xb2, 0x57, 0xd8, 0x40, 0x07, 0xcb, 0xdf, 0xe7, + 0xdc, 0xf0, 0xd2, 0xdd, 0x7c, 0xfb, 0x57, 0x89, 0xd4, 0x72, 0x6d, 0xb5, 0xc0, 0xbf, 0x13, 0xeb, 0x73, 0x21, 0xc8, + 0x3e, 0xef, 0xcb, 0x08, 0x2b, 0x6a, 0x1c, 0x35, 0x9f, 0xb5, 0x17, 0xb5, 0xfc, 0x59, 0x09, 0x08, 0xce, 0xbd, 0x25, + 0xf1, 0x6e, 0x08, 0x1e, 0x77, 0x2e, 0xc9, 0xde, 0xd0, 0xe3, 0x49, 0x1f, 0xb2, 0xf2, 0xb1, 0x83, 0xd9, 0x42, 0x26, + 0xf3, 0x1d, 0x2a, 0x8a, 0x03, 0xf1, 0x46, 0x29, 0x3c, 0xc7, 0xdf, 0xcd, 0xd2, 0x04, 0x29, 0x79, 0xa5, 0xbf, 0x15, + 0x6f, 0x8c, 0x30, 0x1f, 0x63, 0x03, 0x07, 0xa3, 0x00, 0x91, 0xbf, 0x45, 0x83, 0x2a, 0x94, 0x70, 0xb4, 0x10, 0xa7, + 0xa1, 0xea, 0x25, 0x62, 0xdf, 0x95, 0x0f, 0xd5, 0xec, 0xab, 0x7e, 0xa2, 0x4e, 0xd7, 0x99, 0xb5, 0x37, 0x08, 0x85, + 0x6e, 0xb3, 0x5b, 0x6f, 0x32, 0x86, 0x2c, 0xda, 0x86, 0xd3, 0xf1, 0xf8, 0xfb, 0x73, 0xb3, 0x7c, 0xac, 0xd3, 0xec, + 0x5f, 0x2e, 0x0f, 0x48, 0xb5, 0xea, 0x8e, 0x7d, 0x3f, 0x2b, 0xfb, 0xc9, 0x7f, 0x1f, 0x53, 0x5d, 0xc6, 0xd3, 0xbd, + 0x12, 0x80, 0x8f, 0x45, 0x94, 0xa7, 0x17, 0x11, 0x9a, 0xab, 0xf9, 0x4e, 0xfd, 0x95, 0x3c, 0xe2, 0xab, 0xd7, 0xee, + 0x1f, 0xf9, 0xa0, 0x96, 0xd3, 0x55, 0x01, 0x19, 0x32, 0xe2, 0x71, 0xff, 0x55, 0xc8, 0x68, 0xd5, 0x5c, 0x5f, 0xcc, + 0x51, 0xf4, 0xdc, 0xf9, 0x7b, 0xd3, 0x90, 0x4d, 0x2f, 0x45, 0x4f, 0x98, 0x0f, 0xd4, 0xc8, 0x6d, 0x20, 0xe8, 0x26, + 0xdc, 0xe0, 0x74, 0x47, 0xaa, 0x4e, 0x56, 0x8c, 0x2e, 0x17, 0xbf, 0x4f, 0xcf, 0x22, 0xa5, 0xbe, 0x4c, 0x2d, 0x14, + 0xaa, 0x7d, 0x1f, 0x6a, 0x47, 0xc7, 0x55, 0x21, 0x6f, 0x82, 0x07, 0xc5, 0xd9, 0x12, 0x16, 0x6d, 0x54, 0x4e, 0x14, + 0x48, 0x32, 0xac, 0x16, 0x19, 0x37, 0x9f, 0x94, 0xac, 0x21, 0x43, 0x9d, 0x99, 0x23, 0xd0, 0x1c, 0x62, 0xa7, 0x62, + 0x28, 0xe9, 0xfd, 0x65, 0x06, 0x0a, 0x33, 0x44, 0xfb, 0x81, 0x01, 0x7a, 0xe5, 0x3e, 0x9a, 0x5b, 0xe6, 0xe4, 0x62, + 0x5c, 0x76, 0x09, 0xc4, 0x33, 0x8c, 0xbd, 0x6f, 0x91, 0x08, 0xda, 0xc9, 0x3b, 0x2a, 0x0d, 0xbe, 0x98, 0xee, 0x98, + 0x40, 0x72, 0x42, 0xce, 0x99, 0x31, 0x7d, 0xc5, 0x7f, 0xcd, 0xf5, 0xe4, 0xe4, 0x4d, 0x52, 0x1e, 0x57, 0x8f, 0xf0, + 0xdb, 0xb5, 0xf6, 0x2d, 0x72, 0x1f, 0x8c, 0x34, 0x55, 0x4b, 0x7e, 0x5b, 0x2a, 0xc8, 0x12, 0x16, 0x6e, 0xac, 0x7e, + 0xea, 0xca, 0x2e, 0x64, 0xb7, 0xba, 0xf0, 0xdc, 0x36, 0x2f, 0x6b, 0xf4, 0xfb, 0x66, 0xef, 0xa1, 0xbd, 0x75, 0x05, + 0xea, 0x55, 0x6d, 0x40, 0xa5, 0xce, 0xfd, 0xd1, 0xfc, 0xf6, 0x12, 0xf4, 0x4a, 0x7d, 0xf9, 0x78, 0xf0, 0x2b, 0x6a, + 0x2c, 0x62, 0xfa, 0x5b, 0xf5, 0xb7, 0x70, 0xf2, 0x84, 0x7b, 0xab, 0x5d, 0x63, 0x5d, 0x45, 0x03, 0xa1, 0xb9, 0x7b, + 0xed, 0xf9, 0xf0, 0xbe, 0x57, 0x6d, 0x75, 0x0d, 0x50, 0xbd, 0xe3, 0x9f, 0xe1, 0x5b, 0x08, 0xa7, 0x51, 0xe8, 0xee, + 0xa7, 0x16, 0x50, 0xd0, 0xe1, 0x34, 0x55, 0x01, 0x0e, 0x51, 0x5f, 0x03, 0xb4, 0xe4, 0xa7, 0xfa, 0x45, 0x42, 0xf5, + 0xf4, 0xf0, 0xe6, 0xec, 0xd3, 0xb5, 0xec, 0x90, 0x66, 0xab, 0x6d, 0xf4, 0xd3, 0xef, 0x3b, 0x87, 0xa5, 0xec, 0xa3, + 0x4a, 0xe8, 0xad, 0x8b, 0x25, 0x9e, 0x39, 0x13, 0x07, 0xcf, 0x7f, 0xe9, 0x70, 0xed, 0x9e, 0xd8, 0x72, 0xba, 0x3e, + 0xa4, 0x3d, 0x97, 0x69, 0xe4, 0x04, 0xa6, 0x34, 0x3d, 0x49, 0x64, 0x05, 0x63, 0x6a, 0x79, 0x20, 0xa3, 0xa5, 0x65, + 0x3c, 0x6f, 0x5d, 0x1a, 0x4c, 0x79, 0xfd, 0xd0, 0x54, 0x7b, 0x6e, 0x93, 0x6d, 0x1d, 0xb0, 0x5e, 0x8e, 0x53, 0xf3, + 0x1f, 0x2e, 0xa1, 0x46, 0x13, 0x0b, 0x65, 0xc4, 0xda, 0x61, 0x5e, 0x58, 0x25, 0xd4, 0xb4, 0xa5, 0x54, 0x59, 0x54, + 0x39, 0xfb, 0x5c, 0xde, 0xaf, 0x1e, 0xa1, 0x83, 0xc1, 0x84, 0x04, 0xab, 0x53, 0x7d, 0xf1, 0x34, 0x28, 0x7a, 0x25, + 0x9e, 0xdf, 0x94, 0x2e, 0x37, 0x24, 0xf5, 0xb2, 0x4a, 0xe4, 0x2f, 0x55, 0xea, 0x85, 0xf5, 0x84, 0x60, 0x5e, 0x34, + 0xd3, 0x47, 0x98, 0x5b, 0xa7, 0x91, 0xd3, 0x53, 0xa9, 0x7a, 0xa3, 0xcd, 0x02, 0x21, 0x80, 0xc7, 0xa6, 0xeb, 0x76, + 0x8a, 0xe1, 0xd7, 0xfe, 0xb0, 0x3e, 0x66, 0x15, 0x6c, 0xba, 0xf6, 0x14, 0xc2, 0xc0, 0x75, 0xbd, 0x87, 0x37, 0x65, + 0xd3, 0x59, 0xad, 0xc6, 0x89, 0x38, 0x51, 0x29, 0x76, 0x7d, 0x27, 0x26, 0x33, 0x7a, 0xcf, 0xd0, 0x1f, 0x68, 0x8e, + 0x29, 0x21, 0x01, 0x50, 0x93, 0x1e, 0x3e, 0xff, 0x2d, 0xdd, 0xe5, 0xb6, 0x28, 0x86, 0x8a, 0x0d, 0xc2, 0xea, 0x6b, + 0x1d, 0x37, 0xc3, 0xbf, 0xe0, 0x97, 0x0f, 0xc1, 0x27, 0x24, 0x80, 0x2a, 0x0a, 0xfc, 0x83, 0xc1, 0x04, 0xda, 0x25, + 0xa7, 0x64, 0xed, 0x41, 0x73, 0x2b, 0xb1, 0xe2, 0x31, 0x10, 0x43, 0x7c, 0xfa, 0x20, 0x14, 0x31, 0x76, 0x43, 0xbb, + 0x3c, 0xec, 0xf8, 0x8a, 0xe5, 0x52, 0x99, 0x8f, 0xd5, 0x62, 0x49, 0xfa, 0x51, 0xf8, 0x45, 0xb1, 0x13, 0x02, 0x39, + 0x41, 0x35, 0x47, 0x7f, 0xde, 0x12, 0xc4, 0x9c, 0x9a, 0x5d, 0x7b, 0x6a, 0x02, 0xae, 0xe7, 0x30, 0x85, 0x1f, 0x65, + 0x6e, 0x7d, 0x88, 0xa7, 0x28, 0x9d, 0x4b, 0xc0, 0x3b, 0xb9, 0x2f, 0x3c, 0xd8, 0xba, 0xd4, 0x9d, 0x00, 0x1f, 0x12, + 0x88, 0x5a, 0x99, 0x3c, 0x99, 0xf8, 0x68, 0x51, 0x30, 0xe7, 0x33, 0x86, 0x9f, 0x34, 0x49, 0x29, 0xdc, 0x21, 0x3a, + 0x9b, 0x15, 0x4a, 0x3a, 0xa6, 0xd8, 0x0c, 0x90, 0x41, 0x00, 0x04, 0x96, 0x55, 0xfe, 0x40, 0xec, 0x72, 0x15, 0x16, + 0x1a, 0xb1, 0x52, 0x14, 0x84, 0x54, 0xdb, 0x81, 0x69, 0xd7, 0x75, 0xab, 0xc0, 0xb7, 0x4e, 0x38, 0x0d, 0xd7, 0x58, + 0x9e, 0x94, 0x94, 0xcc, 0xb0, 0x32, 0x14, 0x70, 0x6e, 0x25, 0xb2, 0x99, 0xaf, 0x8e, 0x04, 0x4e, 0xfe, 0x15, 0x0c, + 0x72, 0x5f, 0xca, 0xae, 0x1c, 0x80, 0xf3, 0xa9, 0x25, 0x2e, 0xed, 0xeb, 0xb9, 0x70, 0xeb, 0x24, 0x55, 0x9d, 0xad, + 0xf6, 0x60, 0x31, 0x2e, 0xba, 0xb4, 0xdb, 0x92, 0x14, 0x14, 0xe8, 0xbe, 0x78, 0x3a, 0x4d, 0x52, 0x67, 0x3a, 0x49, + 0x79, 0x2c, 0x66, 0x30, 0xb2, 0x44, 0x4b, 0xd5, 0x09, 0xf7, 0x9a, 0x86, 0x9d, 0xe5, 0x7f, 0x0a, 0x8d, 0xd4, 0x64, + 0x33, 0x9d, 0x6e, 0xba, 0x7b, 0xdf, 0xa7, 0x60, 0x31, 0xc0, 0xcf, 0xa2, 0x8a, 0x7c, 0x3a, 0x2b, 0xbb, 0x41, 0x00, + 0x52, 0x04, 0x7a, 0x88, 0xc7, 0xd3, 0xb8, 0x2b, 0x19, 0xd3, 0x04, 0xe6, 0x5b, 0x04, 0xd0, 0xd4, 0x21, 0x51, 0xc0, + 0xc6, 0xbc, 0x0d, 0x35, 0xde, 0x17, 0xd7, 0x77, 0x1e, 0xf3, 0x52, 0x0c, 0x72, 0x40, 0x6e, 0xfb, 0xce, 0x15, 0x40, + 0x38, 0x83, 0x0b, 0xc4, 0x1e, 0xda, 0x09, 0x8c, 0x63, 0xf7, 0xf9, 0x91, 0x48, 0x15, 0x27, 0xcc, 0x77, 0x8a, 0xcc, + 0x1e, 0x9e, 0x1a, 0xef, 0xd1, 0x27, 0xe5, 0x34, 0x1a, 0x3f, 0x48, 0x98, 0xdf, 0xd8, 0x8c, 0x9e, 0xeb, 0xea, 0x69, + 0xce, 0x47, 0x41, 0x74, 0x58, 0xe4, 0xde, 0xff, 0xe9, 0x00, 0x39, 0x31, 0xbe, 0x6e, 0x99, 0x28, 0x39, 0x13, 0x52, + 0x87, 0x5a, 0xd6, 0xfb, 0xd4, 0x44, 0xa5, 0x06, 0x9d, 0xdc, 0xf2, 0x1d, 0x29, 0xa2, 0x2a, 0x8b, 0x1d, 0x5f, 0xeb, + 0x1e, 0x2a, 0xe9, 0xc6, 0x55, 0x5d, 0x74, 0x5a, 0x0d, 0x4d, 0x9e, 0x6a, 0xe9, 0x29, 0x84, 0x9f, 0xfb, 0xb4, 0xa6, + 0x2d, 0x60, 0x3d, 0xff, 0xa1, 0xd0, 0x6c, 0x7e, 0x88, 0xf0, 0x60, 0xf5, 0x19, 0x4d, 0x68, 0xe1, 0xc9, 0x40, 0x76, + 0x1d, 0x70, 0x6c, 0x46, 0xf2, 0xb2, 0x2f, 0x11, 0x74, 0x2d, 0xcc, 0x32, 0x56, 0x8f, 0xb8, 0x51, 0xf6, 0x72, 0xd2, + 0x1e, 0xe9, 0x3c, 0x43, 0x06, 0xed, 0x4f, 0x1d, 0xf7, 0xf0, 0x04, 0x4e, 0x24, 0xf3, 0x30, 0xc6, 0x16, 0x91, 0xf3, + 0x1e, 0xcf, 0x58, 0x07, 0xea, 0xd1, 0x72, 0x8b, 0x78, 0x60, 0xec, 0xd9, 0x2e, 0x82, 0xd2, 0xd0, 0x82, 0x1d, 0xf6, + 0x81, 0x43, 0x20, 0x92, 0x85, 0x6e, 0x2f, 0x59, 0xef, 0x89, 0x54, 0x90, 0x7c, 0xef, 0xa4, 0x8c, 0x0a, 0x70, 0x0d, + 0x2b, 0x1c, 0x31, 0xe6, 0x99, 0xe3, 0x64, 0x30, 0xeb, 0x1e, 0x0a, 0xa5, 0xa1, 0x73, 0xf0, 0xc9, 0x5e, 0x8b, 0x9f, + 0x3f, 0x38, 0x21, 0x87, 0x08, 0x36, 0xf6, 0x12, 0x5d, 0xaa, 0x52, 0x50, 0x29, 0xc3, 0x40, 0xf3, 0xbc, 0xb5, 0x30, + 0x29, 0x48, 0x85, 0x07, 0x98, 0xf1, 0xf1, 0xe8, 0x0f, 0x6d, 0xce, 0xd5, 0x33, 0x64, 0x0e, 0xec, 0xb0, 0x64, 0x1f, + 0xe3, 0x82, 0x22, 0x48, 0xd4, 0x10, 0x26, 0xfa, 0x24, 0x57, 0x0f, 0x8a, 0xf4, 0x09, 0x45, 0xaf, 0x89, 0xd3, 0xd3, + 0x81, 0x09, 0x74, 0xce, 0x93, 0x16, 0xa2, 0x1e, 0x14, 0x95, 0xf3, 0x83, 0xdc, 0xbd, 0x50, 0x19, 0x77, 0x70, 0x92, + 0x4b, 0x4a, 0xd7, 0x36, 0xf3, 0xfe, 0x46, 0xdb, 0xdb, 0xf3, 0x96, 0x54, 0x07, 0x9d, 0x24, 0x31, 0x87, 0x0a, 0xbc, + 0x42, 0x40, 0x42, 0xeb, 0xbb, 0x19, 0x1d, 0xdd, 0x83, 0xde, 0x84, 0xe9, 0x55, 0x45, 0x05, 0xc8, 0xbf, 0xf7, 0x2a, + 0x1a, 0x39, 0x47, 0xea, 0xea, 0xda, 0x91, 0xba, 0xb4, 0xbb, 0xfc, 0x49, 0xa1, 0x42, 0x3e, 0x10, 0x9a, 0x1f, 0x94, + 0x9d, 0x92, 0xbe, 0x26, 0xcc, 0x75, 0x35, 0xaf, 0x09, 0x36, 0xe0, 0xb3, 0x21, 0xc7, 0x91, 0xba, 0xf1, 0x83, 0x9a, + 0x01, 0xae, 0xdc, 0xa8, 0x87, 0x95, 0xdc, 0x77, 0x2e, 0x7e, 0xb1, 0x1f, 0x34, 0x33, 0x1f, 0xe3, 0x89, 0xae, 0x7a, + 0xdc, 0xcc, 0xd8, 0x83, 0xce, 0x0f, 0xa6, 0x4e, 0xd0, 0x6d, 0x93, 0x21, 0xc4, 0x3e, 0x4d, 0xf7, 0x90, 0xe7, 0xce, + 0x8f, 0x1e, 0x4c, 0xd0, 0xb9, 0x29, 0x08, 0xad, 0x9a, 0x14, 0xe8, 0xca, 0x2e, 0x79, 0x09, 0x3f, 0xbd, 0x7a, 0x45, + 0x97, 0x76, 0xd3, 0x5b, 0xf9, 0xe3, 0x26, 0x2c, 0x03, 0x7a, 0x17, 0xdf, 0x3b, 0xd4, 0xa7, 0x4c, 0xc7, 0x3d, 0xab, + 0xdd, 0x4b, 0x7e, 0xaa, 0x39, 0x8d, 0x9f, 0xb1, 0x5a, 0xa2, 0xf3, 0xc3, 0x79, 0x40, 0x70, 0x85, 0x10, 0x57, 0xfd, + 0xc0, 0x9b, 0xbd, 0x88, 0x52, 0xa6, 0x6a, 0x8b, 0xee, 0x4f, 0xfa, 0xc0, 0x2e, 0xe1, 0xf0, 0xb2, 0x6e, 0x8e, 0x13, + 0xf1, 0xdd, 0x58, 0x00, 0x26, 0x0c, 0xea, 0xea, 0xb7, 0x10, 0x30, 0x21, 0xba, 0xf3, 0xe4, 0x67, 0xfc, 0xbf, 0x21, + 0xe6, 0x3b, 0x45, 0xf8, 0xea, 0x78, 0xc1, 0xc9, 0xe9, 0xe4, 0x25, 0x2c, 0x81, 0x6f, 0x77, 0x18, 0x19, 0x22, 0x63, + 0x42, 0xa0, 0x19, 0xf3, 0x17, 0x69, 0x98, 0x4b, 0xe0, 0x2d, 0x0e, 0x81, 0xa3, 0xb8, 0x25, 0xfe, 0x64, 0x83, 0xbb, + 0xf7, 0xf9, 0xa6, 0x0f, 0xb1, 0xa6, 0xca, 0x2e, 0x41, 0xb9, 0xab, 0x78, 0xec, 0x66, 0x14, 0x68, 0x8c, 0xc2, 0x7e, + 0x83, 0x62, 0xa0, 0x45, 0xb7, 0x0e, 0x44, 0x14, 0xfa, 0x67, 0x55, 0xd1, 0xf9, 0x68, 0xa9, 0x88, 0x1d, 0xb2, 0x03, + 0x38, 0x01, 0xb2, 0x8b, 0x38, 0x19, 0x53, 0x97, 0x3b, 0x8a, 0x42, 0x03, 0x01, 0xce, 0xf0, 0x17, 0x3b, 0x9c, 0xf1, + 0x1f, 0xac, 0x03, 0x9b, 0x1c, 0x10, 0xd4, 0x06, 0xeb, 0x62, 0x8b, 0x53, 0x3c, 0x91, 0xfa, 0xc6, 0xec, 0xed, 0x79, + 0x3d, 0x1d, 0xf0, 0x1e, 0xdf, 0x55, 0xa9, 0x68, 0x21, 0x05, 0x5b, 0xf8, 0xb6, 0x1b, 0x32, 0x56, 0x0a, 0xab, 0xa0, + 0x77, 0xe7, 0x41, 0xd5, 0x9f, 0x4a, 0xa3, 0xfa, 0xbf, 0xba, 0x3b, 0xeb, 0xba, 0x05, 0x0f, 0xed, 0x92, 0xee, 0x82, + 0x2c, 0x59, 0xaa, 0x87, 0xad, 0xbe, 0xd9, 0x4d, 0x05, 0x05, 0xa9, 0x1d, 0x72, 0x7d, 0xeb, 0xff, 0xac, 0xc1, 0x81, + 0xac, 0xad, 0xda, 0x88, 0x03, 0xe1, 0x1d, 0x27, 0xc4, 0x57, 0x8f, 0xea, 0xae, 0x2e, 0x12, 0x54, 0x4d, 0xf9, 0xb8, + 0xc4, 0xfc, 0x32, 0x5e, 0xe5, 0x8d, 0x49, 0xaf, 0x2b, 0xbb, 0xef, 0x75, 0x39, 0x91, 0xb7, 0x93, 0xf9, 0x53, 0x10, + 0xdf, 0xd1, 0xcc, 0x00, 0x27, 0x27, 0xa5, 0x6c, 0x3d, 0xfb, 0x58, 0xdc, 0xe7, 0x38, 0x21, 0x92, 0x56, 0x19, 0x46, + 0x77, 0x7e, 0xe9, 0x0c, 0x6f, 0xdf, 0x80, 0xc2, 0xdb, 0x27, 0x4f, 0x80, 0x85, 0xd7, 0x94, 0x35, 0x8e, 0xb4, 0x28, + 0x24, 0x86, 0x61, 0x28, 0x05, 0xa2, 0x89, 0xd3, 0x6d, 0xa3, 0xbc, 0x09, 0xd0, 0x5b, 0xa1, 0x3f, 0x34, 0xf6, 0x88, + 0x9c, 0xb3, 0x7a, 0x79, 0x24, 0x97, 0xcc, 0x9f, 0xa8, 0x63, 0xfc, 0xc4, 0x0f, 0xfd, 0x93, 0x07, 0xf7, 0xba, 0x69, + 0x03, 0x38, 0xa4, 0x82, 0x9c, 0xc8, 0xf3, 0xb6, 0xbd, 0xaa, 0x8b, 0x26, 0x84, 0x3c, 0xe4, 0x56, 0x80, 0x87, 0x3c, + 0x3f, 0x9f, 0xeb, 0xa3, 0x10, 0x86, 0x43, 0x73, 0x05, 0x9c, 0x10, 0xd4, 0xc0, 0xbe, 0x81, 0x71, 0xe4, 0x46, 0x9e, + 0xdb, 0xd4, 0x17, 0x3d, 0x4e, 0xde, 0xed, 0x20, 0xa0, 0x0d, 0xfd, 0xf0, 0xbc, 0x3e, 0x75, 0xf5, 0xa3, 0xed, 0xf5, + 0x89, 0xdf, 0xc7, 0x68, 0x04, 0xe5, 0xcd, 0x5f, 0x61, 0x9f, 0x48, 0x5c, 0x81, 0x2d, 0xcd, 0x71, 0x36, 0x29, 0x9f, + 0xd5, 0x69, 0x53, 0xa1, 0x9e, 0x7c, 0x91, 0x63, 0x92, 0xdc, 0x57, 0x37, 0xd5, 0xc9, 0x42, 0x44, 0x1c, 0xf9, 0x9d, + 0x71, 0x87, 0xc1, 0xfc, 0x3c, 0xc9, 0xcd, 0x85, 0x2e, 0xb9, 0x3e, 0x4d, 0x2a, 0x68, 0xa0, 0xa2, 0x25, 0x79, 0x2f, + 0x3c, 0xe6, 0xad, 0xd0, 0xe4, 0x73, 0x61, 0x99, 0x2f, 0x85, 0xeb, 0x7c, 0x5d, 0x3f, 0x80, 0xef, 0x11, 0x37, 0xaa, + 0x56, 0x63, 0x3f, 0xf6, 0xe4, 0xbb, 0x96, 0x98, 0xf3, 0xaf, 0x34, 0xd6, 0xbc, 0x4d, 0x77, 0x89, 0x15, 0xcc, 0x68, + 0x36, 0x6d, 0x2c, 0x6e, 0x0c, 0x31, 0x15, 0xae, 0x49, 0xef, 0x70, 0x8d, 0xca, 0xf4, 0xec, 0x34, 0x22, 0x10, 0xbd, + 0x20, 0x6b, 0x0a, 0xb2, 0xb3, 0x95, 0x55, 0xb8, 0xb7, 0xd0, 0xb8, 0x58, 0xb8, 0x74, 0x7a, 0x1d, 0xbc, 0x42, 0xf5, + 0xfd, 0x76, 0x3d, 0x72, 0x89, 0xf2, 0xda, 0x94, 0xb4, 0xe1, 0x23, 0x27, 0x98, 0x63, 0xb1, 0x27, 0x28, 0xa1, 0xc8, + 0x50, 0xda, 0x12, 0xf0, 0x5c, 0x38, 0xe0, 0x1b, 0x31, 0x54, 0xeb, 0x7b, 0x5e, 0xa1, 0x27, 0x14, 0x39, 0x3e, 0x2a, + 0x77, 0x55, 0xc5, 0x49, 0x55, 0xba, 0xe6, 0x13, 0x5c, 0xbf, 0x1f, 0xbd, 0x8f, 0x88, 0x62, 0xed, 0x2b, 0xfb, 0xc2, + 0xbf, 0xb7, 0xc5, 0xea, 0xb2, 0x4f, 0x99, 0x80, 0x90, 0x5c, 0xde, 0xf2, 0x13, 0xc5, 0x1e, 0x39, 0x83, 0xef, 0x89, + 0xb0, 0x16, 0x93, 0x1c, 0xa4, 0xe3, 0x45, 0xc4, 0x0f, 0xa0, 0x03, 0x5b, 0xbe, 0x33, 0xcd, 0x29, 0xe2, 0x71, 0x25, + 0xbe, 0xef, 0x27, 0x83, 0x12, 0x2b, 0xb1, 0xca, 0xd9, 0x4f, 0xaa, 0x3a, 0x09, 0xe6, 0x7e, 0xf4, 0x1d, 0x8f, 0x1b, + 0xc1, 0xc1, 0xd4, 0xf1, 0x22, 0x64, 0x40, 0xe0, 0x17, 0xa0, 0x52, 0xe9, 0x01, 0xf1, 0x01, 0xa2, 0x6f, 0x40, 0x85, + 0x40, 0x78, 0x19, 0x94, 0xef, 0x51, 0x55, 0x9d, 0xda, 0x97, 0x73, 0x57, 0xde, 0x11, 0x94, 0x60, 0x1a, 0xde, 0xd1, + 0x48, 0x12, 0x94, 0x07, 0x1a, 0xed, 0x4e, 0x8e, 0xf8, 0xe0, 0x87, 0x37, 0x1d, 0x4d, 0x97, 0x2f, 0x31, 0x13, 0xed, + 0xd5, 0x9b, 0xf2, 0xe2, 0x5f, 0x83, 0x4c, 0x7c, 0xc9, 0x51, 0x20, 0x3e, 0xca, 0x93, 0xf5, 0xa6, 0xa4, 0x57, 0x17, + 0x4b, 0xbc, 0xff, 0x42, 0x39, 0xc2, 0xef, 0x16, 0x54, 0x0b, 0x30, 0xc7, 0xfe, 0xfc, 0xd0, 0xb6, 0x12, 0x3a, 0xc4, + 0x9a, 0xb7, 0xae, 0x04, 0xfe, 0x91, 0x9d, 0xb1, 0xbd, 0x0e, 0xc1, 0x7d, 0xdd, 0x9d, 0xcf, 0x0c, 0x33, 0xd9, 0xbc, + 0x52, 0x5c, 0x5e, 0x5a, 0x5e, 0x46, 0xe5, 0xb9, 0x27, 0xb9, 0xbe, 0x7e, 0x27, 0xf1, 0x9b, 0x4d, 0xed, 0xe2, 0xf2, + 0x5b, 0x4b, 0xdf, 0x98, 0x9a, 0x59, 0x26, 0xe0, 0x7e, 0xa7, 0xd7, 0x78, 0xa0, 0xd6, 0xed, 0x68, 0x6c, 0x33, 0x9b, + 0x13, 0xc6, 0x10, 0xe9, 0x6e, 0x3b, 0x53, 0x7b, 0xaa, 0xe0, 0xb7, 0x25, 0x45, 0xb2, 0x98, 0xd1, 0xd8, 0x22, 0x8e, + 0x54, 0x6f, 0xe5, 0x71, 0xcf, 0x7f, 0x67, 0xfa, 0x21, 0x43, 0xe3, 0x0c, 0x7f, 0xa0, 0x10, 0x01, 0x74, 0x23, 0x90, + 0xbb, 0xf0, 0xf5, 0x9e, 0x7f, 0x51, 0x21, 0x00, 0x40, 0x6d, 0x06, 0xa6, 0xbd, 0x18, 0xe7, 0x3a, 0x51, 0x1b, 0x72, + 0x8a, 0xee, 0xa6, 0xff, 0x1c, 0xbe, 0xd7, 0xdd, 0x89, 0x85, 0x56, 0x54, 0x7f, 0xf3, 0x43, 0x1b, 0xf0, 0x27, 0x75, + 0x9a, 0x62, 0x11, 0xba, 0xd8, 0xb8, 0xbc, 0x41, 0x9e, 0xa2, 0x35, 0x4a, 0x07, 0x55, 0x7e, 0x56, 0xd8, 0x1c, 0xbf, + 0xb7, 0x7f, 0xc6, 0x15, 0x78, 0x6b, 0x27, 0x55, 0xcf, 0x8d, 0xae, 0x0d, 0x00, 0x7d, 0x53, 0xdf, 0x0b, 0x4d, 0x66, + 0xde, 0xa8, 0xd2, 0xeb, 0xf7, 0x7b, 0xf2, 0x44, 0xfb, 0x10, 0x78, 0x02, 0x1a, 0x4e, 0x80, 0xd0, 0x95, 0x7c, 0x32, + 0x1f, 0x60, 0x03, 0x49, 0xa9, 0x38, 0xb0, 0xd3, 0x10, 0x7a, 0xa0, 0x63, 0x39, 0x61, 0x98, 0xc8, 0xe4, 0xc4, 0x71, + 0xc3, 0xff, 0x08, 0x2b, 0x42, 0x3f, 0xfa, 0x7f, 0xc6, 0x22, 0xae, 0x86, 0x73, 0x04, 0x31, 0x6a, 0xde, 0xc8, 0x02, + 0x14, 0xfc, 0xd7, 0xa9, 0xa7, 0x93, 0x13, 0xd9, 0x1c, 0xe7, 0x8c, 0x5a, 0xc6, 0x14, 0x21, 0xf2, 0x20, 0xc4, 0xf8, + 0x15, 0x9b, 0x58, 0x46, 0xd0, 0xf4, 0x43, 0x45, 0xef, 0x06, 0xb5, 0x95, 0x8c, 0xb8, 0x59, 0x3b, 0xb1, 0x03, 0xd7, + 0xde, 0x23, 0x01, 0x03, 0x79, 0xd3, 0x92, 0xed, 0x9b, 0xd2, 0xd5, 0xf8, 0xb0, 0x3f, 0x4e, 0xc6, 0xeb, 0x49, 0x16, + 0xf8, 0x27, 0xcd, 0x6e, 0x76, 0x86, 0xe4, 0x30, 0x49, 0xaa, 0x3c, 0x10, 0x09, 0xbc, 0xeb, 0x16, 0x43, 0x6a, 0x2c, + 0x6d, 0xb5, 0x4c, 0x14, 0xd6, 0x66, 0xc5, 0xc0, 0x91, 0x4d, 0x58, 0x17, 0x21, 0x06, 0x75, 0xb8, 0x2e, 0x4e, 0x01, + 0xd5, 0x09, 0xc2, 0x1c, 0x2a, 0xcc, 0x3a, 0x04, 0x9d, 0x32, 0x70, 0xd0, 0x31, 0xde, 0xd5, 0x83, 0xdf, 0x37, 0xce, + 0x88, 0x27, 0xc7, 0x4d, 0x83, 0xcb, 0xb9, 0xb1, 0x1c, 0x39, 0x37, 0xc2, 0xcb, 0xc0, 0xe9, 0x76, 0xbd, 0x21, 0xd5, + 0x36, 0x9c, 0x55, 0x45, 0x15, 0xad, 0xf2, 0x59, 0x75, 0x12, 0xd3, 0x56, 0xaf, 0xd9, 0x04, 0x51, 0x77, 0xe7, 0xb9, + 0x61, 0x2d, 0x83, 0x86, 0x11, 0x25, 0x21, 0x56, 0xef, 0x03, 0xfd, 0x89, 0x3d, 0xf8, 0xa2, 0x6a, 0x0f, 0x04, 0xba, + 0xd3, 0x60, 0x61, 0xc7, 0xcf, 0x00, 0x1c, 0x6e, 0xc6, 0x31, 0x8e, 0x41, 0xfb, 0xd2, 0x8a, 0x42, 0xaf, 0x96, 0x44, + 0xe0, 0x0c, 0xb3, 0xfc, 0x1f, 0xdf, 0xe2, 0xb5, 0xe8, 0xb5, 0xeb, 0xd0, 0xb9, 0xfa, 0x24, 0x36, 0x75, 0x6d, 0x9a, + 0xf8, 0xe8, 0xba, 0x81, 0x4b, 0x2f, 0xf0, 0x00, 0xee, 0x89, 0x17, 0x5d, 0x81, 0xc0, 0xf3, 0x4f, 0x26, 0x4a, 0xf7, + 0x70, 0x80, 0x39, 0x53, 0xdc, 0x18, 0x4e, 0xb9, 0x94, 0x1c, 0x43, 0xd3, 0xc2, 0x12, 0x18, 0x39, 0x40, 0x7f, 0x31, + 0xae, 0x30, 0x49, 0xba, 0xa4, 0x45, 0x47, 0xd1, 0x43, 0x2e, 0xc9, 0x3a, 0xa7, 0x5f, 0x64, 0x7e, 0xac, 0xae, 0xb1, + 0x51, 0x1c, 0x6b, 0x85, 0xcc, 0x3f, 0x44, 0x34, 0xea, 0x37, 0xe7, 0x05, 0x4c, 0xec, 0x8b, 0x83, 0x39, 0xbf, 0xf7, + 0xb7, 0xd6, 0xf2, 0xfa, 0xe3, 0xc9, 0xa6, 0xa7, 0x8c, 0x06, 0xc0, 0x18, 0x7e, 0xc2, 0xf1, 0x20, 0xa5, 0xd7, 0x57, + 0xa4, 0x82, 0xf7, 0x4d, 0xf1, 0x69, 0x5f, 0xc8, 0x4c, 0x4a, 0xf4, 0x8f, 0x41, 0x2c, 0x7d, 0x36, 0xad, 0x26, 0xd3, + 0x1f, 0xd0, 0xe6, 0x68, 0x0c, 0xe2, 0x51, 0x73, 0x78, 0x8b, 0x45, 0x75, 0xf5, 0x0c, 0x3f, 0xa1, 0xcc, 0x2d, 0x99, + 0x0f, 0xd9, 0x3e, 0x42, 0x5f, 0x8c, 0x25, 0x66, 0x1a, 0x9f, 0x27, 0x3f, 0x77, 0x91, 0x6b, 0xab, 0x37, 0x37, 0xf2, + 0xdf, 0xd4, 0x52, 0xa4, 0x36, 0xaa, 0x08, 0xfe, 0xfa, 0x13, 0xfa, 0x6a, 0x67, 0xde, 0xa4, 0x00, 0x9d, 0x2f, 0x09, + 0xfa, 0xd9, 0x80, 0x2a, 0x52, 0x04, 0x0d, 0xf8, 0xce, 0x16, 0xda, 0xa5, 0x32, 0x1d, 0x73, 0xfa, 0x5a, 0xde, 0xdf, + 0x84, 0x15, 0xdc, 0x1d, 0x6a, 0x1b, 0x92, 0xac, 0x46, 0x7e, 0x3d, 0xc6, 0x8a, 0xad, 0xef, 0x5d, 0x65, 0x38, 0xed, + 0xff, 0x18, 0x07, 0x01, 0xc8, 0x5b, 0xc5, 0xdd, 0x92, 0xd6, 0x69, 0xbd, 0x93, 0x74, 0xa5, 0x18, 0xd2, 0xca, 0xd5, + 0xfd, 0xee, 0xfb, 0xff, 0x30, 0x45, 0x63, 0x4a, 0x9f, 0xd8, 0x08, 0xed, 0x2a, 0x40, 0x92, 0x03, 0xa2, 0x87, 0x07, + 0x2d, 0x1d, 0x7f, 0x08, 0x45, 0x0b, 0x16, 0xbe, 0x05, 0x7d, 0xc3, 0x20, 0xda, 0x1e, 0xc1, 0x01, 0xbc, 0x0b, 0x97, + 0x7f, 0xf8, 0xa5, 0x71, 0x0d, 0x91, 0xdc, 0x7c, 0x4f, 0x6a, 0xea, 0xea, 0xcd, 0xbb, 0xe9, 0x5f, 0x42, 0xd6, 0x4d, + 0xfd, 0xe9, 0xaf, 0xd3, 0xb6, 0x2f, 0xbc, 0x9e, 0x14, 0x89, 0x66, 0x1c, 0x7f, 0x7b, 0x62, 0xe3, 0x8d, 0x31, 0xba, + 0x3e, 0x8a, 0x9e, 0x56, 0xcf, 0xde, 0x7a, 0xe9, 0x41, 0x2f, 0x4e, 0x08, 0x1e, 0xe3, 0x8f, 0x60, 0xc2, 0x6b, 0xfe, + 0x94, 0x50, 0xc7, 0xdf, 0x7a, 0x84, 0x7f, 0x36, 0x53, 0x18, 0x09, 0x15, 0x7e, 0xc7, 0x23, 0x8b, 0xca, 0xc5, 0xa5, + 0x24, 0x03, 0x42, 0x5c, 0x03, 0x60, 0x78, 0x2f, 0x9f, 0x02, 0x44, 0x62, 0xe3, 0xef, 0xe4, 0xde, 0x56, 0x78, 0x38, + 0xf7, 0xee, 0x50, 0x8e, 0xc5, 0xf2, 0x21, 0x65, 0xa6, 0x8f, 0xf8, 0x6d, 0xa4, 0x6e, 0x05, 0x75, 0x97, 0xe3, 0x97, + 0x0d, 0xc4, 0x7c, 0x00, 0xee, 0xef, 0xe1, 0xcd, 0x94, 0x57, 0x3f, 0x88, 0x6b, 0x00, 0xe4, 0xcf, 0x9d, 0x28, 0x99, + 0xd6, 0x1f, 0xed, 0x37, 0x74, 0x32, 0x10, 0x41, 0x82, 0x5a, 0x0b, 0x6a, 0x09, 0xb6, 0xc9, 0xee, 0x4d, 0x08, 0xbb, + 0xb5, 0xde, 0xcc, 0x77, 0xec, 0xc0, 0xce, 0x17, 0x7c, 0xc2, 0xf9, 0xb2, 0xf6, 0xa2, 0x9e, 0x1b, 0x19, 0xfe, 0x1d, + 0xf1, 0x0c, 0xfb, 0x05, 0xf3, 0xda, 0xb3, 0x9b, 0x9b, 0xf4, 0x3a, 0x5d, 0x0f, 0x4b, 0x5a, 0xa3, 0x7e, 0xc3, 0x8a, + 0x47, 0xb7, 0xd3, 0xa9, 0xd5, 0x6d, 0xb3, 0xaf, 0xd3, 0xef, 0x22, 0x96, 0x09, 0x34, 0x3f, 0xf1, 0xd6, 0x07, 0xa4, + 0xbe, 0xfd, 0x34, 0xb2, 0x9d, 0x5f, 0xfc, 0xf0, 0x0d, 0x86, 0xd3, 0x9f, 0x1c, 0xe1, 0xae, 0x0c, 0x7e, 0x66, 0x07, + 0xaa, 0x03, 0x25, 0x9f, 0xdd, 0x32, 0xc2, 0x60, 0x1c, 0x3e, 0xf0, 0x0c, 0x2b, 0xf8, 0x64, 0x7f, 0x15, 0xde, 0xc1, + 0xea, 0x1f, 0x5e, 0xf0, 0x5a, 0x5a, 0x23, 0xd5, 0x02, 0xdb, 0x85, 0x50, 0x42, 0xd9, 0x21, 0x35, 0x6e, 0xd4, 0xf6, + 0xef, 0x5c, 0x30, 0xd9, 0x22, 0xe1, 0xa6, 0x91, 0xe3, 0x57, 0x76, 0x9e, 0xb9, 0x72, 0x3c, 0xdf, 0x70, 0x33, 0xab, + 0xa0, 0x6f, 0x41, 0x19, 0x16, 0x56, 0x5f, 0x3a, 0xbe, 0x68, 0x88, 0xfe, 0x65, 0xf1, 0x0b, 0xc7, 0x51, 0xfe, 0x98, + 0xa3, 0x06, 0xee, 0xfc, 0x32, 0xaa, 0x88, 0x92, 0x3c, 0x66, 0x83, 0x03, 0xbd, 0xa3, 0xd1, 0x3c, 0xa1, 0x53, 0x2b, + 0x34, 0x80, 0x72, 0x0f, 0x1d, 0xfd, 0x80, 0xf5, 0xd4, 0x7e, 0xc3, 0x36, 0x1e, 0xe3, 0xf2, 0xaf, 0xe3, 0x4e, 0x46, + 0x9c, 0x92, 0x62, 0x7e, 0xcb, 0x33, 0x83, 0xf5, 0x82, 0x25, 0x5e, 0x25, 0x61, 0x07, 0xa4, 0xa8, 0xdf, 0xb1, 0xe7, + 0x7f, 0xe7, 0x7b, 0x1d, 0x60, 0xbe, 0x2d, 0xe8, 0xc9, 0xac, 0x01, 0xbf, 0xf5, 0x02, 0x6a, 0x2f, 0xe8, 0x2d, 0xa7, + 0xc5, 0x76, 0x55, 0x0d, 0x70, 0x02, 0x23, 0xa8, 0x99, 0x27, 0xf1, 0xe5, 0xde, 0xda, 0xff, 0x52, 0x71, 0x6a, 0xc0, + 0xc7, 0xc7, 0xeb, 0x07, 0xcc, 0x43, 0x87, 0x1c, 0xe5, 0x19, 0xef, 0x80, 0xcf, 0x1e, 0x1f, 0xf3, 0x1c, 0xb0, 0x63, + 0xf2, 0x7f, 0xe1, 0x61, 0xa9, 0xb3, 0xe7, 0x78, 0xf8, 0x12, 0x76, 0x8b, 0x93, 0x3d, 0xdc, 0x4d, 0xf2, 0x30, 0xde, + 0x24, 0xde, 0x06, 0x4f, 0x74, 0x63, 0xe0, 0x6a, 0xf0, 0xe3, 0x14, 0xeb, 0x3b, 0xde, 0xea, 0xe3, 0xf7, 0x7a, 0x7d, + 0x62, 0x5f, 0x35, 0x78, 0xbe, 0xff, 0x8d, 0x8f, 0xc6, 0x2d, 0xe3, 0x7f, 0xd5, 0x9a, 0xe7, 0x17, 0xa4, 0xaf, 0x63, + 0xf7, 0x74, 0x24, 0xdb, 0xea, 0xb1, 0x20, 0x67, 0x3a, 0x46, 0x47, 0x3a, 0x4e, 0xcb, 0x9f, 0xe2, 0xfa, 0x94, 0x9f, + 0x7c, 0xaf, 0xaf, 0x4f, 0x3c, 0xa9, 0xd4, 0xc6, 0xf3, 0x69, 0xb4, 0x01, 0x47, 0x0b, 0xe8, 0x4f, 0xab, 0x69, 0x6b, + 0x43, 0x12, 0x6f, 0x60, 0xb2, 0x4b, 0x71, 0x68, 0x56, 0xec, 0x96, 0xed, 0x4c, 0x3d, 0xd0, 0x9f, 0x77, 0xad, 0x07, + 0xe7, 0x85, 0xb9, 0xb1, 0x67, 0x05, 0x6e, 0x98, 0xac, 0xd4, 0x0e, 0xd2, 0x20, 0x1a, 0x11, 0x4b, 0x16, 0x88, 0x8e, + 0xfc, 0xda, 0x6b, 0x8f, 0x4d, 0xc5, 0xd9, 0xfd, 0x1a, 0x43, 0x97, 0x92, 0x01, 0x70, 0xb9, 0x2c, 0xec, 0xd4, 0xeb, + 0xed, 0x00, 0x0d, 0x02, 0x14, 0x07, 0x55, 0xce, 0x4c, 0xa0, 0x6c, 0x16, 0xf8, 0x1c, 0xe8, 0x55, 0x80, 0x3d, 0xe8, + 0xc2, 0xd1, 0xb9, 0x21, 0x5e, 0xe0, 0x9c, 0xd9, 0x2b, 0x03, 0x42, 0x89, 0x92, 0x5f, 0x34, 0xbc, 0x2d, 0xc6, 0x1c, + 0x7d, 0xd8, 0x08, 0x6b, 0x46, 0xa7, 0xaa, 0xe3, 0xda, 0x5b, 0xa5, 0xe3, 0xe6, 0xc1, 0xf1, 0x3d, 0x48, 0x90, 0x63, + 0x90, 0xc6, 0xfa, 0x3d, 0x7f, 0xb7, 0x3c, 0x95, 0x19, 0x58, 0x65, 0xc7, 0xfc, 0x7e, 0xc8, 0xad, 0x18, 0x79, 0x33, + 0x99, 0x28, 0x4b, 0x78, 0x90, 0x2b, 0xbc, 0xaf, 0xe2, 0x3f, 0x17, 0x91, 0xec, 0x24, 0x3f, 0xd2, 0x47, 0x42, 0xf5, + 0x8c, 0xf0, 0xcb, 0x27, 0xaa, 0xfe, 0x28, 0x66, 0xc3, 0x50, 0xec, 0xc6, 0x6a, 0x36, 0x0e, 0x73, 0x2e, 0xe7, 0x8d, + 0x36, 0xc4, 0x5b, 0x27, 0xb5, 0x89, 0xb4, 0xc1, 0x15, 0x7e, 0xb3, 0x00, 0x74, 0xc5, 0xf2, 0x40, 0x21, 0x90, 0x23, + 0xb4, 0xb7, 0x15, 0x2d, 0xf1, 0x29, 0x07, 0xbf, 0x60, 0x07, 0x3b, 0x84, 0x66, 0xbf, 0xcc, 0x43, 0x6b, 0x34, 0x6d, + 0x64, 0xd2, 0x61, 0xe7, 0x12, 0xc8, 0xd4, 0x02, 0x23, 0xd4, 0x38, 0xcb, 0xa1, 0xb0, 0x2a, 0xb8, 0x6f, 0xb4, 0x72, + 0x2e, 0x5c, 0xb7, 0x94, 0x4f, 0x86, 0x05, 0x3e, 0xc1, 0x16, 0x3e, 0x63, 0xc2, 0xee, 0x0b, 0xba, 0x39, 0x24, 0x52, + 0x48, 0x15, 0x25, 0x8d, 0xc9, 0xa0, 0x42, 0xe9, 0xb8, 0x8a, 0x5a, 0xa7, 0x81, 0xee, 0x31, 0x21, 0xa6, 0xa7, 0x20, + 0x16, 0x47, 0x6e, 0x5f, 0xfd, 0x11, 0xcf, 0xcf, 0x9b, 0x1f, 0xfb, 0x18, 0x27, 0x1a, 0x8b, 0xc7, 0x91, 0x3a, 0x3f, + 0x42, 0x65, 0xb8, 0xbc, 0x39, 0xed, 0x2e, 0xec, 0x35, 0x75, 0xd9, 0x29, 0x92, 0x12, 0x21, 0xa6, 0xb2, 0x5c, 0xe0, + 0x70, 0xdf, 0xeb, 0x9a, 0x54, 0xec, 0x08, 0xbc, 0x2e, 0xc4, 0x2f, 0x85, 0x7b, 0x6b, 0xf8, 0xbd, 0x62, 0xb7, 0x5a, + 0x3b, 0xdf, 0xb6, 0xb9, 0x33, 0x8f, 0xfc, 0xc0, 0xe1, 0xcc, 0xc9, 0x4c, 0xf4, 0x8b, 0xb0, 0xc4, 0xba, 0x23, 0xc7, + 0xd2, 0x90, 0xe0, 0xc4, 0x33, 0x54, 0xd9, 0x54, 0x43, 0xf7, 0x3b, 0x2f, 0x14, 0x71, 0x53, 0x72, 0xb4, 0x9b, 0x80, + 0x5c, 0xae, 0xe9, 0x52, 0xcb, 0xa8, 0x1c, 0x92, 0x84, 0xe7, 0x39, 0x90, 0xe7, 0x84, 0x62, 0xf5, 0xb3, 0xac, 0x33, + 0xe2, 0xbc, 0x81, 0x52, 0x3e, 0x12, 0x49, 0x78, 0xa6, 0xa6, 0xe7, 0x66, 0xe2, 0x4f, 0xd4, 0x5f, 0x89, 0xb3, 0x37, + 0x19, 0x1e, 0x8e, 0xe3, 0x0a, 0xc3, 0x35, 0xfc, 0x87, 0xd0, 0xa3, 0x07, 0xfe, 0xb9, 0x19, 0xc3, 0x20, 0x24, 0x99, + 0x51, 0x28, 0xc2, 0xa4, 0xf4, 0xea, 0xba, 0x6b, 0x1a, 0xe3, 0x44, 0xc7, 0xbd, 0x07, 0x9f, 0xab, 0x77, 0x13, 0xa4, + 0x84, 0xc4, 0x31, 0xa5, 0x0c, 0xca, 0xb5, 0xa8, 0xf0, 0xe0, 0xa9, 0xf6, 0xf5, 0x8f, 0x99, 0x5b, 0x51, 0x74, 0xf9, + 0x0a, 0xd9, 0x48, 0x00, 0x46, 0x4f, 0x06, 0x58, 0x0f, 0xcb, 0x0c, 0x76, 0x22, 0x21, 0xbe, 0x0a, 0x87, 0x2c, 0xad, + 0x3a, 0xca, 0x00, 0xb0, 0xd9, 0x6d, 0x04, 0xa3, 0x0f, 0x58, 0x75, 0x86, 0x5a, 0xd1, 0xdf, 0xb2, 0xa8, 0x5a, 0x69, + 0xdd, 0x48, 0x79, 0x35, 0x8d, 0x3e, 0xd1, 0x91, 0x0b, 0x3e, 0x97, 0x75, 0xbb, 0x20, 0x29, 0x49, 0x8c, 0x36, 0xb4, + 0xd0, 0x6f, 0x6b, 0x48, 0x57, 0xff, 0xf9, 0xe9, 0x7e, 0x28, 0xa2, 0x28, 0xad, 0x8b, 0x41, 0x72, 0x14, 0x94, 0xfe, + 0x87, 0x50, 0x07, 0x70, 0x36, 0x2d, 0xea, 0x27, 0x51, 0xc7, 0xed, 0x06, 0x1a, 0xf1, 0xe5, 0x07, 0xd8, 0xd9, 0x3a, + 0xba, 0xdb, 0xd6, 0xba, 0xd3, 0x24, 0x9a, 0x5c, 0x34, 0xa3, 0xca, 0x59, 0x23, 0xc7, 0x87, 0x41, 0x76, 0x16, 0xb9, + 0x4c, 0x46, 0x38, 0x77, 0x7b, 0x5d, 0xb0, 0x61, 0x12, 0x65, 0xc5, 0xff, 0x7c, 0x15, 0xa2, 0xbb, 0x94, 0x8b, 0x7e, + 0x00, 0x5b, 0xf2, 0xb2, 0xe3, 0x64, 0xd5, 0x20, 0x20, 0x5a, 0x09, 0x58, 0xce, 0xfc, 0xa2, 0x30, 0x8d, 0xd3, 0x77, + 0x5d, 0xba, 0x98, 0xc6, 0x90, 0xb5, 0x6e, 0x3e, 0xf0, 0x6f, 0x54, 0xb9, 0xc7, 0xb5, 0x26, 0xb8, 0x25, 0xa4, 0x77, + 0xe1, 0x0e, 0xf0, 0x6a, 0x53, 0xc7, 0x6e, 0xd7, 0x21, 0x10, 0x12, 0x08, 0x95, 0x2e, 0x24, 0xa8, 0x42, 0xdd, 0xce, + 0x84, 0x35, 0xd5, 0x23, 0xbc, 0x70, 0x62, 0xec, 0x2f, 0x57, 0x25, 0x37, 0x02, 0x07, 0x50, 0x34, 0x9b, 0x2f, 0x84, + 0xcd, 0xe4, 0xa8, 0x57, 0x8d, 0x6a, 0x47, 0xf0, 0x15, 0x3b, 0x1e, 0xfc, 0xd9, 0x67, 0x12, 0x31, 0x66, 0xe8, 0xc2, + 0x86, 0xdc, 0xf2, 0x33, 0x5d, 0x30, 0x6f, 0x0e, 0xda, 0xe9, 0x69, 0xed, 0xa3, 0xa0, 0x42, 0x75, 0x7e, 0xaa, 0x17, + 0x66, 0x12, 0xf2, 0x1c, 0x8b, 0x17, 0x83, 0xe6, 0xb9, 0xb1, 0x7e, 0x1f, 0x1d, 0xf2, 0xff, 0xd7, 0x0a, 0x18, 0x39, + 0xbb, 0x95, 0xee, 0x99, 0x52, 0xa6, 0xe4, 0xbb, 0xc5, 0xfc, 0xca, 0xc4, 0x70, 0xb5, 0x9c, 0x1a, 0xc1, 0x71, 0x9c, + 0xe6, 0xf1, 0x11, 0x26, 0x83, 0xa8, 0xbe, 0xc6, 0x56, 0x7c, 0xe8, 0xca, 0x90, 0x85, 0x7b, 0xbf, 0x4a, 0x54, 0x7a, + 0x87, 0xa3, 0xa7, 0xda, 0x9a, 0x51, 0xb5, 0x04, 0xea, 0xeb, 0x46, 0xad, 0xa8, 0xd5, 0x82, 0x72, 0x2a, 0x98, 0x57, + 0x98, 0xf2, 0xc4, 0x56, 0xe7, 0xff, 0x2d, 0x2b, 0xe1, 0xcf, 0x0d, 0xff, 0x8b, 0xbf, 0x10, 0x4e, 0x58, 0xb1, 0xa4, + 0xc2, 0x8f, 0x57, 0x07, 0x03, 0xb0, 0xf0, 0xdf, 0x87, 0xdd, 0xe8, 0x6f, 0x63, 0xb9, 0x80, 0xd4, 0xfd, 0xe8, 0x29, + 0x96, 0x4e, 0x11, 0x42, 0x2c, 0xe5, 0x45, 0xcf, 0x54, 0x52, 0x8b, 0x33, 0x2f, 0x1a, 0x00, 0x98, 0xf7, 0x60, 0xcd, + 0x7d, 0x71, 0x9c, 0x24, 0x41, 0xcd, 0x2a, 0xa0, 0x9a, 0x72, 0x3d, 0x27, 0xcc, 0xaf, 0x38, 0xf5, 0xa7, 0xac, 0xe9, + 0x99, 0x53, 0x50, 0xb7, 0xe7, 0x27, 0x69, 0x8c, 0x82, 0x66, 0xac, 0x18, 0xa7, 0xb2, 0x9d, 0xa4, 0xbf, 0x58, 0xbb, + 0xdc, 0xdd, 0x25, 0xca, 0xa4, 0xcb, 0xb3, 0x36, 0xe3, 0xbf, 0x92, 0x4a, 0x69, 0xc5, 0xee, 0xd4, 0xa9, 0xd1, 0x71, + 0x6e, 0x09, 0x6a, 0x34, 0x84, 0xe0, 0xcb, 0x40, 0x7a, 0xc8, 0x79, 0x59, 0x3a, 0xca, 0x73, 0xe0, 0x3c, 0x94, 0xed, + 0xc9, 0x83, 0x19, 0x68, 0x8f, 0xfe, 0x36, 0x8c, 0xa6, 0x96, 0xcc, 0xdf, 0xb9, 0x6a, 0xc3, 0x0e, 0xd1, 0xd0, 0x22, + 0x98, 0xae, 0x36, 0xc7, 0xed, 0x80, 0xf7, 0x72, 0x29, 0xb9, 0x3a, 0xd7, 0xae, 0xcf, 0x92, 0xe7, 0x67, 0xef, 0xc3, + 0x56, 0xd2, 0x6d, 0xfa, 0x4f, 0xde, 0x4e, 0x6d, 0x72, 0x7d, 0x9b, 0x56, 0xba, 0x2c, 0x9b, 0x20, 0x4a, 0x33, 0x34, + 0x71, 0xfe, 0x30, 0xdf, 0x4e, 0xfd, 0x13, 0xc1, 0x72, 0xc6, 0xa6, 0xec, 0xc7, 0xf9, 0xaa, 0x14, 0x66, 0xaa, 0x90, + 0x40, 0xda, 0x14, 0x7d, 0x49, 0x8d, 0x0b, 0x73, 0x3a, 0xbc, 0xa7, 0x93, 0x5c, 0x40, 0xfa, 0x34, 0x42, 0xe8, 0x63, + 0x27, 0x34, 0xe7, 0x68, 0xe4, 0xcd, 0x7f, 0x32, 0xf3, 0x5d, 0xf8, 0x41, 0x34, 0x68, 0xf8, 0x1d, 0x6f, 0x86, 0xda, + 0x76, 0xfa, 0x6a, 0x6f, 0xd3, 0x3c, 0x04, 0xb5, 0x6f, 0x34, 0x09, 0x1a, 0xf8, 0x7a, 0xf6, 0x03, 0x3e, 0xd9, 0x6c, + 0xaa, 0xa9, 0xf6, 0xc3, 0xaf, 0x26, 0xec, 0x90, 0x2a, 0xcb, 0xd0, 0x0c, 0x12, 0x06, 0xed, 0x0a, 0xdf, 0xd3, 0x25, + 0x4c, 0x02}; + +// Backwards compatibility alias +#define INDEX_GZ INDEX_BR + +#endif // USE_WEBSERVER_GZIP + +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/server_index_v3.h b/esphome/components/web_server/server_index_v3.h index 39518197a3..7bf86f6e8b 100644 --- a/esphome/components/web_server/server_index_v3.h +++ b/esphome/components/web_server/server_index_v3.h @@ -6,4050 +6,7619 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { +#ifdef USE_WEBSERVER_GZIP const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0xeb, 0x7a, 0x1b, 0xb7, 0xb2, 0x20, 0xfa, - 0xfb, 0xcc, 0x53, 0x48, 0xbd, 0x1d, 0xa5, 0x21, 0x82, 0x2d, 0x92, 0xba, 0x58, 0x6e, 0x0a, 0xe2, 0xf8, 0x1a, 0x3b, - 0x71, 0x6c, 0xc7, 0x72, 0xec, 0x38, 0x0c, 0xb7, 0x0c, 0x36, 0x41, 0x12, 0x76, 0x13, 0x60, 0x1a, 0xa0, 0x25, 0x85, - 0xe4, 0xbb, 0x9f, 0xaf, 0x70, 0xe9, 0x46, 0x93, 0xb4, 0xd7, 0x5a, 0x73, 0x66, 0xce, 0x37, 0x3b, 0x7b, 0x59, 0x6c, - 0xdc, 0x51, 0x28, 0x14, 0xaa, 0x0a, 0x55, 0x85, 0x8b, 0xfd, 0x91, 0xcc, 0xf4, 0xdd, 0x9c, 0xed, 0x4d, 0xf5, 0x2c, - 0xbf, 0xbc, 0x70, 0xff, 0x32, 0x3a, 0xba, 0xbc, 0xc8, 0xb9, 0xf8, 0xb2, 0x57, 0xb0, 0x9c, 0xf0, 0x4c, 0x8a, 0xbd, - 0x69, 0xc1, 0xc6, 0x64, 0x44, 0x35, 0x4d, 0xf9, 0x8c, 0x4e, 0xd8, 0xde, 0xd1, 0xe5, 0xc5, 0x8c, 0x69, 0xba, 0x97, - 0x4d, 0x69, 0xa1, 0x98, 0x26, 0xbf, 0xbf, 0x7b, 0xd6, 0x3c, 0xbf, 0xbc, 0x50, 0x59, 0xc1, 0xe7, 0x7a, 0x0f, 0x9a, - 0x24, 0x33, 0x39, 0x5a, 0xe4, 0xec, 0xf2, 0xe8, 0xe8, 0xe6, 0xe6, 0x26, 0xf9, 0xac, 0xfe, 0xc7, 0x57, 0x5a, 0xec, - 0xfd, 0x52, 0x90, 0xd7, 0xc3, 0xcf, 0x2c, 0xd3, 0xc9, 0x88, 0x8d, 0xb9, 0x60, 0x6f, 0x0a, 0x39, 0x67, 0x85, 0xbe, - 0xeb, 0x42, 0xe6, 0x4f, 0x05, 0x89, 0x39, 0xd6, 0x98, 0x21, 0x72, 0xa9, 0xf7, 0xb8, 0xd8, 0xe3, 0xbd, 0x5f, 0x0a, - 0x93, 0xb2, 0x64, 0x62, 0x31, 0x63, 0x05, 0x1d, 0xe6, 0x2c, 0xdd, 0x6f, 0xe1, 0x4c, 0x8a, 0x31, 0x9f, 0x2c, 0xca, - 0xef, 0x9b, 0x82, 0x6b, 0xff, 0xfb, 0x2b, 0xcd, 0x17, 0x2c, 0x65, 0x6b, 0x94, 0xf2, 0xbe, 0x1e, 0x10, 0x66, 0x5a, - 0xfe, 0x52, 0x35, 0x1c, 0xff, 0x64, 0x9a, 0xbc, 0x9b, 0x33, 0x39, 0xde, 0xd3, 0xfb, 0x24, 0x52, 0x77, 0xb3, 0xa1, - 0xcc, 0xa3, 0x9e, 0x6e, 0x44, 0x51, 0x0a, 0x65, 0x30, 0x43, 0xdd, 0x4c, 0x0a, 0xa5, 0xf7, 0x04, 0x27, 0x37, 0x5c, - 0x8c, 0xe4, 0x0d, 0xbe, 0x11, 0x44, 0xf0, 0xe4, 0x6a, 0x4a, 0x47, 0xf2, 0xe6, 0xad, 0x94, 0xfa, 0xe0, 0x20, 0x76, - 0xdf, 0x77, 0x8f, 0xaf, 0xae, 0x08, 0x21, 0x5f, 0x25, 0x1f, 0xed, 0xb5, 0x56, 0xab, 0x20, 0x35, 0x11, 0x54, 0xf3, - 0xaf, 0xcc, 0x56, 0x42, 0x07, 0x07, 0x11, 0x1d, 0xc9, 0xb9, 0x66, 0xa3, 0x2b, 0x7d, 0x97, 0xb3, 0xab, 0x29, 0x63, - 0x5a, 0x45, 0x5c, 0xec, 0x3d, 0x91, 0xd9, 0x62, 0xc6, 0x84, 0x4e, 0xe6, 0x85, 0xd4, 0x12, 0x06, 0x76, 0x70, 0x10, - 0x15, 0x6c, 0x9e, 0xd3, 0x8c, 0x41, 0xfe, 0xe3, 0xab, 0xab, 0xaa, 0x46, 0x55, 0x08, 0x7f, 0x11, 0xe4, 0xca, 0x0c, - 0x3d, 0x46, 0xf8, 0x83, 0x20, 0x82, 0xdd, 0xec, 0x7d, 0x60, 0xf4, 0xcb, 0xaf, 0x74, 0xde, 0xcd, 0x72, 0xaa, 0xd4, - 0xde, 0x33, 0xb9, 0x34, 0xd3, 0x28, 0x16, 0x99, 0x96, 0x45, 0xac, 0x31, 0xc3, 0x02, 0x2d, 0xf9, 0x38, 0xd6, 0x53, - 0xae, 0x92, 0xeb, 0x7b, 0x99, 0x52, 0x6f, 0x99, 0x5a, 0xe4, 0xfa, 0x1e, 0xd9, 0x6f, 0x61, 0xb1, 0x4f, 0xc8, 0x17, - 0x81, 0xf4, 0xb4, 0x90, 0x37, 0x7b, 0x4f, 0x8b, 0x42, 0x16, 0x71, 0xf4, 0xf8, 0xea, 0xca, 0x96, 0xd8, 0xe3, 0x6a, - 0x4f, 0x48, 0xbd, 0x57, 0xb6, 0x07, 0xd0, 0x4e, 0xf6, 0x7e, 0x57, 0x6c, 0xef, 0xd3, 0x42, 0x28, 0x3a, 0x66, 0x8f, - 0xaf, 0xae, 0x3e, 0xed, 0xc9, 0x62, 0xef, 0x53, 0xa6, 0xd4, 0xa7, 0x3d, 0x2e, 0x94, 0x66, 0x74, 0x94, 0x44, 0xa8, - 0x6b, 0x3a, 0xcb, 0x94, 0x7a, 0xc7, 0x6e, 0x35, 0xd1, 0xd8, 0x7c, 0x6a, 0xc2, 0xd6, 0x13, 0xa6, 0xf7, 0x54, 0x39, - 0xaf, 0x18, 0x2d, 0x73, 0xa6, 0xf7, 0x34, 0x31, 0xf9, 0xd2, 0xc1, 0x9f, 0xd9, 0x4f, 0xdd, 0xe5, 0xe3, 0xf8, 0x46, - 0x1c, 0x1c, 0xe8, 0x12, 0xd0, 0x68, 0xe9, 0x56, 0x88, 0xb0, 0x7d, 0x9f, 0x76, 0x70, 0xc0, 0x92, 0x9c, 0x89, 0x89, - 0x9e, 0x12, 0x42, 0xda, 0x5d, 0x71, 0x70, 0x10, 0x6b, 0xf2, 0x41, 0x24, 0x13, 0xa6, 0x63, 0x86, 0x10, 0xae, 0x6a, - 0x1f, 0x1c, 0xc4, 0x16, 0x08, 0x92, 0x68, 0x03, 0xb8, 0x1a, 0x8c, 0x51, 0xe2, 0xa0, 0x7f, 0x75, 0x27, 0xb2, 0x38, - 0x1c, 0x3f, 0xc2, 0xe2, 0xe0, 0xe0, 0x83, 0x48, 0x14, 0xb4, 0x88, 0x35, 0x42, 0xeb, 0x82, 0xe9, 0x45, 0x21, 0xf6, - 0xf4, 0x5a, 0xcb, 0x2b, 0x5d, 0x70, 0x31, 0x89, 0xd1, 0xd2, 0xa7, 0x05, 0x15, 0xd7, 0x6b, 0x3b, 0xdc, 0xdf, 0x0a, - 0xc2, 0xc9, 0x25, 0xf4, 0xf8, 0x4c, 0xc6, 0x0e, 0x07, 0x39, 0x21, 0x91, 0x32, 0x75, 0xa3, 0x1e, 0x4f, 0x79, 0x23, - 0x8a, 0xb0, 0x1d, 0x25, 0xfe, 0x22, 0x10, 0x16, 0x1a, 0x50, 0x37, 0x49, 0x12, 0x8d, 0xc8, 0xe5, 0xd2, 0x83, 0x85, - 0x07, 0x13, 0xed, 0xf1, 0x7e, 0x6b, 0x90, 0xea, 0xa4, 0x60, 0xa3, 0x45, 0xc6, 0xe2, 0x58, 0x60, 0x85, 0x25, 0x22, - 0x97, 0xa2, 0x11, 0x17, 0xe4, 0x12, 0xd6, 0xbb, 0xa8, 0x2f, 0x36, 0x21, 0xfb, 0x2d, 0xe4, 0x06, 0x59, 0xf8, 0x11, - 0x02, 0x88, 0xdd, 0x80, 0x0a, 0x42, 0x22, 0xb1, 0x98, 0x0d, 0x59, 0x11, 0x95, 0xc5, 0xba, 0x35, 0xbc, 0x58, 0x28, - 0xb6, 0x97, 0x29, 0xb5, 0x37, 0x5e, 0x88, 0x4c, 0x73, 0x29, 0xf6, 0xa2, 0x46, 0xd1, 0x88, 0x2c, 0x3e, 0x94, 0xe8, - 0x10, 0xa1, 0x35, 0x8a, 0x15, 0x6a, 0xf0, 0xbe, 0x6c, 0xb4, 0x07, 0x18, 0x46, 0x89, 0xba, 0xae, 0x3d, 0x07, 0x01, - 0x86, 0x39, 0x4c, 0x72, 0x8d, 0xff, 0xb4, 0x3b, 0x1f, 0xa6, 0x78, 0x23, 0x7a, 0x3c, 0xd9, 0xde, 0x29, 0x44, 0x27, - 0x33, 0x3a, 0x8f, 0x19, 0xb9, 0x64, 0x06, 0xbb, 0xa8, 0xc8, 0x60, 0xac, 0xb5, 0x85, 0xeb, 0xb1, 0x94, 0x25, 0x15, - 0x4e, 0xa1, 0x54, 0x27, 0x63, 0x59, 0x3c, 0xa5, 0xd9, 0x14, 0xea, 0x95, 0x18, 0x33, 0xf2, 0x1b, 0x2e, 0x2b, 0x18, - 0xd5, 0xec, 0x69, 0xce, 0xe0, 0x2b, 0x8e, 0x4c, 0xcd, 0x08, 0x61, 0x05, 0x5b, 0x3d, 0xe7, 0xfa, 0x95, 0x14, 0x19, - 0xeb, 0xaa, 0x00, 0xbf, 0xcc, 0xca, 0x3f, 0xd4, 0xba, 0xe0, 0xc3, 0x85, 0x66, 0x71, 0x24, 0xa0, 0x44, 0x84, 0x15, - 0xc2, 0x22, 0xd1, 0xec, 0x56, 0x3f, 0x96, 0x42, 0x33, 0xa1, 0x09, 0xf3, 0x50, 0xc5, 0x3c, 0xa1, 0xf3, 0x39, 0x13, - 0xa3, 0xc7, 0x53, 0x9e, 0x8f, 0x62, 0x81, 0xd6, 0x68, 0x8d, 0x7f, 0x17, 0x04, 0x26, 0x49, 0x2e, 0x79, 0x0a, 0xff, - 0x7c, 0x7b, 0x3a, 0xb1, 0x26, 0x97, 0x66, 0x5b, 0x30, 0x12, 0x45, 0xdd, 0xb1, 0x2c, 0x62, 0x37, 0x85, 0x3d, 0x20, - 0x5d, 0xd0, 0xc7, 0xdb, 0x45, 0xce, 0x14, 0x62, 0x0d, 0x22, 0xca, 0x75, 0x74, 0x10, 0xfe, 0xad, 0x88, 0x19, 0x2c, - 0x00, 0x47, 0x29, 0x37, 0x24, 0xf0, 0x25, 0x77, 0x9b, 0x6a, 0x54, 0x12, 0xb5, 0x8f, 0x82, 0x8c, 0x78, 0xa2, 0x8b, - 0x85, 0xd2, 0x6c, 0xf4, 0xee, 0x6e, 0xce, 0x14, 0xfe, 0xb9, 0x20, 0x1f, 0x45, 0xef, 0xa3, 0x48, 0xd8, 0x6c, 0xae, - 0xef, 0xae, 0x0c, 0x35, 0x4f, 0xa3, 0x08, 0xff, 0x6d, 0x8a, 0x16, 0x8c, 0x66, 0x40, 0xd2, 0x1c, 0xc8, 0xde, 0xc8, - 0xfc, 0x6e, 0xcc, 0xf3, 0xfc, 0x6a, 0x31, 0x9f, 0xcb, 0x42, 0x63, 0x2d, 0xc8, 0x52, 0xcb, 0x0a, 0x3e, 0xb0, 0xa2, - 0x4b, 0x75, 0xc3, 0x75, 0x36, 0x8d, 0x35, 0x5a, 0x66, 0x54, 0xb1, 0xbd, 0x47, 0x52, 0xe6, 0x8c, 0x8a, 0x94, 0x13, - 0xde, 0xfb, 0xb9, 0x48, 0xc5, 0x22, 0xcf, 0xbb, 0xc3, 0x82, 0xd1, 0x2f, 0x5d, 0x93, 0x6d, 0x0f, 0x87, 0xd4, 0xfc, - 0x7e, 0x58, 0x14, 0xf4, 0x0e, 0x0a, 0x12, 0x02, 0xc5, 0x7a, 0x3c, 0xfd, 0xf9, 0xea, 0xf5, 0xab, 0xc4, 0xee, 0x15, - 0x3e, 0xbe, 0x8b, 0x79, 0xb9, 0xff, 0xf8, 0x1a, 0x8f, 0x0b, 0x39, 0xdb, 0xe8, 0xda, 0x82, 0x8e, 0x77, 0xbf, 0x31, - 0x04, 0x46, 0xf8, 0xbe, 0x6d, 0x3a, 0x1c, 0xc1, 0x2b, 0x83, 0xf9, 0x90, 0x49, 0x5c, 0xbf, 0xf0, 0x4f, 0x6a, 0x93, - 0x63, 0x8e, 0xbe, 0x3f, 0x5a, 0x5d, 0xdc, 0x2d, 0x19, 0x31, 0xe3, 0x9c, 0xc3, 0xc1, 0x08, 0x63, 0xcc, 0xa8, 0xce, - 0xa6, 0x4b, 0x66, 0x1a, 0x5b, 0xfb, 0x11, 0xb3, 0xf5, 0x1a, 0xbf, 0x92, 0x1e, 0xeb, 0xf5, 0x3e, 0x21, 0xdc, 0xd0, - 0x2b, 0xa2, 0x57, 0x2b, 0x4e, 0x08, 0x47, 0xf8, 0x2d, 0x27, 0x4b, 0xea, 0x27, 0x04, 0x27, 0x1b, 0x6c, 0xcf, 0xd4, - 0x52, 0x19, 0x38, 0x01, 0xbf, 0xb2, 0x42, 0xb3, 0x22, 0xd5, 0x02, 0x17, 0x6c, 0x9c, 0xc3, 0x38, 0xf6, 0xdb, 0x78, - 0x4a, 0xd5, 0xe3, 0x29, 0x15, 0x13, 0x36, 0x4a, 0x5f, 0xc9, 0x35, 0x66, 0x82, 0x44, 0x63, 0x2e, 0x68, 0xce, 0xff, - 0x61, 0xa3, 0xc8, 0x9d, 0x0b, 0xef, 0xf5, 0x1e, 0xbb, 0xd5, 0x4c, 0x8c, 0xd4, 0xde, 0xf3, 0x77, 0xbf, 0xbe, 0x74, - 0x8b, 0x59, 0x3b, 0x2b, 0xd0, 0x52, 0x2d, 0xe6, 0xac, 0x88, 0x11, 0x76, 0x67, 0xc5, 0x53, 0x6e, 0xe8, 0xe4, 0xaf, - 0x74, 0x6e, 0x53, 0xb8, 0xfa, 0x7d, 0x3e, 0xa2, 0x9a, 0xbd, 0x61, 0x62, 0xc4, 0xc5, 0x84, 0xec, 0xb7, 0x6d, 0xfa, - 0x94, 0xba, 0x8c, 0x51, 0x99, 0x74, 0x7d, 0xef, 0x69, 0x6e, 0xe6, 0x5e, 0x7e, 0x2e, 0x62, 0xb4, 0x56, 0x9a, 0x6a, - 0x9e, 0xed, 0xd1, 0xd1, 0xe8, 0x85, 0xe0, 0x9a, 0x9b, 0x11, 0x16, 0xb0, 0x44, 0x80, 0xab, 0xcc, 0x9e, 0x1a, 0x7e, - 0xe4, 0x31, 0xc2, 0x71, 0xec, 0xce, 0x82, 0x29, 0x72, 0x6b, 0x76, 0x70, 0x50, 0x51, 0xfe, 0x1e, 0x4b, 0x6d, 0x26, - 0xe9, 0x0f, 0x50, 0x32, 0x5f, 0x28, 0x58, 0x6c, 0xdf, 0x05, 0x1c, 0x34, 0x72, 0xa8, 0x58, 0xf1, 0x95, 0x8d, 0x4a, - 0x04, 0x51, 0x31, 0x5a, 0x6e, 0xf4, 0xe1, 0xb6, 0x87, 0x26, 0xfd, 0x41, 0x37, 0x24, 0xe1, 0xcc, 0x21, 0xbb, 0xe5, - 0x54, 0x38, 0x53, 0x25, 0x51, 0x89, 0xe1, 0x40, 0x2d, 0x09, 0x8b, 0x22, 0x7e, 0x7e, 0xf3, 0x58, 0x00, 0x0f, 0x11, - 0x52, 0x0e, 0x7f, 0xe6, 0x3e, 0xfd, 0x6a, 0x0e, 0x0f, 0x85, 0x05, 0xc2, 0xda, 0x8e, 0x54, 0x21, 0xb4, 0x46, 0x58, - 0xfb, 0xe1, 0x5a, 0xa2, 0xe4, 0xf9, 0x22, 0x38, 0xb5, 0xc9, 0x5b, 0x6e, 0x8e, 0x6d, 0xa0, 0x6d, 0x54, 0xb3, 0x83, - 0x83, 0x98, 0x25, 0x25, 0x62, 0x90, 0xfd, 0xb6, 0x5b, 0xa4, 0x00, 0x5a, 0xdf, 0x18, 0x37, 0xf4, 0x6c, 0x18, 0x9c, - 0x7d, 0x96, 0x08, 0xf9, 0x30, 0xcb, 0x98, 0x52, 0xb2, 0x38, 0x38, 0xd8, 0x37, 0xe5, 0x4b, 0xce, 0x02, 0x16, 0xf1, - 0xf5, 0x8d, 0xa8, 0x86, 0x80, 0xaa, 0xd3, 0xd6, 0xf3, 0x4d, 0xa4, 0xe2, 0x9b, 0x3c, 0x13, 0x92, 0x46, 0xd7, 0xd7, - 0x51, 0x43, 0x63, 0x07, 0x87, 0x09, 0xf3, 0x5d, 0xdf, 0x3d, 0x61, 0x96, 0x2d, 0x34, 0x4c, 0xc8, 0x16, 0x68, 0x76, - 0xf2, 0x83, 0x71, 0x7d, 0x48, 0x58, 0x63, 0x85, 0xd6, 0xc1, 0x8a, 0xee, 0x6c, 0xda, 0xf0, 0x37, 0x76, 0xe9, 0x96, - 0x13, 0xc3, 0x53, 0x04, 0xeb, 0xd8, 0x67, 0x83, 0x35, 0x36, 0xb0, 0xf7, 0xb3, 0x91, 0x66, 0xa0, 0x7d, 0x3d, 0xe8, - 0xba, 0x7c, 0xa2, 0x2c, 0xe4, 0x0a, 0xf6, 0xf7, 0x82, 0x29, 0x6d, 0x11, 0x39, 0xd6, 0x58, 0x62, 0x38, 0xa3, 0x36, - 0x99, 0xce, 0x1a, 0x4b, 0xba, 0x6b, 0x6c, 0xaf, 0xe7, 0x70, 0x36, 0x2a, 0x40, 0xea, 0xef, 0xe3, 0x13, 0x8c, 0x55, - 0xa3, 0xd5, 0xea, 0x2d, 0xf7, 0xad, 0x54, 0x6b, 0x59, 0xf2, 0x6b, 0x1b, 0x8b, 0xc2, 0x04, 0x72, 0x87, 0xf3, 0x7e, - 0xdb, 0x8d, 0x5f, 0x0c, 0xc8, 0x7e, 0xab, 0xc4, 0x62, 0x07, 0x56, 0x3b, 0x1e, 0x0b, 0xc5, 0xd7, 0xb6, 0x29, 0x64, - 0xce, 0xfa, 0x1a, 0xbe, 0x24, 0xd3, 0x2d, 0x5c, 0x9d, 0x92, 0x3e, 0x70, 0x1d, 0xc9, 0x74, 0xf0, 0x2d, 0x7c, 0xf2, - 0x14, 0x21, 0xd6, 0xdb, 0x79, 0x15, 0xe1, 0xf8, 0x5a, 0x27, 0x1c, 0x1b, 0xd3, 0x88, 0xe6, 0x65, 0x95, 0xa8, 0x44, - 0x33, 0xb7, 0xd5, 0xab, 0x2c, 0x2c, 0xcc, 0x60, 0xaa, 0x29, 0x05, 0x4d, 0xbc, 0xa2, 0x33, 0xa6, 0x62, 0x86, 0xf0, - 0xb7, 0x0a, 0x58, 0xfc, 0x84, 0x22, 0x83, 0xe0, 0x0c, 0x55, 0x70, 0x86, 0x02, 0xbb, 0x0b, 0x4c, 0x5a, 0x7d, 0xcb, - 0x29, 0xcc, 0xfa, 0x6a, 0x50, 0xf1, 0x76, 0xc1, 0xe4, 0xcd, 0xe1, 0xec, 0x10, 0xdc, 0xc3, 0xcf, 0xa6, 0x59, 0xa0, - 0x19, 0x16, 0x42, 0x21, 0xbc, 0xdf, 0xda, 0x5c, 0x49, 0x5f, 0xaa, 0x9a, 0x63, 0x7f, 0x00, 0xeb, 0x60, 0x8e, 0x8d, - 0x84, 0x2b, 0xf3, 0xb7, 0xb6, 0xd5, 0x00, 0x6c, 0x57, 0x80, 0x19, 0xc9, 0x38, 0xa7, 0x3a, 0x6e, 0x1f, 0xb5, 0x80, - 0x31, 0xfd, 0xca, 0xe0, 0x54, 0x41, 0x68, 0x7b, 0x2a, 0x2c, 0x59, 0x08, 0x35, 0xe5, 0x63, 0x1d, 0xff, 0x2e, 0x0c, - 0x51, 0x61, 0xb9, 0x62, 0x20, 0xe1, 0x04, 0xec, 0xb1, 0x21, 0x38, 0xbf, 0x0b, 0xe8, 0xa7, 0x5b, 0x1e, 0x44, 0x6e, - 0xa4, 0x86, 0x70, 0x01, 0x79, 0xa8, 0x58, 0xeb, 0x8a, 0xcc, 0x94, 0x8c, 0x1b, 0x70, 0x8f, 0xed, 0x9e, 0x6d, 0x31, - 0x75, 0xd4, 0x40, 0x04, 0x1c, 0xac, 0x48, 0x43, 0x12, 0xe1, 0x12, 0x75, 0xa2, 0xe5, 0x4b, 0x79, 0xc3, 0x8a, 0xc7, - 0x14, 0x06, 0x9f, 0xda, 0xea, 0x6b, 0x7b, 0x14, 0x18, 0x8a, 0xaf, 0xbb, 0x1e, 0x5f, 0xae, 0xcd, 0xc4, 0xdf, 0x14, - 0x72, 0xc6, 0x15, 0x03, 0xbe, 0xcd, 0xc2, 0x5f, 0xc0, 0x46, 0x33, 0x3b, 0x12, 0x8e, 0x1b, 0x56, 0xe2, 0xd7, 0xc3, - 0x97, 0x75, 0xfc, 0xba, 0xbe, 0xf7, 0x74, 0xe2, 0x29, 0x60, 0x7d, 0x1f, 0x23, 0x1c, 0x3b, 0xf1, 0x22, 0x38, 0xe9, - 0x92, 0x29, 0x72, 0xc7, 0xfc, 0x6a, 0xa5, 0x03, 0x31, 0xae, 0xc6, 0x39, 0x32, 0xbb, 0x6d, 0xd0, 0x9a, 0x8e, 0x46, - 0xc0, 0xe2, 0x15, 0x32, 0xcf, 0x83, 0xc3, 0x0a, 0x8b, 0x6e, 0x79, 0x3c, 0x5d, 0xdf, 0x7b, 0x7a, 0xf5, 0xbd, 0x13, - 0x0a, 0xf2, 0xc3, 0x43, 0xca, 0x0f, 0x54, 0x8c, 0x58, 0x01, 0x72, 0x65, 0xb0, 0x5a, 0xee, 0x9c, 0x7d, 0x2c, 0x85, - 0x60, 0x99, 0x66, 0x23, 0x10, 0x5a, 0x04, 0xd1, 0xc9, 0x54, 0x2a, 0x5d, 0x26, 0x56, 0xa3, 0x17, 0xa1, 0x10, 0x9a, - 0x64, 0x34, 0xcf, 0x63, 0x2b, 0xa0, 0xcc, 0xe4, 0x57, 0xb6, 0x63, 0xd4, 0xdd, 0xda, 0x90, 0xcb, 0x66, 0x58, 0xd0, - 0x0c, 0x4b, 0xd4, 0x3c, 0xe7, 0x19, 0x2b, 0x0f, 0xaf, 0xab, 0x84, 0x8b, 0x11, 0xbb, 0x05, 0x3a, 0x82, 0x2e, 0x2f, - 0x2f, 0x5b, 0xb8, 0x8d, 0xd6, 0x16, 0xe0, 0xcb, 0x2d, 0xc0, 0x7e, 0xe7, 0xd8, 0xb4, 0x82, 0xf8, 0x72, 0x27, 0x59, - 0x43, 0xc1, 0x59, 0xc9, 0xbd, 0xa0, 0x65, 0xc9, 0x33, 0xc2, 0x23, 0x96, 0x33, 0xcd, 0x3c, 0x39, 0x07, 0x66, 0xda, - 0x6e, 0xdd, 0xb7, 0x25, 0xfc, 0x4a, 0x74, 0xf2, 0xbb, 0xcc, 0xaf, 0xb9, 0x2a, 0x45, 0xf7, 0x6a, 0x79, 0x2a, 0x68, - 0xf7, 0xb4, 0x5d, 0x1e, 0xaa, 0x35, 0xcd, 0xa6, 0x56, 0x62, 0x8f, 0xb7, 0xa6, 0x54, 0xb5, 0xe1, 0x48, 0x7b, 0xb9, - 0x89, 0xfe, 0x2c, 0xdc, 0x30, 0x77, 0x81, 0xe0, 0xca, 0x11, 0x05, 0x06, 0x42, 0xa0, 0x5d, 0xb6, 0xc7, 0x34, 0xcf, - 0x87, 0x34, 0xfb, 0x52, 0xc7, 0xfe, 0x0a, 0x0d, 0xc8, 0x26, 0x35, 0x0e, 0xb2, 0x02, 0x92, 0x15, 0xce, 0xdb, 0x53, - 0xe9, 0xda, 0x46, 0x89, 0xf7, 0x5b, 0x15, 0xda, 0xd7, 0x17, 0xfa, 0x9b, 0xd8, 0x6e, 0x46, 0x24, 0xdc, 0xcc, 0x62, - 0xa0, 0x02, 0xff, 0x12, 0xe3, 0x3c, 0x3d, 0x70, 0x78, 0x07, 0x82, 0xc7, 0x7a, 0x63, 0x20, 0x1a, 0x2d, 0xd7, 0x23, - 0xae, 0xbe, 0x0d, 0x81, 0xff, 0x2d, 0xa3, 0x7c, 0x12, 0xf4, 0xf0, 0xef, 0x0e, 0xb4, 0xa4, 0x71, 0x8e, 0x71, 0x2e, - 0x47, 0xe6, 0x18, 0x0a, 0x4f, 0x68, 0x7e, 0x01, 0xe6, 0xc5, 0xe0, 0xfb, 0x6b, 0x9b, 0x65, 0xf8, 0x32, 0x18, 0x86, - 0xea, 0x86, 0x0c, 0x45, 0x0d, 0x05, 0x1c, 0x51, 0x15, 0xe6, 0xcc, 0x95, 0x35, 0x51, 0xd2, 0x71, 0xed, 0x56, 0x1c, - 0x77, 0x34, 0xb7, 0x20, 0x71, 0x1c, 0x2b, 0x90, 0xe6, 0x3c, 0x7f, 0x5f, 0xcd, 0x42, 0x6d, 0xcd, 0x42, 0x25, 0x81, - 0xb4, 0x85, 0x2a, 0x64, 0x0e, 0xaa, 0xa7, 0x5a, 0xa0, 0xb0, 0x14, 0xb0, 0xac, 0x09, 0x50, 0x68, 0x54, 0x12, 0xdc, - 0x9c, 0x68, 0x5c, 0x38, 0x51, 0xc7, 0xe1, 0x1a, 0x90, 0x8c, 0xaa, 0x8a, 0x44, 0x76, 0x73, 0xd4, 0x64, 0x5f, 0x89, - 0x0b, 0xb4, 0xc1, 0xdf, 0xaf, 0xd7, 0x0e, 0x4a, 0x0c, 0xb9, 0xd5, 0xa9, 0x31, 0xc6, 0x01, 0x58, 0xb0, 0x24, 0x8e, - 0x19, 0xb6, 0xac, 0xcf, 0x26, 0x70, 0xca, 0x76, 0xf7, 0x09, 0x91, 0x15, 0x6c, 0x6a, 0x4c, 0xa5, 0xe7, 0xae, 0x24, - 0xc2, 0xd4, 0xb3, 0xa5, 0x45, 0x35, 0x71, 0x42, 0x22, 0xaf, 0x9d, 0x88, 0x7a, 0xcb, 0x9a, 0x70, 0x98, 0x06, 0xc5, - 0xd6, 0x29, 0x10, 0xd5, 0x62, 0x17, 0xbc, 0x77, 0x61, 0x4d, 0xad, 0x9d, 0x00, 0xe2, 0x45, 0x0d, 0xe2, 0x01, 0x68, - 0xa5, 0x25, 0x5e, 0x72, 0x40, 0x68, 0xbd, 0x72, 0xcc, 0x70, 0x61, 0x17, 0x62, 0x0b, 0x8a, 0x9b, 0xec, 0xa7, 0xc1, - 0x42, 0x90, 0x65, 0x15, 0xf0, 0x77, 0xe1, 0x11, 0x11, 0xc3, 0xe0, 0xc5, 0x6a, 0xb5, 0x85, 0x76, 0x3b, 0xb9, 0x50, - 0x94, 0x54, 0xd2, 0xe1, 0x6a, 0xf5, 0x4a, 0xa2, 0xd8, 0xf1, 0xbf, 0x98, 0xa1, 0x9e, 0x27, 0xba, 0x0f, 0x5f, 0x42, - 0x29, 0xc3, 0x8e, 0x56, 0x29, 0xa5, 0xe0, 0x50, 0xc7, 0xda, 0xfa, 0x42, 0xe9, 0x80, 0x72, 0x3f, 0xde, 0x22, 0x60, - 0x26, 0xd1, 0x9d, 0xd4, 0xd5, 0x94, 0x1f, 0xbb, 0xa6, 0x05, 0x42, 0x28, 0x55, 0x46, 0x96, 0xd9, 0xdf, 0x25, 0x5f, - 0x1e, 0x1c, 0xa8, 0xa0, 0xa1, 0xeb, 0x92, 0x52, 0x7c, 0x8e, 0xe1, 0x54, 0x56, 0x77, 0xc2, 0xb0, 0x2f, 0x9f, 0xfd, - 0x39, 0xb4, 0x25, 0x9d, 0xb6, 0xba, 0x20, 0x98, 0xd3, 0x1b, 0xca, 0xf5, 0x5e, 0xd9, 0x8a, 0x15, 0xcc, 0x63, 0x86, - 0x96, 0x8e, 0xdb, 0x48, 0x0a, 0x06, 0xfc, 0x23, 0x90, 0x05, 0xcf, 0x45, 0x5b, 0xc4, 0xcf, 0xa6, 0x0c, 0x54, 0xd9, - 0x9e, 0x91, 0x28, 0xc5, 0xc3, 0x7d, 0x77, 0x90, 0xb8, 0x86, 0x77, 0x8f, 0x7d, 0xbd, 0x59, 0xbd, 0x26, 0x0d, 0xcc, - 0x59, 0x31, 0x96, 0xc5, 0xcc, 0xe7, 0xad, 0x37, 0xbe, 0x1d, 0x71, 0xe4, 0xe3, 0x78, 0x67, 0xdb, 0x4e, 0x04, 0xe8, - 0x6e, 0xc8, 0xde, 0x95, 0xd4, 0x5e, 0x3b, 0x4d, 0xcb, 0x03, 0xd8, 0x2a, 0x08, 0x3d, 0x66, 0xaa, 0x50, 0xca, 0x77, - 0xea, 0xd5, 0xae, 0xd5, 0x9d, 0xec, 0xb7, 0xbb, 0xa5, 0xe4, 0xe7, 0xb1, 0xa1, 0x6b, 0x75, 0x1c, 0xee, 0x54, 0x95, - 0x8b, 0x7c, 0xe4, 0x06, 0x2b, 0x10, 0x66, 0x0e, 0x8f, 0x6e, 0x78, 0x9e, 0x57, 0xa9, 0xff, 0x09, 0x69, 0x57, 0x8e, - 0xb4, 0x4b, 0x4f, 0xda, 0x81, 0x54, 0x00, 0x69, 0xb7, 0xcd, 0x55, 0xd5, 0xe5, 0xd6, 0xf6, 0x94, 0x96, 0xa8, 0x2b, - 0x23, 0x4e, 0x43, 0x7f, 0x0b, 0x3f, 0x02, 0x54, 0x32, 0x5f, 0x5f, 0x62, 0xa7, 0x8f, 0x01, 0x31, 0xd0, 0xea, 0x34, - 0x59, 0xa8, 0xa9, 0xf8, 0x12, 0x23, 0xac, 0xd6, 0xac, 0xc4, 0xec, 0x87, 0x4f, 0x41, 0x69, 0x17, 0x4c, 0x07, 0xce, - 0x31, 0x93, 0xfc, 0x1f, 0xf1, 0x51, 0x7e, 0x76, 0xc2, 0xcd, 0x4e, 0xf9, 0xd9, 0x01, 0xad, 0xaf, 0x66, 0x37, 0xfa, - 0x3e, 0xb5, 0x37, 0xd3, 0x13, 0xe5, 0xf4, 0xaa, 0xf5, 0x5e, 0xad, 0xe2, 0x8d, 0x14, 0xd0, 0xe8, 0x3b, 0x29, 0xa5, - 0x28, 0x5b, 0x07, 0x1a, 0x10, 0x42, 0x06, 0x12, 0xd6, 0x76, 0xd2, 0xe5, 0x29, 0xf7, 0xf2, 0x5f, 0xe9, 0x79, 0x8c, - 0xe2, 0xde, 0xd6, 0x7f, 0x2c, 0x67, 0x73, 0x60, 0xc8, 0x36, 0x50, 0x7a, 0xc2, 0x5c, 0x87, 0x55, 0xfe, 0x7a, 0x47, - 0x5a, 0xad, 0x8e, 0xd9, 0x8f, 0x35, 0x6c, 0x2a, 0xa5, 0xe6, 0xfd, 0xd6, 0x7a, 0x51, 0x26, 0x95, 0x84, 0x63, 0x97, - 0x6e, 0xe5, 0xf1, 0xa6, 0x66, 0xc6, 0x67, 0xbc, 0x8e, 0x85, 0xa5, 0xc3, 0x02, 0x68, 0x5d, 0x40, 0x7e, 0x3c, 0xba, - 0x87, 0xeb, 0xbf, 0xae, 0x80, 0xb3, 0x5c, 0x6f, 0x80, 0x6f, 0xb9, 0x5e, 0xbf, 0xd7, 0x4e, 0xd2, 0xc6, 0xef, 0x77, - 0xc8, 0xbd, 0x25, 0xf4, 0xaa, 0x4c, 0x27, 0x33, 0xf6, 0x07, 0x90, 0xb6, 0xc5, 0x42, 0x92, 0xe5, 0x4c, 0x8e, 0x58, - 0x1a, 0xc9, 0x39, 0x13, 0xd1, 0x1a, 0xf4, 0xac, 0x0e, 0x01, 0xfe, 0x16, 0xf1, 0xf2, 0x6d, 0x5d, 0xdf, 0x9a, 0xbe, - 0xd7, 0x6b, 0x50, 0x85, 0xbd, 0xe4, 0x3b, 0x94, 0xb1, 0xef, 0x59, 0xa1, 0x0c, 0x4f, 0x5a, 0xb2, 0xb7, 0x2f, 0x79, - 0x75, 0x40, 0xbd, 0xe4, 0xe9, 0xb7, 0xab, 0x54, 0x02, 0x49, 0xd4, 0x4e, 0xce, 0x92, 0xe3, 0x08, 0x19, 0x8d, 0xf1, - 0x33, 0xaf, 0x31, 0x5e, 0x94, 0x1a, 0xe3, 0xe7, 0x9a, 0x2c, 0x36, 0x34, 0xc6, 0x7f, 0x08, 0xf2, 0x5c, 0xf7, 0x9e, - 0x7b, 0x6d, 0xfa, 0x1b, 0x99, 0xf3, 0xec, 0x2e, 0x8e, 0x72, 0xae, 0x9b, 0x70, 0x9b, 0x18, 0xe1, 0xa5, 0xcd, 0x00, - 0x55, 0xa3, 0xd1, 0x77, 0xaf, 0xbd, 0xfc, 0x87, 0x85, 0x20, 0xd1, 0xbd, 0x9c, 0xeb, 0x7b, 0x11, 0x9e, 0x6a, 0xf2, - 0x09, 0x7e, 0xdd, 0x5b, 0xc6, 0xbf, 0x52, 0x3d, 0x4d, 0x0a, 0x2a, 0x46, 0x72, 0x16, 0xa3, 0x46, 0x14, 0xa1, 0x44, - 0x19, 0x21, 0xe4, 0x01, 0x5a, 0xdf, 0xfb, 0x84, 0xff, 0x91, 0x24, 0xea, 0x45, 0x8d, 0xa9, 0xc6, 0x9a, 0x92, 0x4f, - 0x17, 0xf7, 0x96, 0xff, 0xc8, 0xf5, 0xe5, 0x27, 0xfc, 0x54, 0x97, 0x6a, 0x7d, 0x7c, 0xcb, 0x48, 0x8c, 0xc8, 0xe5, - 0x53, 0x3f, 0xa4, 0xc7, 0x72, 0x66, 0x15, 0xfc, 0x11, 0xc2, 0x5f, 0x41, 0xaf, 0x7b, 0xc9, 0x2b, 0x22, 0xe4, 0xee, - 0x60, 0xf6, 0x49, 0x24, 0x8d, 0xf2, 0x20, 0x3a, 0x38, 0x08, 0xd2, 0x4a, 0x16, 0x02, 0x7f, 0x96, 0xa4, 0x26, 0xaa, - 0x63, 0x46, 0xa1, 0xa5, 0xcf, 0x32, 0xe6, 0xc8, 0x37, 0x13, 0x7b, 0x4d, 0xb5, 0xdb, 0xb1, 0xbc, 0x6f, 0x75, 0x0f, - 0x09, 0xd7, 0xac, 0xa0, 0x5a, 0x16, 0x03, 0x14, 0xb2, 0x25, 0xf8, 0x15, 0x27, 0x9f, 0xfa, 0x7b, 0xff, 0xcf, 0xff, - 0xf8, 0x6b, 0xfc, 0x57, 0x31, 0xf8, 0x84, 0x05, 0x23, 0x47, 0x17, 0x71, 0x2f, 0x8d, 0xf7, 0x9b, 0xcd, 0xd5, 0x5f, - 0x47, 0xfd, 0xff, 0xa6, 0xcd, 0x7f, 0x1e, 0x36, 0xff, 0x1c, 0xa0, 0x55, 0xfc, 0xd7, 0x51, 0xaf, 0xef, 0xbe, 0xfa, - 0xff, 0x7d, 0xf9, 0x97, 0x1a, 0x1c, 0xda, 0xc4, 0x7b, 0x08, 0x1d, 0x4d, 0xf0, 0x2f, 0x82, 0x1c, 0x35, 0x9b, 0x97, - 0x47, 0x13, 0xfc, 0x93, 0x20, 0x47, 0xf0, 0xf7, 0x4e, 0x93, 0xb7, 0x6c, 0xf2, 0xf4, 0x76, 0x1e, 0x7f, 0xba, 0x5c, - 0xdd, 0x5b, 0xbe, 0xe2, 0x6b, 0x68, 0xb7, 0xff, 0xdf, 0x7f, 0xfd, 0xa5, 0xa2, 0x1f, 0x2f, 0xc9, 0xd1, 0xa0, 0x81, - 0x62, 0x93, 0x7c, 0x48, 0xec, 0x9f, 0xb8, 0x97, 0xf6, 0xff, 0xdb, 0x0d, 0x25, 0xfa, 0xf1, 0xaf, 0x4f, 0x17, 0x97, - 0x64, 0xb0, 0x8a, 0xa3, 0xd5, 0x8f, 0x68, 0x85, 0xd0, 0xea, 0x1e, 0xfa, 0x84, 0xa3, 0x49, 0x84, 0xf0, 0x6f, 0x82, - 0x1c, 0xfd, 0x78, 0x34, 0xc1, 0x7f, 0x0a, 0x72, 0x14, 0x1d, 0x4d, 0xf0, 0x23, 0x49, 0x8e, 0xfe, 0x3b, 0xee, 0xa5, - 0x56, 0x09, 0xb7, 0x32, 0xea, 0x8f, 0x15, 0xdc, 0x84, 0xd0, 0x82, 0xd1, 0x95, 0xe6, 0x3a, 0x67, 0xe8, 0xde, 0x11, - 0xc7, 0xef, 0x25, 0x00, 0x2b, 0xd6, 0xa0, 0xa4, 0x31, 0x97, 0xb0, 0xcb, 0x6b, 0x58, 0x78, 0xc0, 0xa0, 0x7b, 0x29, - 0xc7, 0x56, 0x4f, 0xa0, 0x52, 0x6d, 0x6f, 0x6f, 0x15, 0x5c, 0xdf, 0xe2, 0xc7, 0xe4, 0xbd, 0x8c, 0xdb, 0x08, 0x73, - 0x0a, 0x3f, 0x3a, 0x08, 0x7f, 0xd0, 0xee, 0xc2, 0x13, 0xb6, 0xb9, 0xc5, 0x30, 0x21, 0x2d, 0x3f, 0x13, 0x21, 0xfc, - 0x72, 0x47, 0xa6, 0x9e, 0x82, 0xfa, 0x01, 0xe1, 0x9f, 0x6b, 0xd7, 0xa3, 0xf8, 0xb1, 0x26, 0x25, 0x72, 0xbc, 0x2b, - 0x18, 0xfb, 0x40, 0xf3, 0x2f, 0xac, 0x88, 0x9f, 0x6a, 0xdc, 0xee, 0x3c, 0xc0, 0x46, 0x55, 0xbd, 0xdf, 0x46, 0xdd, - 0xf2, 0x76, 0xeb, 0xb9, 0xb4, 0xf7, 0x09, 0x70, 0x0a, 0xd7, 0xf5, 0x35, 0xb0, 0xf6, 0xfb, 0x7c, 0x4b, 0xa9, 0x55, - 0xd0, 0x9b, 0x08, 0xd5, 0xaf, 0x52, 0xb9, 0xf8, 0x4a, 0x73, 0x3e, 0xda, 0xd3, 0x6c, 0x36, 0xcf, 0xa9, 0x66, 0x7b, - 0x6e, 0xce, 0x7b, 0x14, 0x1a, 0x8a, 0x4a, 0x9e, 0xe2, 0x0f, 0x51, 0x6d, 0xda, 0x3f, 0x44, 0x52, 0xed, 0x9d, 0x18, - 0xee, 0xb3, 0x1c, 0x5f, 0x22, 0x68, 0x79, 0x5d, 0xb6, 0x79, 0x23, 0xd8, 0x6c, 0x83, 0xb2, 0x6c, 0x60, 0xce, 0x6f, - 0x85, 0xe1, 0x7e, 0x93, 0x90, 0x4e, 0x2f, 0xba, 0x50, 0x5f, 0x27, 0x97, 0x11, 0xdc, 0xe4, 0x14, 0x44, 0x30, 0xa3, - 0x3c, 0x82, 0x12, 0x94, 0xb4, 0xba, 0xf4, 0x82, 0x75, 0x69, 0xa3, 0xe1, 0xd9, 0xec, 0x8c, 0xf0, 0x3e, 0xb5, 0xf5, - 0x73, 0x3c, 0xc5, 0x23, 0xd2, 0x6c, 0xe3, 0x05, 0x69, 0x99, 0x2a, 0xdd, 0xc5, 0x45, 0xe6, 0xfa, 0x39, 0x38, 0x88, - 0x8b, 0x24, 0xa7, 0x4a, 0xbf, 0x00, 0x8d, 0x00, 0x59, 0xe0, 0x29, 0x29, 0x12, 0x76, 0xcb, 0xb2, 0x38, 0x43, 0x78, - 0xea, 0x68, 0x10, 0xea, 0xa2, 0x05, 0x09, 0x8a, 0x81, 0x9c, 0x41, 0x04, 0xeb, 0x4d, 0xfb, 0xed, 0x01, 0x21, 0x24, - 0xda, 0x6f, 0x36, 0xa3, 0x5e, 0x41, 0x7e, 0x11, 0x29, 0xa4, 0x04, 0xec, 0x34, 0xf9, 0x09, 0x92, 0x3a, 0x41, 0x52, - 0xfc, 0x48, 0x26, 0x9a, 0x29, 0x1d, 0x43, 0x32, 0x28, 0x09, 0x94, 0xc7, 0xf0, 0xe8, 0xe2, 0x28, 0x6a, 0x40, 0xaa, - 0x41, 0x51, 0x84, 0x0b, 0x72, 0xa7, 0x51, 0x3a, 0xed, 0x1f, 0x0f, 0xc2, 0x33, 0xc2, 0xa6, 0x42, 0xff, 0x77, 0xba, - 0x37, 0xed, 0xb7, 0x4c, 0xff, 0x97, 0x51, 0x2f, 0x2e, 0x88, 0xb2, 0x6c, 0x5c, 0x4f, 0xa5, 0x82, 0x99, 0xf9, 0xa2, - 0xd4, 0x0d, 0xd0, 0xf5, 0x3d, 0x22, 0xcd, 0x4e, 0x1a, 0x8f, 0xc2, 0x99, 0x34, 0xa1, 0x43, 0x07, 0x0a, 0x9c, 0x13, - 0x28, 0x8f, 0x0b, 0x02, 0x9d, 0x56, 0xd5, 0xee, 0x74, 0xea, 0x12, 0x7e, 0x8c, 0x7e, 0xec, 0xfd, 0x29, 0xd2, 0xdf, - 0x84, 0x1d, 0xc1, 0x9f, 0x62, 0xb5, 0x82, 0xbf, 0xbf, 0x89, 0x1e, 0x0c, 0xcb, 0xa4, 0xfd, 0xe2, 0xd2, 0x7e, 0x82, - 0x34, 0xc1, 0x52, 0x33, 0x60, 0xac, 0x4a, 0x7e, 0xcc, 0x2e, 0xce, 0x98, 0xd8, 0x19, 0x1c, 0x1c, 0xf0, 0x3e, 0x6d, - 0xb4, 0x07, 0x70, 0x23, 0x50, 0x68, 0xf5, 0x81, 0xeb, 0x69, 0x1c, 0x1d, 0x5d, 0x46, 0xa8, 0x17, 0xed, 0xc1, 0x2a, - 0x77, 0x65, 0x83, 0x38, 0x58, 0x67, 0x0d, 0x4d, 0xd3, 0xd1, 0x25, 0x69, 0xf5, 0x62, 0x61, 0x89, 0x7c, 0x8e, 0x70, - 0xe6, 0x68, 0x6a, 0x0b, 0x8f, 0x50, 0x43, 0x88, 0x86, 0xff, 0x1e, 0xa1, 0xc6, 0x54, 0x37, 0xc6, 0x28, 0xcd, 0xe0, - 0x6f, 0x3c, 0x22, 0x84, 0x34, 0x3b, 0x65, 0x45, 0x7f, 0x58, 0x52, 0x94, 0x8e, 0xbd, 0x7a, 0xb4, 0x6f, 0x36, 0x87, - 0x6c, 0xc4, 0xbc, 0xcf, 0x06, 0xab, 0x55, 0x74, 0xd1, 0xbb, 0x8c, 0x50, 0x23, 0xf6, 0x68, 0x77, 0xe4, 0xf1, 0x0e, - 0x21, 0x2c, 0x06, 0x6b, 0x77, 0x03, 0x75, 0xc3, 0x6a, 0xb7, 0x4d, 0xcb, 0x6a, 0xff, 0x07, 0x64, 0x81, 0xad, 0x4b, - 0xb9, 0xc7, 0xf2, 0xb7, 0x73, 0x98, 0xaa, 0xc7, 0x6d, 0x49, 0x5a, 0xb8, 0x20, 0x5e, 0xdd, 0x4d, 0x89, 0xae, 0xf0, - 0x3f, 0x23, 0x55, 0x71, 0xdc, 0xcf, 0xf1, 0x74, 0x40, 0x04, 0x35, 0xf2, 0x4b, 0xd7, 0x2b, 0xd3, 0x59, 0x4e, 0x6e, - 0xd8, 0xc6, 0xfd, 0x6f, 0x0e, 0x77, 0x32, 0x8f, 0x75, 0x92, 0x2d, 0x8a, 0x82, 0x09, 0xfd, 0x4a, 0x8e, 0x1c, 0x63, - 0xc7, 0x72, 0x90, 0xad, 0xe0, 0x62, 0x17, 0x03, 0x57, 0xd7, 0xf1, 0x3b, 0x65, 0xb4, 0x95, 0xbd, 0x20, 0x23, 0xcb, - 0x70, 0x99, 0xeb, 0xde, 0xee, 0xc2, 0x89, 0xd2, 0x31, 0xc2, 0x23, 0x77, 0x0f, 0x1c, 0x27, 0x49, 0xb2, 0x48, 0x32, - 0xc8, 0x86, 0x0e, 0x14, 0x5a, 0x9b, 0x7d, 0x15, 0x2b, 0xf2, 0x58, 0x27, 0x82, 0xdd, 0x9a, 0x6e, 0x63, 0x54, 0x1d, - 0xe2, 0x7e, 0xbf, 0x5d, 0xd0, 0xae, 0x21, 0x40, 0x2a, 0x11, 0x72, 0xc4, 0x00, 0x42, 0x70, 0xf7, 0xef, 0x92, 0xa6, - 0x54, 0x85, 0x37, 0x5b, 0xd5, 0x00, 0xfb, 0xa1, 0xca, 0x7b, 0x01, 0x7a, 0x62, 0xc3, 0x9e, 0x95, 0x85, 0xad, 0xf2, - 0x1c, 0x21, 0x3e, 0x8e, 0x17, 0x09, 0xdc, 0x08, 0x1a, 0x4c, 0x12, 0x02, 0xad, 0x56, 0x8b, 0x10, 0xb7, 0xa6, 0x95, - 0x62, 0x7a, 0x4c, 0xa6, 0xfd, 0xa2, 0xd1, 0x30, 0xca, 0xeb, 0x91, 0xc5, 0x8b, 0x05, 0xc2, 0xe3, 0x72, 0xaf, 0xf9, - 0x72, 0x73, 0x52, 0xef, 0x2a, 0x1e, 0xd7, 0x95, 0xc0, 0x0d, 0x21, 0x90, 0xd1, 0x2f, 0x6a, 0x68, 0x1d, 0x4f, 0xc8, - 0x51, 0xdc, 0x4f, 0x7a, 0xff, 0x73, 0x80, 0x7a, 0x71, 0x72, 0x88, 0x8e, 0x2c, 0x2d, 0x19, 0xa3, 0x6e, 0x66, 0xfb, - 0x58, 0x9a, 0xdb, 0xcf, 0x36, 0x36, 0x0a, 0xc8, 0x54, 0x62, 0x41, 0x67, 0x2c, 0x9d, 0xc0, 0xae, 0xf7, 0xc8, 0x33, - 0xc7, 0x80, 0x4c, 0xe9, 0xc4, 0xd1, 0x96, 0x24, 0xea, 0x49, 0x5a, 0x7e, 0xf5, 0xa2, 0x1e, 0xad, 0xbe, 0xfe, 0x67, - 0xd4, 0xcb, 0x68, 0xfa, 0x98, 0xaf, 0x9d, 0x92, 0xbc, 0xd6, 0xc7, 0x99, 0xef, 0x63, 0x6d, 0x17, 0x27, 0x00, 0xde, - 0x08, 0x6d, 0x6b, 0x47, 0x16, 0x68, 0xcd, 0xc7, 0x25, 0x75, 0x52, 0x89, 0xa6, 0x13, 0x80, 0x6a, 0xb0, 0x08, 0x2a, - 0xb4, 0x0d, 0x08, 0xa6, 0x0c, 0xd8, 0xe2, 0x91, 0x16, 0xa0, 0xb9, 0xb8, 0x6c, 0xa1, 0x65, 0xad, 0xb0, 0xe3, 0xac, - 0xea, 0x77, 0xf1, 0x25, 0xf1, 0x1e, 0x03, 0x55, 0xbe, 0x58, 0x74, 0xc7, 0x8d, 0x06, 0x52, 0x1e, 0xbf, 0x46, 0xfd, - 0xf1, 0x00, 0xdf, 0x02, 0x0a, 0xe1, 0x1a, 0x46, 0xe1, 0xda, 0x1c, 0x3b, 0x6e, 0x8e, 0x8d, 0x86, 0x5c, 0xa3, 0x6e, - 0x50, 0x79, 0xe1, 0x2a, 0xaf, 0xd7, 0x16, 0x32, 0x9b, 0x18, 0x77, 0x8e, 0x4c, 0x0a, 0x18, 0x82, 0x11, 0x42, 0xfe, - 0x91, 0x68, 0x67, 0xb3, 0xd0, 0x28, 0x54, 0x37, 0xbb, 0x17, 0x28, 0xaa, 0x3d, 0x3d, 0x62, 0x80, 0x05, 0x54, 0x2d, - 0xd5, 0xc8, 0x53, 0x8d, 0x47, 0x8d, 0xb6, 0x41, 0xf7, 0x66, 0xbb, 0x5b, 0x6f, 0xec, 0x7e, 0xd5, 0x18, 0x1e, 0x35, - 0xc8, 0xb4, 0xda, 0xe1, 0x6b, 0xd9, 0x68, 0xac, 0xeb, 0xf7, 0xa5, 0x7e, 0x13, 0xd7, 0xee, 0x2f, 0x9e, 0x6e, 0x99, - 0x78, 0xf8, 0xd3, 0xb7, 0x3a, 0x6f, 0x45, 0xc2, 0x85, 0x60, 0x05, 0x9c, 0xb0, 0x44, 0x63, 0xb1, 0x5e, 0x97, 0xa7, - 0xfe, 0xef, 0xda, 0xda, 0x8c, 0x11, 0x0e, 0x74, 0xc8, 0x48, 0x6d, 0x58, 0xe2, 0x02, 0x53, 0x43, 0x45, 0x08, 0x21, - 0x1f, 0xb4, 0x37, 0x8f, 0xd1, 0x86, 0x24, 0x65, 0x24, 0x38, 0xbb, 0x63, 0x45, 0x58, 0x72, 0x7d, 0xef, 0xb1, 0xfc, - 0xae, 0x48, 0xd7, 0x17, 0x83, 0xd4, 0x14, 0xcb, 0x1d, 0x21, 0xcb, 0xc9, 0x57, 0x90, 0x73, 0xca, 0x0b, 0x96, 0xc4, - 0x10, 0xc4, 0x27, 0xbc, 0x60, 0x86, 0x71, 0xbf, 0xe7, 0xe5, 0xc6, 0xac, 0xce, 0x69, 0x66, 0xa1, 0xf6, 0x07, 0xa0, - 0x99, 0x83, 0x72, 0x48, 0x92, 0xad, 0x62, 0xd7, 0xf7, 0x1e, 0xbe, 0xde, 0x25, 0x43, 0xaf, 0x56, 0x4e, 0x7a, 0xce, - 0x80, 0xf5, 0xc1, 0x79, 0x35, 0xd4, 0xcc, 0xfd, 0x48, 0xe3, 0xcc, 0x30, 0x51, 0x79, 0xcc, 0x01, 0x99, 0xae, 0xef, - 0x3d, 0x7c, 0x17, 0x73, 0xa3, 0x9b, 0x42, 0x38, 0x9c, 0x77, 0x5c, 0x90, 0x98, 0x12, 0x86, 0xec, 0xe4, 0x4b, 0x3a, - 0x56, 0x04, 0xa7, 0x7b, 0x4a, 0x4d, 0x26, 0x88, 0x1d, 0x7d, 0x31, 0x20, 0x99, 0x03, 0x01, 0xc9, 0x10, 0xce, 0x6a, - 0x72, 0x1d, 0x31, 0x6b, 0x60, 0x3a, 0xbb, 0x82, 0xc5, 0x48, 0x2c, 0x7b, 0x88, 0x70, 0x66, 0xba, 0xd5, 0x6b, 0x7b, - 0x9c, 0x28, 0xba, 0x69, 0xe8, 0x56, 0xc9, 0xb3, 0xef, 0x41, 0xf0, 0xf2, 0x1f, 0xaf, 0x5c, 0xdb, 0x65, 0xc2, 0x13, - 0x6f, 0x91, 0x76, 0x7d, 0xef, 0xe1, 0xaf, 0xce, 0x28, 0x6d, 0x4e, 0x3d, 0xf9, 0xdf, 0x92, 0x51, 0x1f, 0xfe, 0x9a, - 0x54, 0xb9, 0xa6, 0xf0, 0xf5, 0xbd, 0x87, 0xbf, 0xef, 0x2a, 0x06, 0xe9, 0xeb, 0x45, 0xa5, 0x24, 0x30, 0xe3, 0x5b, - 0xb2, 0x3c, 0x5d, 0xba, 0xb3, 0x22, 0x15, 0x6b, 0x6c, 0x4e, 0xa8, 0x54, 0xad, 0x4b, 0xdd, 0xca, 0x13, 0x2c, 0x89, - 0xb9, 0x4a, 0xaa, 0x2f, 0x9b, 0x43, 0x63, 0x2e, 0xc5, 0x55, 0x26, 0xe7, 0xec, 0x1b, 0xf7, 0x4b, 0x4f, 0x35, 0x4a, - 0xf8, 0x0c, 0x0c, 0x71, 0xcc, 0xd8, 0x05, 0xde, 0x6f, 0xa1, 0xee, 0xc6, 0x79, 0x26, 0x0d, 0xa2, 0x16, 0xf5, 0xc3, - 0x06, 0x53, 0xd2, 0xc2, 0x19, 0x69, 0xe1, 0x9c, 0xa8, 0x7e, 0xcb, 0x9e, 0x18, 0xdd, 0xbc, 0x6c, 0xda, 0x9e, 0x3b, - 0xb0, 0xdd, 0x73, 0xbb, 0x6f, 0xed, 0xa1, 0x3c, 0xed, 0xe6, 0x46, 0x7f, 0x69, 0x0e, 0xfa, 0xa9, 0x41, 0x8d, 0x27, - 0x2c, 0x2e, 0x70, 0x61, 0x5a, 0xbe, 0xe2, 0xc3, 0x1c, 0xec, 0x54, 0x60, 0x66, 0x58, 0xa3, 0xb4, 0x2c, 0xdb, 0x76, - 0x65, 0xf3, 0xc4, 0xac, 0x55, 0x81, 0xf3, 0x04, 0x48, 0x39, 0xce, 0x9d, 0x5d, 0x8f, 0xda, 0xae, 0x72, 0x76, 0x70, - 0x10, 0xbb, 0x4a, 0x34, 0x2e, 0x7c, 0x7e, 0x75, 0x03, 0xf8, 0xde, 0x52, 0x8d, 0x29, 0x32, 0x13, 0x68, 0x34, 0xb2, - 0xc1, 0x9a, 0xee, 0x13, 0x12, 0xe7, 0x75, 0x28, 0xfa, 0xd1, 0x1b, 0x66, 0x70, 0x03, 0x00, 0x8d, 0x46, 0x79, 0xdd, - 0xbb, 0x01, 0xb1, 0xa7, 0x1a, 0xcb, 0xf5, 0xd7, 0xb8, 0xb4, 0x26, 0x6a, 0x6d, 0xd9, 0x61, 0xf9, 0x51, 0x20, 0x11, - 0xe2, 0xae, 0xf0, 0xf3, 0x09, 0xb6, 0x86, 0x80, 0x72, 0x2f, 0x9c, 0x0d, 0x04, 0x36, 0x56, 0x5b, 0xae, 0x90, 0x27, - 0x6d, 0x1d, 0x94, 0xfa, 0x42, 0x70, 0xc1, 0x05, 0x85, 0x1a, 0x6b, 0x87, 0xe5, 0x4f, 0xd8, 0xb6, 0x39, 0x27, 0x56, - 0xc8, 0x69, 0xcb, 0xcc, 0x30, 0x0c, 0xc0, 0x3a, 0x25, 0x60, 0x9e, 0x93, 0x97, 0xdf, 0x46, 0xfd, 0x87, 0x01, 0xea, - 0x3f, 0x22, 0x2c, 0xd8, 0x06, 0x56, 0x57, 0x92, 0x48, 0xa7, 0xa0, 0x50, 0x3e, 0xeb, 0xf1, 0x9c, 0x80, 0x36, 0xae, - 0x0e, 0xd5, 0xda, 0x15, 0xe5, 0x37, 0x28, 0x4b, 0xb8, 0x53, 0x8c, 0x3e, 0x13, 0xfb, 0xfb, 0xe4, 0xb8, 0xba, 0xa0, - 0x83, 0xae, 0x77, 0x29, 0x07, 0x43, 0x52, 0xf8, 0xf0, 0xf7, 0xef, 0xdf, 0xad, 0x3e, 0x9e, 0x6f, 0xef, 0xe0, 0xc0, - 0xac, 0x14, 0x66, 0x1d, 0x6c, 0xe0, 0xba, 0x91, 0x29, 0xf4, 0x5f, 0xde, 0x89, 0xd7, 0xa9, 0xd0, 0xc6, 0x66, 0xf4, - 0xc7, 0x21, 0x8c, 0xb6, 0xdd, 0x36, 0x25, 0x58, 0xd0, 0x2c, 0xd0, 0x25, 0x6b, 0xdc, 0x4a, 0x8b, 0x6f, 0x90, 0x91, - 0x87, 0xa6, 0x00, 0x13, 0xa3, 0xdd, 0xd9, 0x8f, 0xd6, 0x0e, 0x4f, 0xec, 0xd0, 0xd0, 0xd2, 0x10, 0x42, 0x8b, 0xf7, - 0x80, 0x39, 0xf6, 0x88, 0x00, 0x10, 0xbd, 0x34, 0x90, 0xaa, 0x40, 0x16, 0x45, 0x95, 0x22, 0xff, 0xf9, 0x3e, 0x21, - 0x2f, 0x2b, 0x45, 0xe6, 0xdb, 0xca, 0x98, 0x0b, 0x10, 0x03, 0xa5, 0x70, 0x91, 0x50, 0x26, 0xd8, 0xcb, 0xd0, 0x0f, - 0xda, 0x97, 0x37, 0xd2, 0x66, 0x52, 0x71, 0xe3, 0xc1, 0x4d, 0xa9, 0x51, 0xf1, 0xd9, 0x7c, 0x0f, 0x89, 0x8d, 0xdc, - 0x7b, 0x90, 0xcb, 0xa8, 0x19, 0x24, 0x7c, 0xbf, 0x33, 0xa5, 0x7d, 0xbb, 0xeb, 0x2f, 0x9b, 0x16, 0x31, 0x1b, 0xeb, - 0x92, 0x70, 0xa1, 0x58, 0xa1, 0x1f, 0xb1, 0xb1, 0x2c, 0xe0, 0xfe, 0xa3, 0x04, 0x0b, 0x5a, 0xdf, 0x0b, 0x74, 0x80, - 0x66, 0x82, 0xc1, 0xa5, 0xc3, 0xc6, 0x0c, 0xcd, 0xaf, 0x2f, 0xe6, 0x0e, 0xfc, 0x7a, 0xb3, 0xd6, 0xcb, 0x83, 0x83, - 0xaf, 0xac, 0x02, 0x94, 0x1b, 0xa6, 0x19, 0x46, 0x40, 0xbc, 0x2c, 0x97, 0xe3, 0x6e, 0x86, 0xef, 0xc5, 0x95, 0xca, - 0xc0, 0x13, 0x8e, 0x90, 0x08, 0x3d, 0x27, 0x7a, 0x3d, 0xd9, 0xa4, 0xf7, 0x4e, 0x9b, 0x21, 0x42, 0xb1, 0x06, 0xc8, - 0x3d, 0xc8, 0xe5, 0x56, 0xc9, 0xa4, 0x2a, 0x5b, 0xdb, 0x72, 0x10, 0x8f, 0x01, 0x5c, 0xb1, 0x11, 0x52, 0x02, 0x34, - 0xdc, 0x2d, 0xb4, 0x3c, 0x97, 0xc0, 0xfe, 0x63, 0x95, 0x80, 0x48, 0x8b, 0x6a, 0x1b, 0x17, 0x21, 0x6c, 0x4d, 0x7d, - 0x02, 0xe3, 0x84, 0x87, 0xcf, 0x77, 0x69, 0xa8, 0x3d, 0x6a, 0x33, 0x73, 0x06, 0x41, 0x09, 0x89, 0xca, 0x0a, 0xc9, - 0xd7, 0x58, 0x38, 0x6e, 0xce, 0xdf, 0xc3, 0x01, 0x29, 0x56, 0x34, 0xb6, 0x77, 0x5b, 0x70, 0x7c, 0x14, 0xc9, 0x22, - 0xae, 0x75, 0xdd, 0x2d, 0x4c, 0x35, 0xec, 0x40, 0x47, 0x43, 0x38, 0x15, 0xe6, 0x9e, 0xf0, 0x71, 0x45, 0x52, 0x7f, - 0xb6, 0x26, 0xda, 0xda, 0x13, 0xc3, 0xca, 0x34, 0x25, 0x98, 0xff, 0xcf, 0xd6, 0xea, 0xba, 0x2c, 0x84, 0x99, 0x19, - 0xc6, 0x8d, 0x5d, 0x05, 0xb6, 0x06, 0x1c, 0x5b, 0x7e, 0x96, 0xc1, 0xa2, 0x7a, 0xa5, 0xb8, 0xe9, 0x34, 0x60, 0x02, - 0xde, 0x82, 0xf5, 0xcc, 0xe6, 0xd6, 0x7f, 0x6e, 0x0e, 0x46, 0x81, 0x55, 0x8d, 0xc0, 0x4b, 0x43, 0xe0, 0x11, 0x30, - 0x6e, 0xde, 0xb4, 0xbc, 0xe7, 0x8c, 0x68, 0x84, 0x3f, 0xf1, 0x1c, 0x9e, 0x59, 0x96, 0x7b, 0xeb, 0x63, 0x63, 0x45, - 0x52, 0x41, 0xc0, 0xb6, 0x08, 0x3b, 0x22, 0x2f, 0x11, 0x56, 0x8d, 0x46, 0x57, 0x5d, 0xb0, 0x4a, 0xab, 0x52, 0x0d, - 0x53, 0xc0, 0x2d, 0x31, 0xe0, 0x7d, 0xed, 0x44, 0x05, 0x43, 0x02, 0x6f, 0xfd, 0xad, 0x40, 0x7d, 0xff, 0xf0, 0x6d, - 0x1c, 0xd2, 0xb7, 0xb0, 0x6c, 0x79, 0x11, 0x0b, 0x53, 0x8a, 0xab, 0x3b, 0x9c, 0x37, 0xdf, 0x37, 0x1b, 0x81, 0x71, - 0xef, 0xb7, 0x31, 0xd8, 0xb8, 0xa1, 0xae, 0xb6, 0xa4, 0xa1, 0xdc, 0x84, 0x5d, 0x54, 0xd9, 0x3b, 0x86, 0x9d, 0x75, - 0x75, 0x25, 0xed, 0x6a, 0xa2, 0xd6, 0x6b, 0xc5, 0x2a, 0xa3, 0x81, 0x0d, 0xc3, 0x4e, 0x73, 0xcc, 0x6c, 0x2b, 0xf0, - 0x1f, 0xcf, 0x89, 0xc6, 0x01, 0xb2, 0xbe, 0xf9, 0xd6, 0x75, 0x4a, 0x35, 0x4c, 0xd8, 0xde, 0xee, 0x7c, 0x7c, 0xcc, - 0x77, 0x9d, 0x8f, 0x58, 0xba, 0xad, 0x6f, 0xce, 0xc6, 0xf6, 0xbf, 0x71, 0x36, 0x3a, 0xb5, 0xbd, 0x3f, 0x1e, 0x81, - 0x3b, 0xa9, 0x1d, 0x8f, 0xf5, 0x35, 0x25, 0x12, 0x0b, 0xb7, 0x1c, 0x97, 0x9d, 0xd5, 0x4a, 0xf4, 0x5b, 0xa0, 0x76, - 0x8a, 0x22, 0xf8, 0xd9, 0xb6, 0x3f, 0x03, 0x92, 0x6c, 0x75, 0xc8, 0xb1, 0x28, 0x45, 0x19, 0x94, 0x80, 0x01, 0x75, - 0x6c, 0x6c, 0xbd, 0x0c, 0x62, 0x3b, 0x1c, 0x72, 0x58, 0x4e, 0x44, 0x79, 0x75, 0x05, 0x23, 0x36, 0xc7, 0x86, 0x13, - 0x30, 0xe3, 0x9d, 0x56, 0x85, 0x5e, 0xfc, 0xfc, 0xd7, 0xcc, 0x69, 0xed, 0x88, 0xb1, 0x9c, 0x44, 0xcd, 0x8a, 0xc1, - 0x8d, 0xc0, 0x31, 0x8c, 0xfb, 0x46, 0x42, 0xad, 0x4e, 0x75, 0x54, 0x3b, 0x92, 0x70, 0x0b, 0xd4, 0x6e, 0xfb, 0xe6, - 0x5c, 0x5a, 0xad, 0x76, 0x1e, 0x2c, 0xb8, 0x08, 0x70, 0xfb, 0x39, 0xd1, 0x35, 0x92, 0x42, 0x89, 0x93, 0xa0, 0x70, - 0x6e, 0x50, 0x55, 0x13, 0xd9, 0x6f, 0x0d, 0x80, 0x27, 0xed, 0x66, 0x17, 0xb2, 0x12, 0x92, 0xb3, 0x46, 0x03, 0xe5, - 0x65, 0xc7, 0xb4, 0x2f, 0x1a, 0xd9, 0x00, 0x33, 0x9c, 0x59, 0x81, 0x05, 0x4e, 0xaf, 0x38, 0xaf, 0xba, 0xee, 0x67, - 0x03, 0x84, 0x8b, 0xd5, 0x2a, 0xb6, 0x43, 0xcb, 0xd1, 0x6a, 0x95, 0x87, 0x43, 0x33, 0xf9, 0x50, 0xf1, 0x65, 0x4f, - 0x93, 0x97, 0xe6, 0x3c, 0x7c, 0x09, 0x83, 0x6c, 0x90, 0x38, 0x77, 0x2a, 0xc1, 0x1c, 0x34, 0x57, 0x0d, 0xd9, 0xcf, - 0x1a, 0xed, 0x41, 0x40, 0xc3, 0xfa, 0xd9, 0x80, 0xe4, 0x6b, 0xb0, 0x9c, 0x55, 0xee, 0xc0, 0xfc, 0x0c, 0x07, 0xdb, - 0x67, 0x73, 0xce, 0xd8, 0x06, 0xc3, 0x35, 0xd9, 0x54, 0x19, 0x94, 0x78, 0xe5, 0x16, 0xd7, 0x97, 0xab, 0x19, 0x58, - 0x94, 0x85, 0xb0, 0xbb, 0x66, 0xee, 0x81, 0xf0, 0x5f, 0x62, 0xbb, 0xa4, 0xa5, 0x11, 0xf7, 0x06, 0xe2, 0x7b, 0xdb, - 0xed, 0x24, 0x49, 0x68, 0x31, 0x31, 0x57, 0x22, 0xfe, 0x86, 0xd7, 0xec, 0x81, 0x63, 0x37, 0xce, 0xa0, 0xe7, 0x7e, - 0xd9, 0xd9, 0x80, 0xd8, 0xf1, 0x7b, 0x66, 0xc7, 0x3b, 0xae, 0x14, 0x74, 0xb7, 0x2e, 0xc2, 0x0e, 0x86, 0xfe, 0x2f, - 0x0f, 0xe6, 0xc4, 0x0d, 0xc6, 0xa2, 0xc9, 0x06, 0xdc, 0xbe, 0x01, 0x8f, 0x82, 0x6e, 0xc0, 0xed, 0xdb, 0xf0, 0xf5, - 0xd0, 0xca, 0xbe, 0x39, 0xc0, 0x80, 0x4c, 0xd8, 0x91, 0x56, 0x09, 0xc1, 0x30, 0x4f, 0x37, 0x39, 0x32, 0x4b, 0x56, - 0xe1, 0x70, 0xd5, 0x24, 0x16, 0x1b, 0x7b, 0xa1, 0x62, 0x52, 0x03, 0xc1, 0x58, 0xa4, 0x2f, 0x51, 0xa8, 0x34, 0xa8, - 0x1b, 0xc7, 0x00, 0x56, 0x39, 0x6d, 0xfd, 0xcb, 0x83, 0x03, 0x10, 0x1a, 0x80, 0xb5, 0x4b, 0x32, 0x3a, 0xd7, 0x8b, - 0x02, 0xf8, 0x2b, 0xe5, 0x7f, 0x43, 0x32, 0xb8, 0x9d, 0x98, 0x34, 0xf8, 0x01, 0x09, 0x73, 0xaa, 0x14, 0xff, 0x6a, - 0xd3, 0xdc, 0x6f, 0x5c, 0x10, 0x8f, 0xd1, 0xca, 0x72, 0x8a, 0x12, 0x75, 0xa5, 0x43, 0xd7, 0x3a, 0xe4, 0x9e, 0x7e, - 0x65, 0x42, 0xbf, 0xe4, 0x4a, 0x33, 0x01, 0x00, 0xa8, 0x10, 0x0f, 0xa6, 0xa4, 0x10, 0x6c, 0xdd, 0x5a, 0x2d, 0x3a, - 0x1a, 0x7d, 0xb7, 0x8a, 0xae, 0xb3, 0x45, 0x53, 0x2a, 0x46, 0xb9, 0xed, 0x24, 0xb4, 0x99, 0xf4, 0x76, 0xa2, 0x65, - 0xc9, 0xd0, 0x62, 0xa7, 0x62, 0x3f, 0x0c, 0xad, 0x8f, 0x05, 0xf1, 0xe7, 0x82, 0x3f, 0x4b, 0xbf, 0xcb, 0xc7, 0xc0, - 0x95, 0xfa, 0x37, 0x56, 0x21, 0x9c, 0x09, 0xd6, 0x01, 0x79, 0x4d, 0xea, 0xe3, 0xf4, 0xa8, 0x93, 0x6f, 0x29, 0x17, - 0x4a, 0xa3, 0xb0, 0x8d, 0x93, 0xc2, 0x60, 0xca, 0xd9, 0xb7, 0x25, 0xae, 0x5f, 0xfd, 0x31, 0xe2, 0x8f, 0x0e, 0xf1, - 0xef, 0x52, 0x69, 0xb4, 0x2c, 0x11, 0x0c, 0xf9, 0x1d, 0xa9, 0x15, 0x5c, 0xc5, 0xe6, 0x5c, 0x3f, 0xd7, 0xb3, 0x7c, - 0xc3, 0x13, 0xa7, 0xab, 0x55, 0x29, 0x15, 0xa8, 0xf8, 0x86, 0xe1, 0x27, 0x0c, 0xee, 0x8d, 0x9f, 0xf1, 0xa0, 0xca, - 0xf6, 0x7d, 0xf1, 0xb3, 0xe0, 0xbe, 0xf8, 0x19, 0x4f, 0xb7, 0x8b, 0x06, 0xf7, 0xc4, 0x9d, 0xe4, 0x3c, 0x69, 0x45, - 0x9e, 0x8f, 0x9a, 0xd2, 0xca, 0xbf, 0xd2, 0x6e, 0x0d, 0x5c, 0xd9, 0xc4, 0x81, 0x71, 0x5e, 0x5d, 0x84, 0x62, 0xce, - 0x9c, 0xd1, 0x72, 0xf8, 0xdf, 0x5a, 0x27, 0x77, 0xf2, 0x48, 0x2b, 0x85, 0xbc, 0xa1, 0x85, 0xbe, 0x07, 0x1b, 0xae, - 0xd8, 0xf2, 0x01, 0xa4, 0x04, 0x94, 0x6d, 0xff, 0x5e, 0x17, 0x81, 0x38, 0xae, 0xac, 0xf3, 0x51, 0xd8, 0x3e, 0x29, - 0x4a, 0xae, 0xae, 0x2e, 0x84, 0xdc, 0x1a, 0x2d, 0x01, 0xc2, 0xd4, 0xbb, 0xe6, 0x31, 0x47, 0x93, 0x59, 0xba, 0x5c, - 0x97, 0xaa, 0x83, 0xc2, 0x72, 0x75, 0x1c, 0xe1, 0x62, 0x6d, 0x6e, 0xd0, 0xff, 0xe1, 0xf8, 0x33, 0x77, 0x34, 0xf2, - 0xe7, 0x92, 0x02, 0xbd, 0xdf, 0xed, 0x6b, 0xb3, 0x83, 0x44, 0xda, 0x39, 0x94, 0x96, 0x02, 0x80, 0xd5, 0x06, 0x5f, - 0xd7, 0x1e, 0xa7, 0x9e, 0x48, 0x37, 0x9b, 0x6f, 0x1a, 0xc2, 0x62, 0x56, 0x5a, 0xf0, 0x98, 0x6e, 0x76, 0x58, 0x8e, - 0x7a, 0x59, 0x5c, 0x97, 0x7b, 0xac, 0xd6, 0x2f, 0xfa, 0x06, 0x28, 0x2b, 0x43, 0xb4, 0xd5, 0x2a, 0xae, 0xc3, 0x9b, - 0x88, 0xe0, 0x1a, 0x04, 0x61, 0x11, 0x18, 0x70, 0xd4, 0x18, 0x6f, 0x5b, 0x27, 0x46, 0x9b, 0xf6, 0x4b, 0x9e, 0x75, - 0xaf, 0x8d, 0x23, 0x54, 0x34, 0xd8, 0xea, 0xa1, 0xe6, 0x01, 0xdb, 0xd9, 0x95, 0x1d, 0x05, 0x10, 0x9a, 0x52, 0x6f, - 0x9c, 0x5b, 0x59, 0xd1, 0xee, 0x80, 0x2f, 0xfa, 0x8e, 0x79, 0xae, 0x03, 0xdd, 0x76, 0x7e, 0x60, 0xdb, 0xf4, 0x44, - 0x7e, 0xcb, 0xb6, 0xa9, 0xc6, 0x09, 0xef, 0xb7, 0xd0, 0xf7, 0x0d, 0x61, 0x6d, 0x5f, 0xbb, 0x8b, 0xfc, 0x2f, 0x74, - 0xd7, 0x06, 0xf4, 0xb4, 0x60, 0xf6, 0x34, 0xe6, 0x83, 0x5e, 0xaf, 0x7f, 0x2e, 0xfd, 0x17, 0x8c, 0xad, 0xd0, 0xcf, - 0x76, 0x17, 0x38, 0xb1, 0xd2, 0x38, 0x04, 0xc7, 0xff, 0x70, 0x32, 0xc9, 0xe5, 0x90, 0xe6, 0xef, 0xa0, 0xc7, 0x2a, - 0xf7, 0xf9, 0xdd, 0xa8, 0xa0, 0x9a, 0x39, 0x5a, 0x53, 0x8d, 0xe2, 0x1f, 0x1e, 0x0c, 0xe3, 0x1f, 0x6e, 0x29, 0x77, - 0xd5, 0x02, 0x5e, 0xbe, 0x2c, 0x9b, 0x48, 0x7f, 0x5e, 0x97, 0x32, 0x98, 0xda, 0xdd, 0xcb, 0x26, 0x49, 0x63, 0x25, - 0x49, 0x63, 0x2a, 0xde, 0x6c, 0x2a, 0x8e, 0x3f, 0x7f, 0x63, 0xb0, 0xdb, 0x64, 0xee, 0x73, 0x40, 0xe6, 0x3e, 0xf3, - 0xf4, 0xbb, 0xb5, 0x02, 0x8a, 0x77, 0x9c, 0x1c, 0x1b, 0xcb, 0x18, 0x3b, 0xea, 0xb7, 0x1a, 0x0c, 0x1a, 0x34, 0xb9, - 0x0c, 0xbc, 0x1d, 0xaa, 0xd3, 0xcb, 0xdb, 0x1f, 0xc5, 0xd9, 0x42, 0x69, 0x39, 0x73, 0x8d, 0x2a, 0xe7, 0xe3, 0x64, - 0x32, 0x41, 0x81, 0x6d, 0xee, 0xf0, 0xd3, 0xba, 0x1b, 0xd9, 0xf2, 0x0b, 0x17, 0xa3, 0x54, 0x61, 0x77, 0xb6, 0xa8, - 0x54, 0xae, 0x89, 0x37, 0x73, 0xde, 0xce, 0xc3, 0x63, 0x2e, 0xb8, 0x9a, 0xb2, 0x22, 0x2e, 0xd0, 0xf2, 0x5b, 0x9d, - 0x15, 0x70, 0x9b, 0x63, 0x3b, 0xc3, 0xa3, 0xd2, 0x72, 0x40, 0x27, 0xd0, 0x1a, 0xe8, 0x8c, 0x66, 0x4c, 0x4f, 0xe5, - 0x08, 0x0c, 0x5f, 0x92, 0x51, 0xe9, 0x4e, 0x75, 0x70, 0xb0, 0x1f, 0x47, 0x46, 0x7f, 0x01, 0x3e, 0xe8, 0x61, 0x0e, - 0xea, 0x2d, 0xc1, 0x31, 0xa8, 0xea, 0x9a, 0xa1, 0x25, 0xdb, 0xf4, 0xa1, 0xd1, 0xc9, 0x17, 0x76, 0x87, 0x39, 0x5a, - 0xaf, 0x53, 0x3b, 0xea, 0x68, 0xcc, 0x59, 0x3e, 0x8a, 0xf0, 0x17, 0x76, 0x97, 0x96, 0x6e, 0xeb, 0xc6, 0xcb, 0xda, - 0x2c, 0x62, 0x24, 0x6f, 0x44, 0x84, 0xab, 0x4e, 0xd2, 0xe5, 0x1a, 0xcb, 0x82, 0x4f, 0x00, 0x47, 0x7f, 0x61, 0x77, - 0xa9, 0x6b, 0x2f, 0x70, 0x15, 0x44, 0x4b, 0x0f, 0xfa, 0x24, 0x48, 0x0e, 0x97, 0xc1, 0x09, 0x1c, 0x7d, 0x53, 0x77, - 0x40, 0x6a, 0xe5, 0x2a, 0x11, 0x12, 0xa1, 0xf5, 0xbf, 0x3b, 0x15, 0xbc, 0x08, 0xcf, 0x39, 0x5d, 0xb3, 0xb8, 0xdd, - 0xa8, 0xc4, 0xa0, 0x42, 0x65, 0x41, 0xf2, 0x31, 0xe6, 0x7e, 0xf7, 0x39, 0xef, 0x87, 0x40, 0x67, 0xb6, 0xa0, 0xae, - 0xd1, 0x74, 0x64, 0x7e, 0xa1, 0xea, 0x0e, 0x6a, 0xa6, 0xab, 0x8a, 0x7b, 0x1f, 0x63, 0x00, 0x3c, 0x58, 0xcb, 0x50, - 0xe3, 0x10, 0xba, 0xf6, 0x66, 0xaa, 0x63, 0x4a, 0xe2, 0xa5, 0x9f, 0x43, 0xca, 0x43, 0x30, 0xea, 0x35, 0xa0, 0xa1, - 0x43, 0x30, 0x6b, 0x79, 0xc8, 0xc7, 0xb1, 0xd8, 0x3a, 0x43, 0xa5, 0x39, 0x43, 0x93, 0x00, 0xe4, 0xdf, 0x38, 0x33, - 0x99, 0x81, 0x86, 0xe1, 0x2d, 0xcd, 0x01, 0xe8, 0x56, 0xd7, 0xe1, 0x50, 0xb8, 0xa2, 0xa5, 0xf3, 0x9e, 0x5d, 0x74, - 0x59, 0x1b, 0x56, 0x6c, 0xda, 0x41, 0xeb, 0x14, 0xa6, 0xc4, 0x6c, 0x81, 0xb5, 0xd7, 0xfb, 0x70, 0x6f, 0x57, 0x1b, - 0x17, 0x89, 0x9f, 0x16, 0xf1, 0x30, 0x89, 0x29, 0x5a, 0xf2, 0x98, 0x62, 0x09, 0x76, 0x90, 0xc5, 0xba, 0x1c, 0x3f, - 0x0b, 0x97, 0xa3, 0x66, 0x25, 0xbd, 0xdb, 0xc1, 0x10, 0xb8, 0x7c, 0x0d, 0xb6, 0xa1, 0x98, 0x7b, 0xc2, 0xc2, 0x63, - 0xe3, 0xe9, 0x17, 0xac, 0xdb, 0xdc, 0x2e, 0x88, 0x5f, 0x81, 0x31, 0x8d, 0x97, 0xc1, 0x2c, 0x42, 0xa7, 0x72, 0xe7, - 0x70, 0xe8, 0xae, 0x09, 0x2b, 0xe3, 0xd5, 0x58, 0x91, 0x8d, 0xa3, 0xe7, 0xfb, 0x36, 0x9e, 0x7f, 0x2f, 0x58, 0x71, - 0x77, 0xc5, 0xc0, 0xc6, 0x5a, 0x82, 0xbb, 0x71, 0xb5, 0x0c, 0x95, 0x81, 0x7c, 0x4f, 0x1a, 0xd6, 0x65, 0x8d, 0xbf, - 0x1b, 0x15, 0x63, 0x6d, 0xee, 0x29, 0x03, 0x6d, 0x8d, 0xdd, 0x2e, 0xec, 0x9b, 0xae, 0x9b, 0xac, 0x6b, 0x14, 0x71, - 0x15, 0xa4, 0xdd, 0xdd, 0x02, 0x2e, 0x42, 0x7f, 0xd8, 0xbe, 0x1a, 0x6c, 0xaa, 0x6e, 0x20, 0x09, 0xae, 0xfd, 0xe4, - 0xb7, 0xa7, 0xba, 0xcb, 0x5a, 0xf7, 0xdb, 0x53, 0xad, 0x5d, 0x16, 0x1a, 0x43, 0x22, 0xec, 0xfa, 0x29, 0xfd, 0xa7, - 0xc5, 0x7a, 0x8d, 0xd6, 0x30, 0xbc, 0x47, 0xbc, 0x1b, 0xc7, 0x8f, 0xbc, 0x85, 0x62, 0x02, 0x17, 0xb9, 0x57, 0xb9, - 0xf4, 0x84, 0xbc, 0x1a, 0xc1, 0x23, 0xbe, 0x35, 0x84, 0x47, 0x3c, 0x70, 0x7a, 0x05, 0xa9, 0x69, 0x22, 0xd8, 0xc8, - 0xd3, 0x4f, 0x64, 0x91, 0xd0, 0xf0, 0x71, 0xaf, 0x39, 0x11, 0xfa, 0x53, 0x0a, 0xfc, 0x17, 0x1e, 0x2e, 0xb4, 0x96, - 0x02, 0x73, 0x31, 0x5f, 0x68, 0xac, 0xcc, 0xe8, 0x97, 0x63, 0x29, 0x74, 0x73, 0x4c, 0x67, 0x3c, 0xbf, 0x4b, 0x17, - 0xbc, 0x39, 0x93, 0x42, 0xaa, 0x39, 0xcd, 0x18, 0x56, 0x77, 0x4a, 0xb3, 0x59, 0x73, 0xc1, 0xf1, 0x73, 0x96, 0x7f, - 0x65, 0x9a, 0x67, 0x14, 0xbf, 0x95, 0x43, 0xa9, 0x25, 0x7e, 0x7d, 0x7b, 0x37, 0x61, 0x02, 0xff, 0x3e, 0x5c, 0x08, - 0xbd, 0xc0, 0x8a, 0x0a, 0xd5, 0x54, 0xac, 0xe0, 0xe3, 0x6e, 0xb3, 0x39, 0x2f, 0xf8, 0x8c, 0x16, 0x77, 0xcd, 0x4c, - 0xe6, 0xb2, 0x48, 0xff, 0xab, 0x75, 0x4c, 0x1f, 0x8c, 0x4f, 0xba, 0xba, 0xa0, 0x42, 0x71, 0x58, 0x98, 0x94, 0xe6, - 0xf9, 0xde, 0xf1, 0x69, 0x6b, 0xa6, 0xf6, 0xed, 0x85, 0x1f, 0x15, 0x7a, 0xfd, 0x09, 0x7f, 0x90, 0x30, 0xca, 0x64, - 0xa8, 0x85, 0x1b, 0xe4, 0x32, 0x5b, 0x14, 0x4a, 0x16, 0xe9, 0x5c, 0x72, 0xa1, 0x59, 0xd1, 0x1d, 0xca, 0x62, 0xc4, - 0x8a, 0x66, 0x41, 0x47, 0x7c, 0xa1, 0xd2, 0x93, 0xf9, 0x6d, 0xb7, 0xde, 0x83, 0xcd, 0x4f, 0x85, 0x14, 0xac, 0x0b, - 0xfc, 0xc6, 0xa4, 0x90, 0x0b, 0x31, 0x72, 0xc3, 0x58, 0x08, 0xc5, 0x74, 0x77, 0x4e, 0x47, 0x60, 0x07, 0x9c, 0x9e, - 0xcf, 0x6f, 0xbb, 0x66, 0xd6, 0x37, 0x8c, 0x4f, 0xa6, 0x3a, 0x3d, 0x6d, 0xb5, 0xec, 0xb7, 0xe2, 0xff, 0xb0, 0xb4, - 0xdd, 0x49, 0x3a, 0xa7, 0xf3, 0x5b, 0xe0, 0xe0, 0x35, 0x2b, 0x9a, 0x00, 0x0b, 0xa8, 0xd4, 0x4e, 0x5a, 0x0f, 0x8e, - 0xef, 0x43, 0x06, 0xd8, 0x38, 0x34, 0xcd, 0x84, 0xc0, 0xd8, 0x3d, 0x5d, 0xcc, 0xe7, 0xac, 0x00, 0x2f, 0xfa, 0xee, - 0x8c, 0x16, 0x13, 0x2e, 0x9a, 0x85, 0x69, 0xb4, 0x79, 0x3e, 0xbf, 0x5d, 0xc3, 0x7c, 0x52, 0x6b, 0xb6, 0xea, 0xa6, - 0xe5, 0xbe, 0x96, 0xc1, 0x10, 0x4d, 0x4c, 0x9a, 0xb4, 0x98, 0x0c, 0x69, 0xdc, 0xee, 0xdc, 0xc7, 0xfe, 0x7f, 0x49, - 0x07, 0x05, 0x60, 0x6b, 0x8e, 0x16, 0x85, 0xb9, 0x45, 0x4d, 0xdb, 0xca, 0x36, 0x3b, 0x95, 0x5f, 0x59, 0xe1, 0x5b, - 0x35, 0x1f, 0xcb, 0xad, 0x79, 0xff, 0x47, 0x8d, 0x52, 0xdb, 0xd6, 0x0b, 0x75, 0x05, 0x34, 0x7a, 0xbb, 0xb1, 0xff, - 0xea, 0x9c, 0xd3, 0xfb, 0x27, 0xa7, 0x1e, 0xee, 0xe3, 0xf1, 0xb8, 0x06, 0x74, 0x0f, 0xdd, 0x76, 0x6b, 0x7e, 0xbb, - 0xd7, 0x69, 0x79, 0x18, 0x5b, 0x98, 0x9e, 0xcd, 0x6f, 0x77, 0xac, 0x60, 0x80, 0x15, 0x9b, 0xbd, 0xed, 0x25, 0xc7, - 0x6a, 0x8f, 0x51, 0xc5, 0xd6, 0x9f, 0xf0, 0x84, 0x02, 0x6e, 0x18, 0xa4, 0xed, 0x1b, 0x39, 0x15, 0x56, 0x60, 0xb0, - 0xbc, 0xe1, 0x23, 0x3d, 0x4d, 0xdb, 0xad, 0xd6, 0x0f, 0x15, 0x26, 0x75, 0xa7, 0x76, 0x49, 0xdb, 0x05, 0x9b, 0xd5, - 0xf0, 0x6b, 0x46, 0xcb, 0x5d, 0xb0, 0x9c, 0x4b, 0xd7, 0x69, 0xc1, 0x72, 0x13, 0xe5, 0x66, 0xed, 0xb6, 0xc2, 0xd6, - 0x94, 0xb9, 0x98, 0xb2, 0x82, 0xeb, 0x6e, 0xfd, 0xab, 0xea, 0x78, 0x7b, 0x4e, 0x6b, 0x2b, 0x1f, 0x2f, 0x6d, 0x0d, - 0x77, 0x19, 0xfb, 0x18, 0x3e, 0xb6, 0xb1, 0xf2, 0x2b, 0x2d, 0xe2, 0x8d, 0x0d, 0x83, 0xc3, 0x1a, 0x68, 0x1d, 0xcc, - 0xb9, 0x00, 0x53, 0xd1, 0x01, 0xfe, 0x06, 0x14, 0x32, 0x9a, 0x67, 0x31, 0x8c, 0x68, 0xaf, 0xb9, 0x77, 0x5c, 0xb0, - 0x19, 0xf2, 0x80, 0x48, 0xee, 0x9f, 0x16, 0x6c, 0xb6, 0x4e, 0x4c, 0xf5, 0xa5, 0x41, 0x5d, 0x9a, 0xf3, 0x89, 0x48, - 0x33, 0x06, 0xdb, 0x6a, 0x9d, 0x30, 0xa1, 0xb9, 0xbe, 0x6b, 0x16, 0xf2, 0x66, 0x39, 0xe2, 0x6a, 0x9e, 0xd3, 0xbb, - 0x74, 0x9c, 0xb3, 0xdb, 0xae, 0x29, 0xd5, 0xe4, 0x9a, 0xcd, 0x94, 0x2b, 0xdb, 0x85, 0xf4, 0xe6, 0xc8, 0x9a, 0x73, - 0x00, 0xf4, 0xe4, 0xcd, 0xe6, 0xbe, 0xf6, 0x8b, 0xd6, 0x94, 0x0b, 0xbd, 0xd7, 0x52, 0xdd, 0x19, 0x17, 0x4d, 0x37, - 0x90, 0x13, 0xc0, 0x88, 0x6d, 0xc8, 0x07, 0xfd, 0x27, 0xec, 0x76, 0x4e, 0xc5, 0x88, 0x8d, 0x96, 0x41, 0xb5, 0x0e, - 0xd4, 0x0b, 0x4b, 0xa5, 0x42, 0x4f, 0x9b, 0xc6, 0x06, 0x2d, 0xee, 0x08, 0xf4, 0x0d, 0x94, 0x7f, 0xd0, 0xc2, 0xf6, - 0xff, 0x93, 0x36, 0x0a, 0x2b, 0xef, 0x41, 0x38, 0x28, 0x3e, 0xbe, 0x6b, 0xc2, 0xdf, 0x25, 0xf8, 0x3c, 0xf1, 0x8c, - 0xe6, 0x0e, 0x22, 0x33, 0x3e, 0x1a, 0xe5, 0xb5, 0x11, 0x5d, 0x06, 0x9d, 0xb5, 0xd1, 0x12, 0xe6, 0x9f, 0xb6, 0xf6, - 0x5a, 0x7b, 0x66, 0x2e, 0x6e, 0x1b, 0x9c, 0x9c, 0xdc, 0x3f, 0x7e, 0xc0, 0xba, 0x39, 0x17, 0xac, 0x36, 0xd5, 0xef, - 0x82, 0x3a, 0x6c, 0xb8, 0xe3, 0x1a, 0x6e, 0xef, 0xb5, 0xf7, 0x4e, 0x5a, 0x3f, 0x78, 0x2a, 0x92, 0xb3, 0xb1, 0xb6, - 0xfb, 0xa6, 0x46, 0x56, 0xce, 0x7d, 0xd3, 0x37, 0x05, 0x9d, 0xa7, 0x42, 0xc2, 0x9f, 0x2e, 0x6c, 0xfe, 0x71, 0x2e, - 0x6f, 0xd2, 0x29, 0x1f, 0x8d, 0x98, 0xb0, 0x05, 0xca, 0x44, 0x96, 0xe7, 0x7c, 0xae, 0xb8, 0x5d, 0x0d, 0x87, 0xbb, - 0xa7, 0x1b, 0x50, 0x0d, 0x07, 0x74, 0x1c, 0x0c, 0xe8, 0xb4, 0x1a, 0x50, 0xd5, 0x7f, 0x38, 0xc2, 0xce, 0xc6, 0x5c, - 0x4d, 0xa9, 0x6e, 0x0d, 0x93, 0x3e, 0x2f, 0x94, 0x06, 0x98, 0x7b, 0xe3, 0x11, 0x73, 0xba, 0x34, 0x87, 0x4c, 0xdf, - 0x30, 0x26, 0xbe, 0x3d, 0x88, 0xcb, 0x54, 0x8a, 0xfc, 0xce, 0x7e, 0x2e, 0xc3, 0x2e, 0xe9, 0x42, 0xcb, 0x75, 0x32, - 0xe4, 0x82, 0x16, 0x77, 0xd7, 0x8a, 0x09, 0x25, 0x8b, 0x6b, 0x39, 0x1e, 0x2f, 0xbf, 0x45, 0xf2, 0xee, 0xa3, 0x75, - 0xa2, 0xb8, 0x98, 0xe4, 0xcc, 0x12, 0x38, 0x83, 0x08, 0xee, 0x90, 0xb1, 0xed, 0x9a, 0x26, 0x6b, 0x83, 0x5e, 0x27, - 0x59, 0xce, 0x67, 0x54, 0x33, 0x03, 0xe7, 0x80, 0xd4, 0xb8, 0xc9, 0x5b, 0x2a, 0xd7, 0xda, 0xb3, 0x7f, 0xaa, 0xd2, - 0xb0, 0x8d, 0x82, 0xc2, 0xbe, 0x49, 0x2e, 0x0c, 0x7e, 0x18, 0x70, 0x98, 0x5d, 0x64, 0x56, 0xcf, 0xac, 0x5d, 0x00, - 0x3b, 0x98, 0x5d, 0xad, 0xa9, 0x4b, 0x47, 0x97, 0x6c, 0x8b, 0xa7, 0xad, 0x1f, 0xea, 0xb9, 0x39, 0x1d, 0xb2, 0x7c, - 0x69, 0x37, 0xaa, 0x07, 0xae, 0xdb, 0xaa, 0xe1, 0x32, 0x07, 0x24, 0xc3, 0x80, 0x68, 0x90, 0xa6, 0xcd, 0x1b, 0x36, - 0xfc, 0xc2, 0xb5, 0xdd, 0x32, 0x4d, 0x75, 0x03, 0x4e, 0x45, 0x66, 0x4c, 0x73, 0x56, 0x2c, 0x3d, 0x21, 0x6f, 0xd5, - 0x08, 0xe8, 0x95, 0x30, 0x07, 0xb4, 0xa6, 0xc3, 0x26, 0x84, 0x58, 0x63, 0xc5, 0x72, 0xd7, 0xe4, 0x66, 0xf4, 0xd6, - 0xa1, 0xd8, 0x83, 0xd6, 0x0f, 0xb5, 0x43, 0xf6, 0xa4, 0xd5, 0xf2, 0x47, 0x44, 0xd3, 0xd6, 0x48, 0xdb, 0xc9, 0x29, - 0x9b, 0x95, 0x89, 0x5a, 0xce, 0xd3, 0x5a, 0xc2, 0x50, 0x6a, 0x2d, 0x67, 0x36, 0x6d, 0x07, 0x35, 0xaa, 0x93, 0xde, - 0x76, 0x67, 0x7e, 0xbb, 0x67, 0xfe, 0x69, 0xed, 0xb5, 0xb6, 0x49, 0xed, 0x36, 0x56, 0x1c, 0x23, 0x8f, 0xc7, 0xd0, - 0x71, 0x9b, 0xcd, 0xba, 0x0b, 0x05, 0xc7, 0xbd, 0x81, 0xb8, 0x39, 0xd1, 0xd6, 0x66, 0xb2, 0x00, 0x58, 0xca, 0x05, - 0x9c, 0xae, 0xf6, 0xb0, 0x83, 0x3e, 0x94, 0x04, 0x73, 0xf8, 0x9d, 0x8d, 0xd6, 0x87, 0xd5, 0xda, 0xab, 0x06, 0x06, - 0xff, 0xac, 0x3f, 0x55, 0xfc, 0xf9, 0x0b, 0x16, 0xc8, 0x47, 0xbc, 0x91, 0x9c, 0xae, 0x5a, 0x4e, 0x26, 0x1a, 0xe9, - 0x4a, 0x54, 0x33, 0x1e, 0x25, 0x33, 0x7a, 0x6b, 0x5d, 0x4b, 0x66, 0x5c, 0x80, 0xe1, 0x1a, 0xc2, 0x3a, 0x30, 0xf1, - 0x9f, 0x86, 0x0d, 0x8d, 0x74, 0x0c, 0x0d, 0x1f, 0x76, 0x92, 0xd3, 0x53, 0x84, 0x5b, 0xb8, 0x73, 0x7a, 0x1a, 0xc8, - 0x64, 0x63, 0xbd, 0xab, 0xe8, 0xae, 0x92, 0x72, 0x47, 0xc9, 0x23, 0xd3, 0xe8, 0x51, 0xbb, 0xd5, 0xc2, 0xc6, 0x7d, - 0xbe, 0x2c, 0xcc, 0xd5, 0x8e, 0x66, 0xdb, 0xad, 0x16, 0x34, 0x0b, 0x7f, 0xdc, 0xbc, 0x7e, 0x21, 0xcb, 0x56, 0xda, - 0xc2, 0xed, 0xb4, 0x8d, 0x3b, 0x69, 0x07, 0x1f, 0xa7, 0xc7, 0xf8, 0x24, 0x3d, 0xc1, 0xa7, 0xe9, 0x29, 0x3e, 0x4b, - 0xcf, 0xf0, 0xfd, 0xf4, 0x3e, 0x3e, 0x4f, 0xcf, 0xf1, 0x83, 0xf4, 0x01, 0x7e, 0x98, 0xb6, 0x5b, 0xf8, 0x51, 0xda, - 0x6e, 0xe3, 0xc7, 0x69, 0xbb, 0x83, 0x9f, 0xa4, 0xed, 0x63, 0xfc, 0x34, 0x6d, 0x9f, 0xe0, 0x67, 0x69, 0xfb, 0x14, - 0x53, 0xc8, 0x1d, 0x42, 0x6e, 0x06, 0xb9, 0x23, 0xc8, 0x65, 0x90, 0x3b, 0x4e, 0xdb, 0xa7, 0x6b, 0xac, 0x6c, 0xc8, - 0x8d, 0xa8, 0xd5, 0xee, 0x1c, 0x9f, 0x9c, 0x9e, 0xdd, 0x3f, 0x7f, 0xf0, 0xf0, 0xd1, 0xe3, 0x27, 0x4f, 0x9f, 0x45, - 0x03, 0x3c, 0x34, 0x9e, 0x2f, 0x4a, 0xf4, 0xf9, 0x41, 0xfb, 0x74, 0x80, 0xaf, 0xfd, 0x67, 0xcc, 0x0f, 0x3a, 0x27, - 0x2d, 0x74, 0x79, 0x79, 0x32, 0x68, 0x94, 0xb9, 0x8f, 0x8c, 0xc3, 0x4d, 0x95, 0x45, 0x08, 0x89, 0x21, 0x07, 0xe1, - 0x3b, 0x53, 0xef, 0x11, 0x8b, 0x79, 0x52, 0xa0, 0x83, 0x03, 0xf3, 0x63, 0xe2, 0x7f, 0x0c, 0xfd, 0x0f, 0x1a, 0x2c, - 0xd2, 0x2d, 0x8d, 0x9d, 0xc7, 0xb5, 0x2e, 0xfd, 0x1d, 0x4a, 0x53, 0xa2, 0x3d, 0xee, 0x8c, 0xfa, 0xff, 0x2b, 0xb2, - 0x46, 0x3b, 0xe4, 0xc4, 0x2a, 0xc6, 0x4e, 0x7b, 0x8c, 0x2c, 0x8b, 0xb4, 0x73, 0x7a, 0x7a, 0xf0, 0x4b, 0x9f, 0xf7, - 0xdb, 0x83, 0xc1, 0x61, 0xfb, 0x3e, 0x9e, 0x94, 0x09, 0x1d, 0x9b, 0x30, 0x2c, 0x13, 0x8e, 0x6d, 0x02, 0x4d, 0x6d, - 0x6d, 0x48, 0x3a, 0x31, 0x49, 0x50, 0x62, 0x9d, 0x9a, 0xb6, 0xef, 0xdb, 0xb6, 0x1f, 0x80, 0x35, 0x99, 0x69, 0xde, - 0x35, 0x7d, 0x71, 0x71, 0xb2, 0x72, 0x8d, 0xe2, 0x49, 0xea, 0x5a, 0xf3, 0x89, 0x27, 0x83, 0x01, 0x1e, 0x9a, 0xc4, - 0xd3, 0x2a, 0xf1, 0x6c, 0x30, 0x70, 0x5d, 0x3d, 0x30, 0x5d, 0xdd, 0xaf, 0xb2, 0xce, 0x07, 0x03, 0xd3, 0x25, 0x72, - 0xb1, 0x03, 0x94, 0xde, 0xfb, 0x5a, 0xea, 0x6f, 0xf8, 0x45, 0xe7, 0xf4, 0xb4, 0x07, 0x18, 0x66, 0x6c, 0x82, 0x3d, - 0x8c, 0x6e, 0x02, 0x18, 0xdd, 0xc1, 0xef, 0xde, 0x90, 0xa6, 0xd7, 0xb4, 0x04, 0x52, 0x2f, 0xfa, 0xaf, 0xa8, 0xa1, - 0x0d, 0xcc, 0xcd, 0x9f, 0x89, 0xfd, 0x33, 0x44, 0x8d, 0xaf, 0x14, 0xc0, 0x0d, 0x1a, 0x29, 0xaf, 0x52, 0x36, 0x3d, - 0x7e, 0xa1, 0xe0, 0xe2, 0x33, 0x55, 0x39, 0xed, 0xad, 0xa6, 0x37, 0xc3, 0xd5, 0x54, 0x7d, 0x45, 0x7f, 0xc5, 0x7f, - 0xa9, 0xc3, 0xb8, 0xdf, 0x6c, 0x24, 0xec, 0xaf, 0x11, 0xf8, 0x12, 0xf5, 0xd2, 0x11, 0x9b, 0xa0, 0x5e, 0xff, 0x2f, - 0x85, 0x07, 0x8d, 0x20, 0xe3, 0x87, 0xed, 0x14, 0xf0, 0x34, 0xda, 0x4c, 0x8c, 0x7f, 0x40, 0x3d, 0xd4, 0xfb, 0x4b, - 0x1d, 0xfe, 0x85, 0xee, 0x1d, 0x55, 0x73, 0xf9, 0x5d, 0xba, 0x2d, 0x5c, 0x85, 0x1f, 0x3a, 0x2c, 0xb7, 0x30, 0xc3, - 0xed, 0x26, 0x83, 0x60, 0x6d, 0xe0, 0x8a, 0x4e, 0x62, 0xd9, 0xe0, 0x47, 0xc7, 0x2d, 0xf4, 0x43, 0xbb, 0x03, 0xca, - 0x95, 0xa6, 0x38, 0xdc, 0xde, 0xf4, 0x45, 0xf3, 0x18, 0x3f, 0x68, 0x16, 0xb8, 0x8d, 0x70, 0xb3, 0xed, 0xb5, 0xde, - 0x7d, 0x15, 0xb7, 0x10, 0x56, 0xf1, 0x39, 0xfc, 0x73, 0x82, 0x06, 0xd5, 0x86, 0xbc, 0xa2, 0x9b, 0xbd, 0x83, 0xdf, - 0x2c, 0x89, 0x55, 0x83, 0x1f, 0x9d, 0xb5, 0xd0, 0x0f, 0x67, 0xa6, 0x23, 0x76, 0xa8, 0x77, 0x74, 0x25, 0xf1, 0x49, - 0x53, 0x42, 0x47, 0xad, 0xb2, 0x1f, 0x11, 0x9f, 0x22, 0x2c, 0xe2, 0x63, 0xf8, 0xa7, 0x1d, 0xf6, 0xf3, 0xeb, 0x56, - 0x3f, 0x66, 0xde, 0x6d, 0x9c, 0x9c, 0x5a, 0x37, 0x5c, 0x65, 0xef, 0xc4, 0x1b, 0xec, 0xb2, 0x6d, 0x2e, 0xf3, 0xda, - 0x47, 0xf0, 0x81, 0xb0, 0x3e, 0x24, 0x0a, 0xb3, 0x43, 0xf0, 0xdf, 0x05, 0xb3, 0x15, 0x75, 0x71, 0xdc, 0x55, 0x8d, - 0x06, 0x12, 0x7d, 0x35, 0x38, 0x24, 0xed, 0xa6, 0x6e, 0x32, 0x0c, 0xbf, 0x1b, 0xa4, 0x0c, 0x0a, 0x27, 0xaa, 0x5e, - 0x1f, 0xbb, 0x5e, 0xed, 0xcd, 0xbf, 0xc7, 0x0e, 0x42, 0x88, 0xea, 0xc5, 0xba, 0xc9, 0xd0, 0x91, 0x68, 0xc4, 0xfa, - 0x82, 0xf5, 0xce, 0xd2, 0x16, 0x32, 0xd8, 0xa9, 0x7a, 0x31, 0x6b, 0x72, 0x48, 0xef, 0xa4, 0x31, 0x6f, 0x6a, 0xf8, - 0x75, 0x12, 0xcc, 0x42, 0x00, 0xde, 0x55, 0xde, 0x48, 0xc5, 0x51, 0xe7, 0xf4, 0x14, 0x0b, 0xc2, 0x93, 0x89, 0xf9, - 0xa5, 0x08, 0x4f, 0x86, 0xe6, 0x97, 0x24, 0x25, 0xbc, 0x6c, 0xef, 0xb8, 0x20, 0xc1, 0xaa, 0x9a, 0x14, 0x0a, 0x0b, - 0x5a, 0xa0, 0xa3, 0x8e, 0x37, 0x0b, 0xc0, 0x53, 0x3f, 0x07, 0x50, 0x83, 0x14, 0xc6, 0x22, 0x54, 0x36, 0x0b, 0x9c, - 0x13, 0x7a, 0x99, 0x9c, 0xf6, 0xa6, 0x47, 0x71, 0xa7, 0x29, 0x9b, 0x05, 0x4a, 0xa7, 0x47, 0xa6, 0x26, 0xce, 0xc8, - 0x63, 0x6a, 0x5b, 0xc3, 0x53, 0xb8, 0xcb, 0xcd, 0x48, 0x76, 0x78, 0xd6, 0x6a, 0x24, 0xa7, 0x08, 0xf7, 0xb3, 0x55, - 0x0b, 0xe7, 0xab, 0x55, 0x0b, 0xd3, 0x60, 0x19, 0x1e, 0x0b, 0x0f, 0x90, 0x52, 0x53, 0xb7, 0x19, 0x9b, 0xa7, 0xc7, - 0x63, 0x0d, 0x76, 0x09, 0x1a, 0xbc, 0x7d, 0x34, 0xf8, 0x21, 0xa5, 0xdc, 0x5d, 0x08, 0x22, 0x13, 0x9d, 0x70, 0x1c, - 0xea, 0xee, 0x5e, 0x0b, 0xbf, 0xae, 0xde, 0xb2, 0x54, 0xc4, 0xbf, 0x4b, 0x6c, 0xd3, 0x82, 0x62, 0x74, 0xbb, 0xd8, - 0xaf, 0x74, 0xab, 0xd8, 0x9b, 0x1d, 0xc5, 0xae, 0xb6, 0x8b, 0x7d, 0x94, 0x81, 0xa6, 0x91, 0xff, 0x70, 0x7c, 0xd6, - 0x6a, 0x1c, 0x03, 0xb2, 0x1e, 0x9f, 0xb5, 0xaa, 0x42, 0xf7, 0x68, 0xb5, 0x56, 0x9a, 0x7c, 0xa1, 0xd6, 0xd7, 0x82, - 0x7b, 0xa7, 0x6f, 0xb3, 0x70, 0xd6, 0xe5, 0xbc, 0xf4, 0x2f, 0xef, 0x9f, 0x82, 0x2d, 0x8b, 0x30, 0xd4, 0x4e, 0xf7, - 0xcf, 0x06, 0xbd, 0x29, 0x8b, 0x1b, 0x90, 0x8a, 0xd2, 0xb1, 0x76, 0xbf, 0x50, 0x79, 0xa5, 0xfd, 0x51, 0x42, 0x52, - 0x67, 0x80, 0xb0, 0x24, 0x0d, 0xdd, 0x3f, 0x1e, 0x98, 0xf3, 0xae, 0x80, 0xdf, 0x27, 0xe6, 0x77, 0xa9, 0x50, 0x72, - 0x0e, 0x19, 0xd3, 0x9b, 0x61, 0xd4, 0x13, 0xe4, 0x35, 0x8d, 0x8d, 0x8d, 0x3d, 0x4a, 0xcb, 0x0c, 0xf5, 0x15, 0x32, - 0xde, 0x94, 0x19, 0x82, 0xbc, 0x16, 0xee, 0x37, 0x5e, 0x16, 0x29, 0xd8, 0xdb, 0xe0, 0x49, 0x0a, 0xb6, 0x36, 0x78, - 0x98, 0x0a, 0xf0, 0x07, 0xa1, 0x29, 0x0b, 0xac, 0xf8, 0x1f, 0x3a, 0x0d, 0x9e, 0xb9, 0x75, 0x26, 0x06, 0x4b, 0xbb, - 0x0c, 0x4e, 0x8a, 0x8f, 0x32, 0x86, 0xbf, 0x0d, 0x8d, 0x30, 0x83, 0x36, 0x19, 0xc2, 0x3c, 0x29, 0x08, 0xa4, 0x61, - 0x9e, 0x4c, 0x08, 0x83, 0x26, 0x79, 0x32, 0x24, 0xac, 0xdf, 0x09, 0xd0, 0xe4, 0xa9, 0x81, 0x1d, 0x00, 0x87, 0xd7, - 0x2f, 0xf2, 0xb5, 0x6d, 0x1c, 0x2c, 0x04, 0xa0, 0x09, 0x41, 0xb8, 0x8a, 0x61, 0x16, 0xb0, 0x39, 0xcd, 0xcf, 0x4e, - 0x15, 0xfe, 0x92, 0x27, 0xd4, 0x50, 0xef, 0x4f, 0x40, 0x56, 0xe3, 0x7b, 0x4b, 0xb6, 0xc6, 0x7b, 0xf7, 0x96, 0x62, - 0xfd, 0x03, 0xfc, 0x51, 0xf6, 0x0f, 0x30, 0x0f, 0x09, 0x45, 0x6b, 0xf4, 0x29, 0x85, 0x62, 0x3b, 0x4a, 0xa1, 0x4f, - 0xde, 0x1d, 0x50, 0x91, 0xe5, 0x6d, 0x1a, 0x8d, 0x68, 0xf1, 0x25, 0xc2, 0x7f, 0xa6, 0x51, 0x0e, 0xdc, 0x62, 0x84, - 0x3f, 0xa6, 0x51, 0xc1, 0x22, 0xfc, 0x47, 0x1a, 0x0d, 0xf3, 0x45, 0x84, 0x3f, 0xa4, 0xd1, 0xa4, 0x88, 0xf0, 0x7b, - 0x50, 0xd6, 0x8e, 0xf8, 0x62, 0x16, 0xe1, 0xdf, 0xd3, 0x48, 0x19, 0x6f, 0x08, 0xfc, 0x30, 0x8d, 0x18, 0x8b, 0xf0, - 0xbb, 0x34, 0x92, 0x79, 0x84, 0xaf, 0xd2, 0x48, 0x16, 0x11, 0x7e, 0x94, 0x46, 0x05, 0x8d, 0xf0, 0xe3, 0x34, 0x82, - 0x42, 0x93, 0x08, 0x3f, 0x49, 0x23, 0x68, 0x59, 0x45, 0xf8, 0x6d, 0x1a, 0x71, 0x11, 0xe1, 0xdf, 0xd2, 0x48, 0x2f, - 0x8a, 0xbf, 0x17, 0x92, 0xab, 0x08, 0x3f, 0x4d, 0xa3, 0x29, 0x8f, 0xf0, 0x9b, 0x34, 0x2a, 0x64, 0x84, 0x5f, 0xa7, - 0x11, 0xcd, 0x23, 0xfc, 0x2a, 0x8d, 0x72, 0x16, 0xe1, 0x5f, 0xd3, 0x68, 0xc4, 0x22, 0xfc, 0x32, 0x8d, 0xee, 0x58, - 0x9e, 0xcb, 0x08, 0x3f, 0x4b, 0x23, 0x26, 0x22, 0xfc, 0x4b, 0x1a, 0x65, 0xd3, 0x08, 0xff, 0x94, 0x46, 0xb4, 0xf8, - 0xa2, 0x22, 0xfc, 0x3c, 0x8d, 0x18, 0x8d, 0xf0, 0x0b, 0xdb, 0xd1, 0x24, 0xc2, 0x3f, 0xa7, 0xd1, 0xcd, 0x34, 0x5a, - 0x63, 0xa5, 0xc8, 0xf2, 0x35, 0xcf, 0xd8, 0x1f, 0x2c, 0x8d, 0xc6, 0xad, 0xf1, 0xf9, 0x78, 0x1c, 0x61, 0x2a, 0x34, - 0xff, 0x7b, 0xc1, 0x6e, 0x9e, 0x6a, 0x48, 0xa4, 0x6c, 0x38, 0xba, 0x1f, 0x61, 0xfa, 0xf7, 0x82, 0xa6, 0xd1, 0x78, - 0x6c, 0x0a, 0xfc, 0xbd, 0xa0, 0x33, 0x5a, 0xbc, 0x65, 0x69, 0x74, 0x7f, 0x3c, 0x1e, 0x8f, 0x4e, 0x22, 0x4c, 0xff, - 0x59, 0x7c, 0x34, 0x2d, 0x98, 0x02, 0x43, 0xc6, 0x27, 0x50, 0xf7, 0x74, 0x7c, 0x3a, 0xca, 0x22, 0x3c, 0xe4, 0xea, - 0xef, 0x05, 0x7c, 0x8f, 0xd9, 0x49, 0x76, 0x12, 0xe1, 0x61, 0x4e, 0xb3, 0x2f, 0x69, 0xd4, 0x32, 0xbf, 0xc4, 0x2f, - 0x6c, 0xf4, 0x7a, 0x26, 0xcd, 0x55, 0xc6, 0x98, 0x0d, 0xb3, 0x51, 0x84, 0xcd, 0x60, 0xc6, 0xf0, 0xf7, 0x2b, 0x7f, - 0xc7, 0x74, 0x1a, 0x9d, 0xd3, 0xce, 0x90, 0x75, 0x22, 0x3c, 0x7c, 0x73, 0x23, 0xd2, 0x88, 0x9e, 0x76, 0x68, 0x87, - 0x46, 0x78, 0xb8, 0x28, 0xf2, 0xbb, 0x1b, 0x29, 0x47, 0x00, 0x84, 0xe1, 0xf9, 0xf9, 0xfd, 0x08, 0x67, 0xf4, 0x57, - 0x0d, 0xb5, 0x4f, 0xc7, 0x0f, 0x18, 0x6d, 0x45, 0xf8, 0x17, 0x5a, 0xe8, 0x8f, 0x0b, 0xe5, 0x06, 0xda, 0x82, 0x14, - 0x99, 0xbd, 0x03, 0x35, 0x7f, 0x34, 0xea, 0x9c, 0x3d, 0x68, 0xb3, 0x08, 0x67, 0x57, 0xaf, 0xa1, 0xb7, 0xfb, 0xe3, - 0xd3, 0x16, 0x7c, 0x08, 0x90, 0x4b, 0x59, 0x01, 0x8d, 0x9c, 0x9d, 0x3c, 0x38, 0x65, 0x23, 0x93, 0xa8, 0x78, 0xfe, - 0xc5, 0xcc, 0xfe, 0x1c, 0xe6, 0x93, 0x15, 0x7c, 0xa6, 0xa4, 0x48, 0xa3, 0x51, 0xd6, 0x3e, 0x39, 0x86, 0x84, 0x3b, - 0x2a, 0x3c, 0x70, 0x6e, 0xa1, 0xea, 0xf9, 0x30, 0xc2, 0xb7, 0x36, 0xf5, 0x7c, 0x68, 0x3e, 0x26, 0xef, 0x7e, 0x15, - 0x6f, 0x46, 0x69, 0x34, 0x3c, 0x3f, 0x3f, 0x6b, 0x41, 0xc2, 0x07, 0x7a, 0x97, 0x46, 0xf4, 0x01, 0xfc, 0x07, 0xd9, - 0x1f, 0x9f, 0x41, 0x87, 0x30, 0xc2, 0xdb, 0xc9, 0xc7, 0x30, 0xe7, 0xcb, 0x94, 0x7e, 0xe1, 0x69, 0x34, 0x1c, 0x0d, - 0xef, 0x9f, 0x41, 0xbd, 0x19, 0x9d, 0x3c, 0xd3, 0x14, 0xda, 0x6d, 0xb5, 0x4c, 0xcb, 0xef, 0xf8, 0x57, 0x66, 0xaa, - 0x9f, 0x9e, 0x9e, 0x0d, 0x3b, 0x30, 0x82, 0x2b, 0x50, 0xa8, 0xc0, 0x78, 0xce, 0x33, 0xd3, 0xe0, 0x55, 0xf6, 0x74, - 0x94, 0x46, 0x0f, 0x1e, 0x1c, 0x77, 0xb2, 0x2c, 0xc2, 0xb7, 0x1f, 0x47, 0xb6, 0xb6, 0xc9, 0x53, 0x00, 0xfb, 0x34, - 0x62, 0x0f, 0x1e, 0x9c, 0xdd, 0xa7, 0xf0, 0xfd, 0xdc, 0xb4, 0x75, 0x3e, 0x1e, 0x66, 0xe7, 0xd0, 0xd6, 0xef, 0x30, - 0x9d, 0x93, 0xf3, 0xe3, 0x91, 0xe9, 0xeb, 0x77, 0x33, 0xea, 0xce, 0xf8, 0x64, 0x7c, 0x62, 0x32, 0xcd, 0x50, 0xcb, - 0xcf, 0xdf, 0x58, 0x1a, 0x65, 0x6c, 0xd4, 0x8e, 0xf0, 0xad, 0x5b, 0xb8, 0x07, 0x27, 0xad, 0xd6, 0xe8, 0x38, 0xc2, - 0xa3, 0x87, 0xf3, 0xf9, 0x5b, 0x03, 0xc1, 0xf6, 0xc9, 0x03, 0xfb, 0xad, 0xbe, 0xdc, 0x41, 0xd3, 0x43, 0x03, 0xb4, - 0x11, 0x9f, 0x99, 0x96, 0xcf, 0x1e, 0xc0, 0x7f, 0xe6, 0xdb, 0x34, 0x5d, 0x7e, 0xcb, 0xd1, 0xc4, 0x2e, 0x4a, 0x9b, - 0x3d, 0x68, 0x41, 0x8d, 0x31, 0xff, 0x38, 0x2c, 0x38, 0xa0, 0xd1, 0xb0, 0x03, 0xff, 0x17, 0xe1, 0x71, 0x7e, 0xf5, - 0xda, 0xe1, 0xec, 0x78, 0x4c, 0xc7, 0xad, 0x08, 0x8f, 0xe5, 0x47, 0xa5, 0x3f, 0x3c, 0x14, 0x69, 0xd4, 0xe9, 0x9c, - 0x0f, 0x4d, 0x99, 0xc5, 0x2f, 0x8a, 0x1b, 0x3c, 0x6e, 0x99, 0x56, 0x26, 0xf4, 0xad, 0x1a, 0x5e, 0x49, 0x58, 0x49, - 0xf8, 0x2f, 0xc2, 0x13, 0xd0, 0xc2, 0xb9, 0x56, 0xce, 0xed, 0x76, 0x98, 0xbc, 0x33, 0xa8, 0x39, 0xba, 0x0f, 0xf0, - 0xf2, 0xcb, 0x38, 0xa2, 0xf4, 0xb4, 0xd3, 0x8a, 0xb0, 0x19, 0xf5, 0x79, 0x0b, 0xfe, 0x8b, 0xb0, 0x85, 0x9c, 0x81, - 0xeb, 0xe4, 0xe3, 0xb3, 0x97, 0x37, 0x69, 0x44, 0x47, 0xe3, 0x31, 0x2c, 0x89, 0x99, 0x8c, 0x2f, 0x36, 0x95, 0x82, - 0xdd, 0xfd, 0x7a, 0xe3, 0xb6, 0x8b, 0x49, 0xd0, 0x0e, 0x3a, 0x67, 0x0f, 0x86, 0x27, 0x11, 0x7e, 0x3b, 0xe2, 0x54, - 0xc0, 0x2a, 0x65, 0xa3, 0xd3, 0xec, 0x34, 0x33, 0x09, 0x13, 0x99, 0x46, 0x27, 0xb0, 0xe4, 0x9d, 0x08, 0xf3, 0xaf, - 0x57, 0x77, 0x16, 0xdd, 0xa0, 0xb6, 0x43, 0x90, 0x71, 0x8b, 0x9d, 0x9d, 0x67, 0x11, 0xce, 0xe9, 0xd7, 0x67, 0xbf, - 0x16, 0x69, 0xc4, 0xce, 0xd8, 0xd9, 0x98, 0xfa, 0xef, 0x3f, 0xd4, 0xd4, 0xd4, 0x68, 0x8d, 0x4f, 0x21, 0xe9, 0x46, - 0x98, 0xb1, 0xde, 0xcf, 0xc6, 0x06, 0x43, 0x5e, 0xcd, 0xa4, 0xc8, 0x9e, 0x8e, 0xc7, 0xd2, 0x62, 0x31, 0x85, 0x4d, - 0xf8, 0x27, 0x40, 0x9b, 0x8e, 0x46, 0xe7, 0xec, 0x2c, 0xc2, 0x7f, 0xda, 0x5d, 0xe2, 0x26, 0xf0, 0xa7, 0xc5, 0x6c, - 0xe6, 0x76, 0xfb, 0x9f, 0x16, 0x28, 0x30, 0xdf, 0x31, 0x1d, 0xd3, 0x51, 0x27, 0xc2, 0x7f, 0x1a, 0xb8, 0x8c, 0x8e, - 0xe1, 0x3f, 0x28, 0x00, 0x9d, 0x3d, 0x68, 0x31, 0xf6, 0xa0, 0x65, 0xbe, 0xc2, 0x3c, 0x37, 0xf3, 0xe1, 0x59, 0xd6, - 0x8e, 0xf0, 0x9f, 0x0e, 0x1d, 0xc7, 0x63, 0xda, 0x02, 0x74, 0xfc, 0xd3, 0xa1, 0x63, 0xa7, 0x35, 0xec, 0x50, 0xf3, - 0x6d, 0xb1, 0xe6, 0xfc, 0x7e, 0xc6, 0x60, 0x72, 0x7f, 0x5a, 0x84, 0xbc, 0x7f, 0xff, 0xfc, 0xfc, 0xc1, 0x03, 0xf8, - 0x34, 0x6d, 0x97, 0x9f, 0x4a, 0x3f, 0xcc, 0x0d, 0x92, 0xb5, 0xb2, 0x13, 0xa0, 0x93, 0x7f, 0x9a, 0x31, 0x8e, 0xc7, - 0x63, 0xd6, 0x8a, 0x70, 0xce, 0x67, 0xcc, 0x62, 0x82, 0xfd, 0x6d, 0x3a, 0x3a, 0xee, 0x64, 0xa3, 0xe3, 0x4e, 0x84, - 0xf3, 0xb7, 0xcf, 0xcc, 0x6c, 0x5a, 0x30, 0x7b, 0xbf, 0xe5, 0x3c, 0xd6, 0xcc, 0xe8, 0x1b, 0x18, 0x24, 0xac, 0x34, - 0x54, 0x7e, 0x1f, 0xd0, 0xc3, 0xb3, 0xb3, 0x6c, 0x04, 0x03, 0x7d, 0x0f, 0xdd, 0x02, 0x18, 0xdf, 0xdb, 0xcd, 0x37, - 0xa4, 0xa7, 0xa7, 0x30, 0xdd, 0xf7, 0xf3, 0x45, 0x31, 0x7f, 0x95, 0x46, 0x0f, 0x8e, 0xef, 0xb7, 0x46, 0xc3, 0x08, - 0xbf, 0x77, 0x13, 0x3c, 0xce, 0x86, 0xc7, 0xf7, 0xdb, 0x11, 0x7e, 0x6f, 0xf6, 0xdb, 0xfd, 0xe1, 0xd9, 0x39, 0x9c, - 0x1b, 0xef, 0xd5, 0xbc, 0x78, 0x3b, 0x31, 0x05, 0xc6, 0xf4, 0x01, 0x34, 0xfb, 0x9b, 0xd9, 0x8d, 0xa3, 0x36, 0x6c, - 0xe4, 0xf7, 0x66, 0x93, 0x19, 0x3c, 0xb9, 0xdf, 0x3e, 0x3d, 0x3f, 0x8d, 0xf0, 0x8c, 0x8f, 0x04, 0x10, 0x78, 0xb3, - 0x51, 0x1e, 0xb4, 0x1f, 0xdc, 0x6f, 0x45, 0x78, 0xf6, 0x56, 0x67, 0x1f, 0xe9, 0xcc, 0x50, 0xe3, 0x31, 0xc0, 0x6c, - 0xc6, 0x95, 0xbe, 0x7b, 0xa3, 0x1c, 0x3d, 0x66, 0xed, 0x08, 0xcf, 0x64, 0x96, 0x51, 0xf5, 0xd6, 0x26, 0x0c, 0x4f, - 0x23, 0x2c, 0xe8, 0x57, 0xfa, 0x59, 0xfa, 0xcd, 0x34, 0x62, 0x74, 0x64, 0xd2, 0x0c, 0x0e, 0x47, 0xf8, 0xdd, 0x08, - 0x2e, 0x23, 0xd3, 0x68, 0x3c, 0x1a, 0x9f, 0x02, 0x78, 0x80, 0x00, 0x59, 0xec, 0x06, 0x68, 0xc0, 0xd7, 0xe8, 0xd1, - 0x30, 0x8d, 0xce, 0x86, 0xe7, 0xac, 0x73, 0x1c, 0xe1, 0x92, 0x1a, 0xd1, 0x53, 0xc8, 0x37, 0x9f, 0x1f, 0xcd, 0x96, - 0x3a, 0xb1, 0x09, 0x06, 0x40, 0x23, 0x7a, 0xbf, 0x35, 0x3a, 0x8b, 0xf0, 0xfc, 0x35, 0xf3, 0x7b, 0x8c, 0x31, 0x76, - 0x0e, 0xb0, 0x84, 0x24, 0x83, 0x40, 0xe7, 0xe3, 0xe1, 0x83, 0x73, 0xf3, 0x0d, 0x60, 0xa0, 0x63, 0xc6, 0x00, 0x48, - 0xf3, 0xd7, 0xac, 0x04, 0xc4, 0x68, 0x78, 0xbf, 0x05, 0xf4, 0x65, 0x4e, 0xe7, 0xf4, 0x8e, 0xde, 0x3c, 0x9d, 0x9b, - 0x39, 0x8d, 0x47, 0xa7, 0x11, 0x9e, 0x3f, 0xff, 0x65, 0xbe, 0x18, 0x8f, 0xcd, 0x84, 0xe8, 0xf0, 0x41, 0x84, 0xe7, - 0xac, 0x58, 0xc0, 0x1a, 0x9d, 0x9f, 0x1e, 0x8f, 0x23, 0xec, 0xd0, 0x30, 0x6b, 0x65, 0x43, 0xb8, 0x6d, 0x5d, 0xcc, - 0xd2, 0x68, 0x34, 0xa2, 0xad, 0x11, 0xdc, 0xbd, 0xca, 0x9b, 0x5f, 0x0b, 0x8b, 0x46, 0xcc, 0xe0, 0x83, 0x5b, 0x43, - 0x98, 0x2f, 0xc0, 0xe3, 0xe3, 0x90, 0x65, 0x19, 0x75, 0x89, 0x67, 0x67, 0xc7, 0xc7, 0x80, 0x7b, 0x76, 0x86, 0x16, - 0x41, 0xde, 0xa8, 0xbb, 0x61, 0x21, 0xe1, 0xe8, 0x02, 0xa2, 0x0a, 0x64, 0xf5, 0xcd, 0xdd, 0x6b, 0x43, 0x57, 0xdb, - 0x67, 0x0f, 0x60, 0x01, 0x14, 0x1d, 0x8d, 0x5e, 0xd9, 0xc3, 0xed, 0x7c, 0x78, 0x72, 0xda, 0x3e, 0x8e, 0xb0, 0xdf, - 0x08, 0xf4, 0xbc, 0x75, 0xbf, 0x03, 0x25, 0xc4, 0xe8, 0xce, 0x96, 0x18, 0x9f, 0xd0, 0x93, 0xb3, 0x56, 0x84, 0xfd, - 0xd6, 0x60, 0xe7, 0xc3, 0xd3, 0xfb, 0xf0, 0xa9, 0xa6, 0x2c, 0xcf, 0x0d, 0x7e, 0x9f, 0x02, 0x5c, 0x14, 0x7f, 0x26, - 0x68, 0x1a, 0xd1, 0xd6, 0x69, 0xa7, 0x33, 0x82, 0xcf, 0xfc, 0x2b, 0x2b, 0xd2, 0x28, 0x6b, 0xc1, 0x7f, 0x11, 0x0e, - 0x76, 0x12, 0x1b, 0x46, 0xd8, 0xe0, 0xdd, 0x19, 0x3d, 0x35, 0x7b, 0xdf, 0xed, 0xaa, 0xd6, 0x79, 0x0b, 0x36, 0xac, - 0xdb, 0x54, 0xee, 0x4b, 0x09, 0x79, 0xe3, 0x48, 0x2c, 0x8d, 0x70, 0x80, 0xa0, 0xe3, 0xfb, 0xe3, 0x08, 0xfb, 0x1d, - 0x77, 0x72, 0x76, 0xde, 0x01, 0x52, 0xa6, 0x81, 0x50, 0x8c, 0x3a, 0xc3, 0x13, 0x20, 0x4d, 0x9a, 0xbd, 0xb6, 0x78, - 0x12, 0x61, 0xfd, 0x54, 0xe9, 0x57, 0x69, 0x34, 0x3a, 0x1f, 0x8e, 0x47, 0xe7, 0x11, 0xd6, 0x72, 0x46, 0xb5, 0x34, - 0x14, 0xf0, 0xf8, 0xe4, 0x7e, 0x84, 0x0d, 0x9a, 0xb7, 0x58, 0x6b, 0xd4, 0x8a, 0xb0, 0x3b, 0x4a, 0x18, 0x3b, 0xef, - 0xc0, 0xb4, 0x7e, 0x7e, 0xae, 0x01, 0x97, 0x47, 0x6c, 0x78, 0x1c, 0xe1, 0x92, 0xde, 0x1b, 0x42, 0x04, 0x5f, 0x6a, - 0x26, 0xbf, 0x38, 0xd6, 0x03, 0x48, 0x9d, 0xdf, 0xf0, 0xb0, 0x0c, 0x2f, 0x6f, 0x2c, 0x1a, 0x51, 0xb3, 0xc5, 0x83, - 0xdb, 0xe8, 0x27, 0x34, 0xf6, 0x6c, 0x3b, 0x27, 0xcb, 0x35, 0x2e, 0x83, 0xbc, 0x7e, 0x61, 0x77, 0x2a, 0x56, 0xca, - 0x70, 0xb2, 0x41, 0x0a, 0x38, 0x62, 0x38, 0xb7, 0x06, 0xe7, 0xb9, 0x0a, 0x82, 0xa4, 0x20, 0xad, 0xae, 0xb8, 0xf0, - 0xde, 0xb4, 0x5d, 0x01, 0xa1, 0x1f, 0x20, 0xbd, 0x20, 0x94, 0x68, 0x88, 0x90, 0x63, 0x85, 0x49, 0xef, 0x64, 0x60, - 0x64, 0x4a, 0x69, 0xdd, 0x16, 0x28, 0xa1, 0x3e, 0x36, 0x3e, 0x5c, 0x95, 0x43, 0xf4, 0x28, 0xd4, 0x95, 0xc4, 0x44, - 0xba, 0x7e, 0x21, 0x74, 0xac, 0x54, 0xbf, 0x18, 0xe0, 0xf6, 0x19, 0xc2, 0x10, 0x43, 0x82, 0xf4, 0xe5, 0xe5, 0x65, - 0xfb, 0xec, 0xc0, 0x08, 0x7d, 0x97, 0x97, 0xe7, 0xf6, 0x07, 0xfc, 0x3b, 0xa8, 0xe2, 0x76, 0xc3, 0xf8, 0xde, 0xb3, - 0x40, 0xa3, 0x67, 0xf8, 0xeb, 0xf7, 0x6c, 0xb5, 0x8a, 0xdf, 0x33, 0x02, 0x33, 0xc6, 0xef, 0x59, 0x62, 0xee, 0x48, - 0xac, 0x87, 0x10, 0xe9, 0x83, 0xe6, 0xac, 0x85, 0x21, 0x9a, 0xbc, 0xe7, 0xbc, 0xdf, 0xb3, 0x3e, 0xaf, 0x7b, 0x97, - 0x57, 0x21, 0x9c, 0x0f, 0x0e, 0x96, 0x45, 0xaa, 0xad, 0x98, 0xa0, 0xad, 0x98, 0xa0, 0xad, 0x98, 0xa0, 0xab, 0x20, - 0xfa, 0x27, 0x3d, 0x90, 0x52, 0x8c, 0xb2, 0xc5, 0xf1, 0xd4, 0xef, 0x40, 0xed, 0x01, 0xda, 0xc9, 0x5e, 0xa5, 0xec, - 0x28, 0x75, 0x15, 0x3b, 0x15, 0x18, 0x3b, 0x13, 0x9d, 0xb6, 0xe3, 0xe8, 0xdf, 0x51, 0x77, 0xbc, 0xac, 0x89, 0x65, - 0xef, 0x76, 0x8a, 0x65, 0xb0, 0x92, 0x46, 0x34, 0xdb, 0xb7, 0xf1, 0x48, 0x74, 0xff, 0xbe, 0x11, 0xcc, 0xaa, 0x20, - 0x79, 0x0d, 0x48, 0xea, 0x82, 0x14, 0x72, 0x6e, 0xa4, 0xb4, 0x02, 0xa5, 0x23, 0x1d, 0x17, 0xa0, 0xa1, 0xf4, 0x0a, - 0xca, 0x32, 0x96, 0x6b, 0xc3, 0x00, 0x44, 0x59, 0x19, 0xcd, 0xca, 0x6a, 0xa7, 0x20, 0xba, 0x80, 0x26, 0xcc, 0x48, - 0x2c, 0xd0, 0x80, 0x30, 0x0d, 0x08, 0x57, 0x19, 0xc4, 0x19, 0x97, 0x7d, 0x62, 0xb2, 0x95, 0xc9, 0x56, 0x65, 0xb6, - 0xf4, 0xd9, 0x56, 0x48, 0x94, 0x26, 0x5b, 0x96, 0xd9, 0x20, 0xb3, 0xe1, 0x49, 0xaa, 0xf0, 0x30, 0x95, 0x56, 0x54, - 0xab, 0x64, 0xab, 0xb7, 0x34, 0xd4, 0xe6, 0x1e, 0x1c, 0xc4, 0xa5, 0x9c, 0x64, 0xd4, 0xc4, 0xf7, 0x96, 0x3c, 0x29, - 0x8c, 0x0c, 0xc4, 0x93, 0x89, 0xfb, 0x3b, 0x5c, 0x6f, 0xca, 0x4a, 0xc5, 0x64, 0xf8, 0x8d, 0x92, 0xe8, 0x93, 0x57, - 0xa2, 0xbe, 0xe7, 0x26, 0x0a, 0xd0, 0x05, 0x49, 0x5a, 0xad, 0xe3, 0xf6, 0x71, 0xeb, 0xbc, 0xc7, 0x0f, 0xdb, 0x9d, - 0xe4, 0x41, 0x27, 0x35, 0x8a, 0x88, 0xb9, 0xbc, 0x01, 0x05, 0xcc, 0x51, 0x27, 0x39, 0x41, 0x87, 0xed, 0xa4, 0x75, - 0x7a, 0xda, 0x84, 0x7f, 0xf0, 0x23, 0x5d, 0x56, 0x3b, 0x69, 0x9d, 0x9c, 0xf6, 0xf8, 0xd1, 0x46, 0xa5, 0x98, 0x37, - 0xa0, 0x20, 0x3a, 0x32, 0x95, 0x30, 0xd4, 0xaf, 0x96, 0xf7, 0xd9, 0x96, 0x9e, 0xe7, 0x91, 0x8e, 0xa5, 0x55, 0xc5, - 0x01, 0x54, 0xfd, 0xd7, 0xc4, 0x00, 0xd1, 0x7f, 0x0d, 0xcb, 0x48, 0xbd, 0xcb, 0x02, 0x44, 0xed, 0xf7, 0x3c, 0x16, - 0x0d, 0x76, 0x18, 0xdb, 0x7c, 0x0d, 0x75, 0x9b, 0x10, 0x3d, 0x0f, 0x4f, 0x5c, 0xae, 0x0a, 0x73, 0x27, 0x08, 0x35, - 0x15, 0xe4, 0x0e, 0x5d, 0xae, 0x0c, 0x73, 0x87, 0x08, 0x35, 0x25, 0xe4, 0xd2, 0x94, 0x27, 0x14, 0x72, 0x74, 0x42, - 0x9b, 0x06, 0x92, 0xd5, 0xa2, 0x3c, 0x67, 0x7e, 0xd8, 0x7c, 0x0c, 0xcb, 0x63, 0x08, 0x8a, 0x13, 0xa4, 0x05, 0xbc, - 0xb0, 0x52, 0x6a, 0x73, 0x5a, 0xb8, 0x54, 0xe3, 0x40, 0x46, 0x03, 0xfe, 0x39, 0x64, 0xe6, 0xd9, 0x8d, 0x56, 0xef, - 0xf8, 0xac, 0x95, 0xb6, 0xc1, 0x55, 0x1c, 0x64, 0x6d, 0x61, 0x65, 0x6d, 0xe1, 0x65, 0x6d, 0xe1, 0x65, 0x6d, 0x10, - 0xe0, 0x83, 0xbe, 0xff, 0x96, 0x35, 0xf3, 0x1b, 0x5e, 0xda, 0xf2, 0x58, 0x63, 0x8d, 0x58, 0xaf, 0x56, 0xcb, 0x35, - 0x58, 0x5a, 0x55, 0x2a, 0x77, 0x55, 0xa9, 0x3f, 0x97, 0x45, 0xda, 0xc2, 0x93, 0x14, 0xb4, 0xdc, 0x2d, 0x4c, 0xcd, - 0xe6, 0xf6, 0x54, 0x61, 0x33, 0x8a, 0x4f, 0xcf, 0xab, 0x93, 0x2f, 0xc9, 0xb1, 0xd1, 0x1e, 0x2f, 0x8b, 0x94, 0x5b, - 0x9a, 0xc1, 0x2d, 0xcd, 0xe0, 0x96, 0x66, 0x40, 0x23, 0xb8, 0x2c, 0x6c, 0xca, 0x26, 0x94, 0xc0, 0x95, 0x40, 0xff, - 0x78, 0x00, 0x41, 0x0c, 0x63, 0x4d, 0xcc, 0xa8, 0x37, 0x3a, 0x6f, 0x43, 0xd0, 0x36, 0x5b, 0x52, 0x27, 0xd4, 0xf8, - 0xae, 0x97, 0x63, 0x7e, 0x55, 0x43, 0xfb, 0x04, 0x5e, 0xd4, 0x79, 0xa8, 0xe3, 0x16, 0x98, 0xae, 0x44, 0x45, 0xd4, - 0x33, 0x64, 0x21, 0x35, 0x3a, 0x1b, 0x67, 0x92, 0xfe, 0x65, 0xc3, 0x13, 0xd8, 0x52, 0x82, 0xf0, 0x1d, 0x89, 0x2f, - 0xac, 0x0a, 0x4d, 0x50, 0x5a, 0xdc, 0x3a, 0x73, 0x39, 0x7b, 0x24, 0x74, 0xc1, 0x6c, 0xde, 0xc7, 0xbc, 0xea, 0x09, - 0x22, 0x95, 0x71, 0xda, 0x24, 0x55, 0xd4, 0x66, 0x70, 0x62, 0x26, 0xb7, 0xd4, 0xb8, 0xf4, 0xbc, 0xb0, 0x7f, 0x5e, - 0xd1, 0xc0, 0xe7, 0xb1, 0x98, 0x0c, 0xbd, 0xab, 0xf0, 0xb5, 0x89, 0x6d, 0x44, 0xf6, 0xf7, 0xad, 0x45, 0xbb, 0xf9, - 0xda, 0x34, 0x69, 0x37, 0x89, 0x26, 0x1b, 0x76, 0xa8, 0x5f, 0xa3, 0xbf, 0xbd, 0xc7, 0x5e, 0x31, 0x19, 0xa2, 0x80, - 0x66, 0x1b, 0xb0, 0xca, 0x0a, 0x58, 0xca, 0xd5, 0x2b, 0x1d, 0x39, 0xa1, 0x77, 0x33, 0xe6, 0x75, 0x31, 0x19, 0xee, - 0x7c, 0x7a, 0xc5, 0xf6, 0xd8, 0x7b, 0x4b, 0x83, 0x1e, 0xbc, 0x6a, 0x7b, 0xca, 0x6e, 0xbf, 0x57, 0xe7, 0x66, 0x67, - 0x1d, 0x95, 0x7f, 0xaf, 0xce, 0xd3, 0x5d, 0x75, 0x66, 0xfc, 0x36, 0xf6, 0x7b, 0x47, 0x07, 0x6a, 0x6c, 0x63, 0x26, - 0x35, 0x19, 0x42, 0xac, 0x7c, 0xf8, 0x6b, 0x23, 0xda, 0x74, 0x3d, 0x09, 0x87, 0x55, 0x90, 0xbd, 0xe4, 0x34, 0x65, - 0x98, 0x92, 0xce, 0x61, 0x61, 0x62, 0xda, 0x88, 0x84, 0x36, 0x55, 0x42, 0x71, 0x4e, 0xe2, 0x98, 0x1e, 0x66, 0x10, - 0x99, 0xa7, 0xdd, 0xa3, 0x69, 0x4c, 0x1b, 0x19, 0x3a, 0x8a, 0xdb, 0x0d, 0x7a, 0x98, 0x21, 0xd4, 0x68, 0x83, 0xce, - 0x54, 0x92, 0x76, 0x33, 0x87, 0x58, 0x9d, 0x86, 0x14, 0xe7, 0x87, 0x22, 0x29, 0x1a, 0xf2, 0x50, 0x25, 0x45, 0x23, - 0x39, 0xc5, 0x22, 0x99, 0x94, 0xc9, 0x13, 0x93, 0x3c, 0xb1, 0xc9, 0xc3, 0x32, 0x79, 0x68, 0x92, 0x87, 0x36, 0x99, - 0x92, 0xe2, 0x50, 0x24, 0xb4, 0x11, 0xb7, 0x9b, 0x05, 0x3a, 0x84, 0x11, 0xf8, 0xd1, 0x13, 0x11, 0x86, 0x48, 0x5f, - 0x1b, 0x1b, 0xa3, 0xb9, 0xcc, 0x5d, 0xd0, 0xd2, 0x0a, 0x48, 0xa5, 0xe3, 0x17, 0xd4, 0x79, 0x16, 0x80, 0x09, 0x6b, - 0xfb, 0xc7, 0x87, 0xe4, 0x5b, 0x67, 0xb9, 0x14, 0x81, 0x63, 0x1b, 0xd8, 0xe2, 0x7f, 0x71, 0xee, 0x3c, 0x00, 0xd5, - 0x35, 0xcd, 0xe7, 0x53, 0xba, 0xe5, 0x3d, 0x5c, 0x4c, 0x86, 0x6e, 0x67, 0x95, 0xcd, 0x30, 0x5a, 0xd8, 0x50, 0xd7, - 0x75, 0x3f, 0x4f, 0x00, 0xb5, 0xf7, 0x2d, 0x4d, 0xa8, 0x51, 0x92, 0xdb, 0x1a, 0x93, 0x82, 0xdd, 0xa9, 0x8c, 0xe6, - 0x2c, 0xae, 0x0e, 0xe0, 0x6a, 0x98, 0x8c, 0xbc, 0x00, 0x8f, 0x80, 0xe2, 0x30, 0x39, 0x6e, 0xe8, 0x64, 0x72, 0x98, - 0x9c, 0x3e, 0x68, 0xe8, 0x64, 0x78, 0x98, 0xb4, 0xdb, 0x15, 0xce, 0x26, 0x05, 0xd1, 0xc9, 0x84, 0x68, 0xd0, 0x18, - 0xda, 0x46, 0xe5, 0x9c, 0x82, 0x89, 0xdb, 0xbf, 0x31, 0x8c, 0x86, 0x1b, 0x86, 0x60, 0x13, 0x1b, 0xf5, 0x73, 0x6b, - 0x0c, 0x61, 0x37, 0x9d, 0xd3, 0xd3, 0xa6, 0x4e, 0x0a, 0xac, 0xed, 0x4a, 0x36, 0x75, 0x32, 0xc1, 0xda, 0x2e, 0x5f, - 0x53, 0x27, 0x43, 0xdb, 0x94, 0xd1, 0x01, 0x32, 0x11, 0x00, 0xeb, 0x39, 0x0b, 0x20, 0xdf, 0xf1, 0x4e, 0x3a, 0x6b, - 0xd0, 0x1a, 0x7e, 0xaf, 0x5c, 0xd3, 0x17, 0x54, 0x54, 0x83, 0xa9, 0x13, 0xfb, 0x56, 0xd1, 0x76, 0xd5, 0x24, 0xfb, - 0xd7, 0x65, 0xcb, 0x66, 0x0b, 0xa9, 0xeb, 0x05, 0x1f, 0xd6, 0x30, 0xc4, 0x95, 0x72, 0x07, 0xf7, 0x3f, 0x94, 0xc4, - 0x10, 0xdb, 0xcf, 0x9c, 0x42, 0x9c, 0x78, 0x3d, 0x32, 0x24, 0xf1, 0x46, 0x63, 0x8d, 0xe2, 0xe0, 0xbc, 0x7d, 0x1a, - 0x52, 0xd5, 0xad, 0x80, 0x7f, 0x84, 0x44, 0x0b, 0x61, 0x4d, 0x42, 0x47, 0x51, 0xc0, 0x82, 0x38, 0xed, 0x6e, 0xed, - 0x80, 0x38, 0x38, 0xd8, 0x3c, 0x2f, 0xfc, 0xd3, 0x0b, 0x5b, 0xcf, 0x2d, 0x54, 0xf6, 0x84, 0xfe, 0x41, 0x28, 0x6b, - 0x69, 0xcc, 0x03, 0x44, 0xf1, 0xa1, 0xb7, 0xee, 0x1b, 0x0a, 0xdf, 0xaf, 0xe2, 0x0e, 0xba, 0x9c, 0xe6, 0x99, 0xc9, - 0x30, 0x7d, 0x0d, 0x82, 0xb1, 0xbd, 0x09, 0x27, 0x54, 0xda, 0x4a, 0xfe, 0xcb, 0x8e, 0x83, 0x4e, 0xdc, 0x83, 0x35, - 0x61, 0xa3, 0x9f, 0x43, 0xcb, 0xe4, 0x0a, 0x36, 0xce, 0x27, 0x7d, 0xb5, 0xaa, 0x3d, 0x4f, 0x64, 0x1f, 0xc1, 0x41, - 0x07, 0x07, 0x5c, 0x3d, 0x03, 0x63, 0x6a, 0x16, 0x37, 0xc2, 0xc3, 0xf7, 0xef, 0xda, 0x69, 0xfd, 0xd9, 0x9c, 0xab, - 0x69, 0x70, 0xd0, 0x3d, 0xac, 0xe5, 0xef, 0x5c, 0x89, 0x9e, 0x4e, 0xb9, 0x5b, 0xeb, 0xcf, 0x95, 0xa9, 0xfa, 0xd6, - 0x43, 0x59, 0x07, 0x07, 0xbc, 0x0a, 0x57, 0x15, 0xfd, 0x10, 0xa1, 0x9e, 0x91, 0x41, 0x9e, 0xe5, 0x92, 0xc2, 0x8d, - 0x28, 0x5c, 0x31, 0xa4, 0x0d, 0x7e, 0xa4, 0xf1, 0x1f, 0xf2, 0xff, 0x53, 0x23, 0x87, 0x3a, 0x6d, 0xf0, 0x40, 0x00, - 0x0b, 0x59, 0xa1, 0x2a, 0x50, 0xa4, 0x81, 0x74, 0x68, 0x79, 0x8e, 0xca, 0xc3, 0x9c, 0xce, 0xe7, 0xf9, 0x9d, 0x79, - 0x2b, 0x2c, 0xe0, 0xa8, 0xaa, 0x8b, 0x26, 0x17, 0xa5, 0x0f, 0x17, 0xc0, 0xd3, 0x03, 0xee, 0x21, 0xe3, 0x65, 0x5b, - 0x5e, 0x6e, 0x0b, 0x04, 0x92, 0x99, 0x22, 0xb2, 0xd9, 0xee, 0xaa, 0x4b, 0x90, 0xcb, 0x9a, 0x4d, 0xa4, 0x5d, 0xf0, - 0x72, 0xcc, 0x41, 0x26, 0x53, 0xd6, 0x93, 0x76, 0xcf, 0x16, 0x04, 0xc9, 0x4d, 0x1a, 0x91, 0x6d, 0x77, 0x29, 0x3e, - 0x8e, 0x01, 0x8d, 0x90, 0x15, 0xf8, 0x42, 0x61, 0x91, 0x03, 0xd7, 0x59, 0xf8, 0x8e, 0xbf, 0xd1, 0x52, 0xd1, 0x57, - 0x83, 0x01, 0x2e, 0xcc, 0xf3, 0x18, 0xe5, 0x7c, 0x0a, 0x15, 0x3c, 0xb7, 0x14, 0x88, 0x28, 0x7c, 0xb5, 0xda, 0x87, - 0xd7, 0x8c, 0x5c, 0x9b, 0xe0, 0x7a, 0xeb, 0x7e, 0x56, 0x2f, 0x97, 0xc0, 0x38, 0x18, 0x69, 0x99, 0x8b, 0x42, 0x27, - 0x6f, 0xb2, 0x0b, 0xd1, 0x6d, 0x34, 0x98, 0x09, 0x34, 0x45, 0x20, 0xaa, 0x1c, 0xf8, 0x45, 0xc2, 0x1f, 0x1b, 0x3b, - 0x4a, 0x31, 0x1b, 0x81, 0x0f, 0x42, 0x83, 0xd7, 0x12, 0x56, 0x2b, 0x65, 0x23, 0xbc, 0x98, 0x1c, 0x1b, 0xeb, 0xa5, - 0xec, 0xa7, 0x0c, 0x25, 0x5b, 0x99, 0x71, 0x70, 0xb7, 0xd5, 0xdf, 0x56, 0xfb, 0x79, 0x8f, 0xdb, 0x6b, 0x3c, 0x6e, - 0xe2, 0x26, 0x18, 0x40, 0x2d, 0x37, 0x36, 0xb8, 0xb5, 0xf3, 0x8f, 0xad, 0x51, 0x32, 0xdb, 0x84, 0xa0, 0x28, 0xe3, - 0x04, 0xd8, 0x9b, 0x5b, 0x1f, 0x37, 0x51, 0x99, 0x39, 0x29, 0xa4, 0xfb, 0x20, 0x47, 0x0f, 0x08, 0x74, 0x6e, 0x7f, - 0x56, 0x74, 0xa1, 0x92, 0x89, 0xcb, 0x31, 0xfe, 0x12, 0xdc, 0xe6, 0xf5, 0xa3, 0xeb, 0x6b, 0xb3, 0xc9, 0xaf, 0xaf, - 0x23, 0x1c, 0x1a, 0xd7, 0x47, 0x01, 0x2f, 0x18, 0x0d, 0xca, 0xd0, 0x5a, 0x66, 0xe3, 0x37, 0xdb, 0x55, 0x63, 0x8f, - 0x68, 0x85, 0x77, 0xb0, 0x3c, 0xa6, 0xf1, 0x2d, 0x67, 0xd4, 0x3e, 0x07, 0x78, 0xb3, 0x3e, 0x1f, 0x74, 0xdf, 0xc4, - 0x0a, 0x1d, 0x1c, 0xbc, 0x89, 0x25, 0xea, 0x5d, 0x31, 0x73, 0xe7, 0x06, 0xde, 0xe8, 0x7d, 0x6e, 0x86, 0x2f, 0x03, - 0x04, 0xb8, 0x62, 0x9b, 0x92, 0xcd, 0x5b, 0x13, 0xfb, 0x23, 0x85, 0xd8, 0xe2, 0x10, 0xe1, 0xd8, 0x81, 0x04, 0x7a, - 0x7d, 0x13, 0x42, 0xbb, 0xcb, 0x08, 0x03, 0x16, 0xbe, 0xf4, 0x15, 0x64, 0xc9, 0x8c, 0x15, 0x13, 0x56, 0xac, 0x56, - 0x8f, 0xa8, 0xf5, 0xff, 0xdb, 0x08, 0x55, 0xa9, 0xba, 0x8d, 0x06, 0x35, 0xe3, 0x07, 0xf1, 0x81, 0x0e, 0xf0, 0xfe, - 0x9b, 0xb8, 0x40, 0x08, 0x2c, 0x8c, 0xb8, 0x58, 0x78, 0x5f, 0xb7, 0xac, 0xb6, 0x2e, 0x05, 0x2a, 0x1b, 0xc9, 0x49, - 0x0b, 0x4f, 0x49, 0x56, 0xae, 0xd1, 0xc5, 0xb4, 0xdb, 0x68, 0xe4, 0x48, 0xc6, 0x59, 0x3f, 0x1f, 0x60, 0x8e, 0x0b, - 0xb8, 0x4c, 0xdd, 0x5e, 0x87, 0x39, 0xab, 0x51, 0x2e, 0x37, 0xdf, 0xa5, 0x1d, 0x6b, 0xfa, 0x9e, 0xae, 0x03, 0x60, - 0xbc, 0xa7, 0x01, 0x91, 0xd8, 0x05, 0x64, 0x61, 0x81, 0xac, 0x3c, 0x90, 0x85, 0x01, 0xb2, 0x42, 0xbd, 0x39, 0x04, - 0x6d, 0x52, 0x28, 0xdd, 0xa2, 0xe8, 0xf5, 0xf0, 0xa2, 0xce, 0x75, 0x05, 0x73, 0x13, 0xe1, 0xc2, 0x2d, 0x07, 0xb8, - 0xb1, 0x38, 0x6f, 0x48, 0x45, 0x96, 0x51, 0x64, 0x22, 0xed, 0xe2, 0x5b, 0xf3, 0x27, 0xb9, 0xc5, 0x77, 0xf6, 0xc7, - 0x5d, 0xa0, 0x4c, 0x7a, 0x5e, 0xd3, 0x36, 0x70, 0x17, 0x97, 0x2e, 0x4a, 0x22, 0x40, 0x6b, 0x17, 0x64, 0x51, 0xd4, - 0xdf, 0x9d, 0x53, 0x36, 0x1c, 0x86, 0x68, 0x10, 0x85, 0x45, 0x40, 0x3a, 0xff, 0xfa, 0x2b, 0x42, 0x3d, 0x01, 0xd1, - 0x8c, 0xdc, 0xc9, 0xd6, 0x6c, 0xa3, 0x46, 0x94, 0x44, 0x69, 0xec, 0x83, 0x65, 0xc0, 0xce, 0x88, 0xa2, 0xe0, 0xcd, - 0x99, 0x72, 0x18, 0x1f, 0x6a, 0xc3, 0x30, 0x83, 0xaa, 0xc2, 0x7f, 0x5c, 0x2e, 0x37, 0x83, 0x2d, 0x19, 0xa8, 0x0a, - 0x13, 0xe9, 0x06, 0xd9, 0x87, 0xd8, 0x18, 0x61, 0x07, 0x07, 0xac, 0x2f, 0x06, 0xc1, 0xcb, 0x6a, 0xd5, 0x75, 0xb8, - 0x0e, 0x17, 0x2e, 0xa6, 0x10, 0xed, 0x7e, 0xb5, 0xb2, 0x7f, 0xc9, 0x07, 0x23, 0xcd, 0xc0, 0x13, 0x79, 0xc1, 0x19, - 0x2b, 0x76, 0xcb, 0x62, 0x89, 0x96, 0xbf, 0x83, 0x65, 0x9f, 0x8b, 0x5d, 0xc8, 0xdd, 0x54, 0xdb, 0x1e, 0xea, 0x73, - 0xa3, 0x51, 0x08, 0x22, 0x07, 0x57, 0x47, 0x1a, 0x9e, 0xeb, 0x30, 0xaf, 0x16, 0x01, 0x38, 0x53, 0x65, 0x20, 0x57, - 0x38, 0x52, 0x12, 0xb0, 0xf4, 0x36, 0x74, 0x12, 0x7e, 0xd4, 0xa9, 0xa4, 0x63, 0x21, 0x01, 0x0a, 0x1c, 0x99, 0xcb, - 0x79, 0x13, 0xa8, 0x9f, 0xa1, 0x1d, 0x44, 0x2e, 0x30, 0xa1, 0xa9, 0xcb, 0x96, 0x2e, 0xa2, 0x56, 0x34, 0x93, 0x0b, - 0xc5, 0x16, 0x73, 0x38, 0xdf, 0xcb, 0xb4, 0x2c, 0xe7, 0xd9, 0x97, 0x7a, 0x0a, 0x18, 0x44, 0xde, 0xea, 0x19, 0x13, - 0x8b, 0xc8, 0xcd, 0xf3, 0x95, 0x15, 0xf7, 0xdf, 0xbc, 0xc0, 0xef, 0x49, 0xe7, 0xf0, 0x15, 0xfe, 0x48, 0xc9, 0xfb, - 0xc6, 0x2b, 0x3c, 0xe1, 0xc4, 0xf2, 0x06, 0xc9, 0x9b, 0xd7, 0x57, 0x2f, 0xde, 0xbd, 0x78, 0xff, 0xf4, 0xfa, 0xc5, - 0xab, 0x67, 0x2f, 0x5e, 0xbd, 0x78, 0xf7, 0x11, 0xff, 0x4d, 0xc9, 0xab, 0xa3, 0xf6, 0x79, 0x0b, 0x7f, 0x20, 0xaf, - 0x8e, 0x3a, 0xf8, 0x56, 0x93, 0x57, 0x47, 0x27, 0x38, 0x57, 0xe4, 0xd5, 0x61, 0xe7, 0xe8, 0x18, 0x2f, 0xb4, 0x6d, - 0x32, 0x97, 0x93, 0x76, 0x0b, 0xff, 0xed, 0xbe, 0x40, 0xbc, 0xaf, 0x66, 0x31, 0x61, 0x1b, 0xc6, 0x0f, 0xa6, 0x0c, - 0x1d, 0x2a, 0x63, 0x88, 0x72, 0x11, 0xa0, 0xd3, 0x54, 0x85, 0xe8, 0x64, 0x43, 0x49, 0x83, 0x0d, 0x23, 0xa0, 0x15, - 0x27, 0xae, 0x1d, 0x7e, 0xd4, 0x66, 0xc7, 0x40, 0x9f, 0x78, 0x29, 0x1c, 0x97, 0x2a, 0x9c, 0xb6, 0xd3, 0x62, 0x8c, - 0x73, 0x29, 0x8b, 0x78, 0x01, 0x8c, 0x80, 0xd1, 0x5a, 0xf0, 0xa3, 0x32, 0x66, 0x95, 0xb8, 0x20, 0xed, 0x5e, 0x3b, - 0x15, 0x17, 0xa4, 0xd3, 0xeb, 0xc0, 0x9f, 0xd3, 0xde, 0x69, 0xda, 0x6e, 0xa1, 0xc3, 0x60, 0x1c, 0x7f, 0xd4, 0xd0, - 0xba, 0x3f, 0xc0, 0xae, 0x0b, 0xf5, 0x77, 0xa1, 0xbd, 0x4a, 0x4f, 0x38, 0x75, 0x6c, 0xbb, 0x2b, 0x2e, 0x98, 0xd1, - 0xc3, 0xf2, 0x1f, 0x00, 0xb5, 0x8d, 0x5b, 0x4d, 0xb9, 0x71, 0xdc, 0x2f, 0x7e, 0x24, 0x50, 0x2d, 0x30, 0x4e, 0xcc, - 0x56, 0x2d, 0x04, 0x4c, 0xa3, 0xc9, 0x06, 0x73, 0xa0, 0x44, 0xc9, 0x42, 0xfb, 0xe0, 0xfe, 0xaa, 0x29, 0x51, 0x32, - 0x97, 0xf3, 0xb8, 0xa6, 0x6a, 0xf8, 0x35, 0x30, 0x73, 0xdc, 0xe7, 0xea, 0x15, 0x7d, 0x15, 0xd7, 0x78, 0x9e, 0x90, - 0xb5, 0x0b, 0xb7, 0xc5, 0x2f, 0xce, 0x8a, 0xa2, 0x06, 0xae, 0x12, 0xb0, 0x7e, 0x54, 0x4d, 0x7d, 0x01, 0xaf, 0x18, - 0xb2, 0x86, 0xbe, 0x24, 0x01, 0xf5, 0xfc, 0xa9, 0x34, 0xe3, 0x2a, 0x95, 0xd1, 0x5e, 0x11, 0x6d, 0xcc, 0x82, 0xbc, - 0x22, 0xfa, 0x42, 0x19, 0x20, 0x48, 0xc2, 0xfb, 0x62, 0x00, 0x07, 0xbe, 0x1d, 0xa0, 0x34, 0x74, 0x0e, 0xd4, 0x4a, - 0x95, 0x99, 0x90, 0xf9, 0x34, 0x21, 0x1a, 0x40, 0xf3, 0x54, 0xa9, 0xa0, 0xcc, 0x27, 0x96, 0x28, 0x18, 0xfa, 0x9f, - 0xe1, 0x06, 0x38, 0x8c, 0x0d, 0x2a, 0x06, 0xd9, 0xf7, 0x44, 0x3d, 0xbf, 0x7d, 0xde, 0x3a, 0x7a, 0x15, 0xe4, 0x8f, - 0x94, 0xb7, 0xf7, 0xf8, 0x1c, 0x50, 0x72, 0x1b, 0x54, 0xac, 0x8d, 0x7d, 0x3c, 0xb8, 0x6e, 0x08, 0x90, 0x43, 0x8d, - 0x8e, 0xcc, 0x83, 0x8e, 0x5d, 0xa4, 0x0f, 0x49, 0xbb, 0x05, 0x41, 0xdc, 0x76, 0x50, 0xbe, 0x9f, 0x36, 0x60, 0xaa, - 0x93, 0xdb, 0x26, 0xd0, 0x6a, 0x78, 0xe3, 0xe9, 0xae, 0xc9, 0x93, 0x3b, 0xac, 0x02, 0x9c, 0x61, 0x87, 0xac, 0x21, - 0x0e, 0x05, 0x72, 0xc1, 0x6f, 0xed, 0x06, 0xd0, 0x54, 0x74, 0xec, 0x5b, 0x83, 0xde, 0x38, 0xea, 0xa2, 0x99, 0x9c, - 0x1e, 0xbe, 0x3a, 0x38, 0x88, 0x65, 0x83, 0xbc, 0x47, 0x78, 0x49, 0xc1, 0x66, 0x1b, 0x7c, 0xef, 0xb8, 0x65, 0xe2, - 0x53, 0x15, 0x50, 0xc7, 0x85, 0xaa, 0x1d, 0x6b, 0x55, 0x67, 0xe5, 0x6e, 0xf0, 0x63, 0xea, 0xa0, 0x46, 0x90, 0x66, - 0x47, 0xd7, 0x09, 0xa1, 0xfc, 0x5b, 0xcd, 0x51, 0x0e, 0xb6, 0x65, 0xe3, 0x23, 0x45, 0x3f, 0xbc, 0x6f, 0xbe, 0x0a, - 0xca, 0xd4, 0x4c, 0x93, 0xde, 0x37, 0xde, 0xa3, 0x1f, 0xde, 0x07, 0xae, 0x8e, 0xbc, 0x62, 0x4f, 0x3c, 0x37, 0xf2, - 0x9b, 0xe5, 0x4a, 0x7f, 0x03, 0xc9, 0xbe, 0x20, 0xbf, 0x01, 0x96, 0x53, 0xf2, 0x5b, 0x2c, 0x9b, 0x10, 0x02, 0x92, - 0xfc, 0x16, 0x17, 0xf0, 0x23, 0x27, 0xbf, 0xc5, 0x80, 0xed, 0x78, 0x6a, 0x7e, 0x14, 0x25, 0x30, 0xc0, 0xbd, 0x4e, - 0x5a, 0x2f, 0xbb, 0x62, 0xb5, 0x12, 0x07, 0x07, 0xd2, 0xfe, 0xa2, 0x97, 0xd9, 0xc1, 0x41, 0x7e, 0x31, 0x0d, 0x6c, - 0x6f, 0xf5, 0x2e, 0xfa, 0x62, 0x10, 0x0a, 0x07, 0xa6, 0x69, 0xbc, 0x86, 0x57, 0x35, 0xca, 0x0a, 0x0d, 0x34, 0x8f, - 0x3b, 0xf7, 0xcf, 0xce, 0x31, 0xfc, 0x7b, 0x3f, 0x28, 0xf8, 0x73, 0xc9, 0x77, 0x91, 0x36, 0x6b, 0x9e, 0x55, 0x75, - 0x2e, 0x03, 0x7c, 0xc6, 0x0c, 0x35, 0xc5, 0xc1, 0x01, 0xbf, 0x08, 0x70, 0x19, 0x33, 0xd4, 0x08, 0x2c, 0xf6, 0x1e, - 0x96, 0xf6, 0x64, 0x86, 0x6b, 0x82, 0xc7, 0x7d, 0x79, 0xbf, 0x18, 0x5c, 0x68, 0x47, 0x4d, 0xc2, 0x10, 0xe0, 0x8a, - 0xb4, 0xdc, 0x26, 0xeb, 0x8a, 0xa6, 0xba, 0x6c, 0x77, 0x91, 0x24, 0xaa, 0x21, 0x2e, 0x2f, 0xdb, 0x18, 0x54, 0xf2, - 0x3d, 0x45, 0x64, 0x2a, 0x88, 0x77, 0x53, 0x5c, 0xe6, 0x32, 0x55, 0x78, 0xca, 0x53, 0xe1, 0xe5, 0xec, 0xd7, 0xde, - 0x7a, 0xda, 0x38, 0x8e, 0x9a, 0x9e, 0x19, 0x16, 0x3d, 0x55, 0x3a, 0x3c, 0xc2, 0x26, 0x55, 0x03, 0x78, 0x3b, 0xb1, - 0xc4, 0x3c, 0x66, 0xbd, 0xfc, 0x18, 0xc4, 0xa6, 0x56, 0x8d, 0x36, 0x64, 0xc2, 0xe7, 0x3a, 0x55, 0x30, 0x50, 0x53, - 0xf8, 0x02, 0xc8, 0x54, 0x56, 0x19, 0x66, 0xfb, 0x86, 0xa1, 0x80, 0x80, 0x02, 0x97, 0x84, 0x05, 0x12, 0x3c, 0xdc, - 0x7e, 0x04, 0x84, 0xa3, 0x4e, 0x2e, 0xec, 0xe4, 0x2e, 0x14, 0x74, 0x27, 0x06, 0x17, 0xba, 0x8b, 0x44, 0xa3, 0xe1, - 0xb8, 0xed, 0x4b, 0x61, 0x06, 0xd1, 0x6c, 0x0f, 0x2e, 0x59, 0x17, 0xa9, 0x66, 0xb3, 0x34, 0x80, 0xbc, 0x6c, 0xad, - 0x56, 0xea, 0xc2, 0x37, 0xd2, 0xf3, 0xe7, 0xb8, 0xe1, 0xbb, 0xbc, 0xe0, 0xf9, 0x9b, 0x24, 0xfd, 0x08, 0xa8, 0x2a, - 0xf0, 0xd9, 0x72, 0x1e, 0xe1, 0xc8, 0x3c, 0xab, 0x07, 0x7f, 0xcd, 0x73, 0x68, 0x11, 0x8e, 0xdc, 0x4b, 0x7b, 0xd1, - 0xa0, 0x1a, 0x2c, 0xcf, 0xca, 0x20, 0xf1, 0x3c, 0xb9, 0x06, 0xc6, 0x41, 0x7f, 0x56, 0x68, 0x59, 0xfd, 0x4e, 0x72, - 0x17, 0x2e, 0x45, 0xf9, 0xc7, 0xdf, 0xdc, 0xa8, 0xd6, 0xbb, 0x1d, 0x54, 0x39, 0x8e, 0x7c, 0x55, 0x78, 0x44, 0xe1, - 0x3b, 0xaf, 0x4f, 0xb6, 0xdd, 0xa3, 0xe7, 0xcb, 0xb2, 0x07, 0xe0, 0xbc, 0xd7, 0x6b, 0x84, 0x7f, 0x93, 0x3b, 0x5f, - 0x40, 0x8e, 0xae, 0xa5, 0x78, 0x42, 0x35, 0x8d, 0x1a, 0x6f, 0x8c, 0xe1, 0x9b, 0x95, 0xb3, 0xba, 0xdf, 0x1a, 0x07, - 0xfb, 0xb7, 0xba, 0x87, 0x00, 0x16, 0xb5, 0xc7, 0x9a, 0xac, 0xec, 0x6b, 0xc2, 0x96, 0xc8, 0xc0, 0xf4, 0x6d, 0x07, - 0x3c, 0xfc, 0x18, 0x29, 0xb8, 0x55, 0x5b, 0x3e, 0x89, 0x42, 0x64, 0xd8, 0x9a, 0x33, 0x37, 0xa4, 0xd8, 0x3e, 0x8c, - 0xe3, 0xef, 0x1a, 0x85, 0x5c, 0xf7, 0x58, 0xd5, 0x89, 0x69, 0xd5, 0x8d, 0x91, 0x3a, 0xd8, 0x26, 0x0b, 0xce, 0xaa, - 0xde, 0x8d, 0x84, 0x52, 0xbd, 0x6b, 0x67, 0xde, 0x26, 0x6d, 0xb6, 0xcd, 0x63, 0xcf, 0xf6, 0xf5, 0x3b, 0x05, 0x86, - 0xbc, 0xfb, 0x65, 0xd0, 0xae, 0x4b, 0x38, 0x76, 0xe3, 0x00, 0xb2, 0x92, 0x5c, 0x2e, 0xdd, 0xcb, 0x74, 0xbc, 0x2f, - 0x07, 0xeb, 0xf2, 0x9d, 0xba, 0x00, 0x0f, 0xaa, 0x91, 0x8a, 0x2c, 0xe4, 0x0c, 0xfc, 0x23, 0x8f, 0x35, 0xfd, 0x10, - 0xff, 0x07, 0x0e, 0xf8, 0x0a, 0x49, 0x53, 0xab, 0x7e, 0x82, 0xf7, 0xa3, 0x40, 0xe1, 0x6d, 0xeb, 0xfe, 0x29, 0x43, - 0x47, 0xdd, 0xba, 0x4e, 0xc5, 0xfa, 0xc2, 0xd6, 0x15, 0x2b, 0x65, 0xe1, 0x80, 0x6a, 0xc5, 0x68, 0x9d, 0x3a, 0xbf, - 0x59, 0xf7, 0xe8, 0xd4, 0x43, 0x01, 0xbe, 0x31, 0x5c, 0x8a, 0x67, 0x05, 0x44, 0x11, 0x0b, 0xf5, 0x69, 0x3f, 0xcb, - 0xf0, 0x55, 0xe5, 0x3e, 0xdc, 0x13, 0x96, 0x3c, 0x67, 0xf9, 0x12, 0x38, 0x2c, 0x90, 0x02, 0x0a, 0xa5, 0xb0, 0x58, - 0xad, 0x62, 0x01, 0x81, 0x24, 0xfe, 0x74, 0xa1, 0x85, 0xdd, 0x1b, 0x22, 0x46, 0x7f, 0x07, 0x75, 0xb1, 0x57, 0x8f, - 0x18, 0x13, 0x56, 0x14, 0x5e, 0x3a, 0xa9, 0x2c, 0xe8, 0x6b, 0x57, 0x1f, 0xa2, 0x9a, 0x72, 0x2f, 0x36, 0xfa, 0xde, - 0x77, 0x7c, 0xc6, 0xe4, 0x02, 0x1e, 0x6f, 0xc2, 0x8c, 0x28, 0xa6, 0xfd, 0x37, 0x50, 0x10, 0x78, 0x01, 0x88, 0x87, - 0xf8, 0x08, 0x7c, 0x95, 0xa7, 0x75, 0x32, 0xf3, 0x4f, 0x82, 0x44, 0x26, 0x64, 0x67, 0xd4, 0x8b, 0xc0, 0x8b, 0x08, - 0x44, 0x28, 0x42, 0x22, 0x26, 0x46, 0x51, 0x2f, 0x32, 0x2e, 0x59, 0x11, 0x58, 0x8d, 0x81, 0x92, 0x3b, 0xc2, 0x73, - 0x55, 0x11, 0xb1, 0xb0, 0xa6, 0x0e, 0x2a, 0xb1, 0xd4, 0x98, 0x69, 0x1f, 0x75, 0x2a, 0x10, 0x16, 0xd9, 0xa6, 0xa0, - 0xac, 0x37, 0xd4, 0x05, 0x58, 0x12, 0x63, 0x7a, 0xcb, 0x93, 0x6b, 0xe0, 0xe6, 0xd8, 0xc8, 0x15, 0x5d, 0xf2, 0x2b, - 0x50, 0x4f, 0xa7, 0x05, 0xbe, 0x36, 0x0c, 0xdb, 0x28, 0xa5, 0x6b, 0xc2, 0x71, 0x46, 0x8a, 0x84, 0xde, 0x42, 0x6c, - 0x8d, 0x19, 0x17, 0x69, 0x8e, 0x67, 0xf4, 0x36, 0x9d, 0xe2, 0x19, 0x17, 0x4f, 0xec, 0xb2, 0xa7, 0x23, 0x48, 0xf2, - 0x1f, 0x8b, 0x35, 0x31, 0x4f, 0x83, 0xfd, 0xae, 0x58, 0xf1, 0x08, 0x78, 0x15, 0x15, 0xa3, 0xee, 0xc8, 0xd8, 0x94, - 0x73, 0x5d, 0x19, 0xaf, 0xbf, 0xd6, 0x31, 0xc5, 0x19, 0xce, 0x51, 0x92, 0x4b, 0xcc, 0x7a, 0x22, 0x7d, 0x0d, 0x71, - 0xb5, 0x33, 0x6c, 0x9f, 0x15, 0xe3, 0xb7, 0x2c, 0x7f, 0x26, 0x8b, 0xf7, 0x66, 0xcb, 0xe7, 0x08, 0x0a, 0x81, 0x8b, - 0x8a, 0x68, 0xc2, 0xed, 0xde, 0xa2, 0x27, 0xab, 0xa6, 0xe8, 0xad, 0x6d, 0xca, 0x0d, 0x71, 0x0a, 0x01, 0x89, 0x93, - 0x29, 0x6f, 0xb4, 0x31, 0xeb, 0xb5, 0xbe, 0xd3, 0xe8, 0x14, 0x95, 0x25, 0x11, 0x86, 0xb5, 0x6a, 0xaa, 0x54, 0x12, - 0xd1, 0x54, 0x4e, 0xc2, 0x5b, 0x1a, 0x60, 0xa7, 0x0a, 0x67, 0x72, 0x21, 0x74, 0x2a, 0x03, 0xbc, 0xa1, 0xd5, 0xe6, - 0x5a, 0xde, 0x5a, 0x88, 0x69, 0x7c, 0x67, 0x7f, 0x30, 0x7c, 0x6d, 0x54, 0xfc, 0x6f, 0xc1, 0xb0, 0x47, 0xa5, 0x02, - 0xe0, 0x07, 0x86, 0xb3, 0x00, 0x39, 0xcb, 0x4f, 0xde, 0x02, 0xf8, 0x2c, 0x0b, 0x79, 0x07, 0xa9, 0xcc, 0xa4, 0xde, - 0x41, 0x2a, 0x83, 0x54, 0xe3, 0x51, 0xbf, 0x2f, 0x2a, 0x65, 0x51, 0xd8, 0x20, 0x51, 0xb8, 0x54, 0x07, 0x4b, 0x22, - 0x12, 0x68, 0xd7, 0x88, 0x72, 0x33, 0x2e, 0x20, 0xb4, 0x22, 0x34, 0x6e, 0xbf, 0xe9, 0x2d, 0x7c, 0xdf, 0xd9, 0x7c, - 0xe6, 0xf3, 0xef, 0x6c, 0xbe, 0xe9, 0xc8, 0x63, 0x7c, 0xfd, 0xb6, 0xd3, 0x58, 0xc6, 0x4b, 0x87, 0xb5, 0x1f, 0xca, - 0x87, 0x6c, 0x5a, 0xe6, 0xc1, 0x70, 0xd2, 0xc6, 0x93, 0x00, 0x29, 0x9b, 0x15, 0x0f, 0xd7, 0xc1, 0xed, 0xd6, 0x61, - 0xcc, 0x9b, 0xa4, 0x8d, 0xd0, 0xa1, 0x13, 0xae, 0x44, 0x6c, 0x24, 0xa7, 0xc3, 0xf7, 0x47, 0x70, 0xf7, 0x32, 0x53, - 0x1b, 0xbe, 0x52, 0xb6, 0x5a, 0xb3, 0xdd, 0x3a, 0xe4, 0x3b, 0xab, 0x34, 0xda, 0x78, 0xc6, 0xc8, 0x12, 0x3c, 0xd0, - 0x68, 0x61, 0x55, 0x0d, 0xe0, 0xb2, 0xfa, 0x42, 0xfc, 0xb6, 0xa0, 0x23, 0xf3, 0x7d, 0x68, 0x53, 0x5e, 0x2f, 0xb4, - 0x4f, 0x6a, 0x72, 0x18, 0x44, 0x07, 0xb9, 0x92, 0x41, 0x4e, 0xcc, 0x8f, 0x48, 0x72, 0x8a, 0x2e, 0xda, 0xbd, 0xe4, - 0xf4, 0x90, 0x1f, 0xf2, 0x14, 0x78, 0xd8, 0xb8, 0xe9, 0x2b, 0x34, 0xdb, 0xbe, 0xce, 0xe3, 0xc5, 0x90, 0x67, 0xae, - 0xf9, 0xaa, 0x83, 0x32, 0xd5, 0xce, 0x11, 0xb2, 0x00, 0xc5, 0x7c, 0x2f, 0x41, 0x76, 0xbd, 0x9b, 0x43, 0x9e, 0x42, - 0x3f, 0x50, 0xab, 0x63, 0x6b, 0x95, 0x83, 0xfb, 0x6d, 0x01, 0x08, 0xe6, 0x3b, 0xaa, 0xcd, 0xc5, 0xa6, 0x37, 0xe3, - 0xaa, 0xb3, 0x43, 0x5e, 0x8d, 0x30, 0x2c, 0xb3, 0xdd, 0x9f, 0x9f, 0x5a, 0xd5, 0xe5, 0x61, 0x00, 0x91, 0xdf, 0x16, - 0x5c, 0x84, 0x9d, 0x86, 0xdd, 0xba, 0x9c, 0xb0, 0xd3, 0xfa, 0x2c, 0x83, 0x22, 0xdb, 0xbd, 0x6e, 0xcd, 0xb4, 0x3e, - 0xdb, 0x2b, 0x70, 0x24, 0x84, 0x49, 0x99, 0x95, 0xce, 0xe0, 0x0a, 0xfd, 0xf0, 0x03, 0x72, 0xad, 0xbf, 0x5e, 0x68, - 0x9f, 0x5f, 0x22, 0x02, 0x64, 0x57, 0x5d, 0x97, 0xd5, 0xa1, 0x8f, 0xb2, 0x89, 0x57, 0x87, 0x3c, 0x58, 0xb9, 0xa7, - 0xb7, 0x73, 0x99, 0x7a, 0x7c, 0xed, 0xb5, 0xd2, 0x2d, 0xe4, 0x04, 0xe2, 0xe1, 0xba, 0x0b, 0xcb, 0x82, 0x9c, 0xdd, - 0xdc, 0x42, 0xc9, 0x70, 0xe2, 0xbe, 0xf4, 0x07, 0x66, 0xaf, 0x1b, 0xf8, 0x45, 0x72, 0x0a, 0x53, 0xdf, 0xec, 0xe1, - 0xb0, 0x03, 0x7d, 0x18, 0x38, 0x6c, 0x36, 0xe8, 0x33, 0x2b, 0x88, 0x3c, 0xe6, 0x85, 0xc5, 0xb3, 0x4b, 0xd2, 0xee, - 0xf1, 0xd4, 0x6d, 0x26, 0x23, 0x1a, 0xb5, 0x9b, 0x3c, 0x98, 0x19, 0xe0, 0x97, 0x2b, 0x1b, 0x16, 0xf1, 0xeb, 0x14, - 0x40, 0xc9, 0x17, 0xab, 0xd6, 0xa7, 0x82, 0x57, 0xbd, 0xe1, 0x74, 0x33, 0xdd, 0xaf, 0x1b, 0xdc, 0xee, 0x7a, 0x78, - 0xc2, 0x43, 0x34, 0x16, 0xad, 0xfd, 0xc4, 0x27, 0xc0, 0x01, 0x25, 0xad, 0xfb, 0xa7, 0xe0, 0x42, 0x59, 0xc2, 0x72, - 0xbb, 0xdc, 0x6c, 0xab, 0x9c, 0x85, 0xa3, 0x2d, 0x19, 0x70, 0x07, 0x9b, 0x10, 0x85, 0x0e, 0x0e, 0x3b, 0x38, 0x69, - 0xb7, 0x3b, 0xa7, 0x38, 0x39, 0x39, 0x85, 0x81, 0x36, 0x92, 0xd3, 0xc3, 0x99, 0xb2, 0x00, 0x0c, 0x72, 0xd6, 0xae, - 0xdd, 0x47, 0x10, 0xb4, 0x2a, 0x14, 0xaf, 0xf9, 0x61, 0x1c, 0xb7, 0x93, 0xfb, 0xad, 0xf6, 0xe9, 0x79, 0x03, 0x00, - 0xd4, 0x74, 0x1f, 0xae, 0xc6, 0xeb, 0x85, 0xae, 0x57, 0x29, 0x11, 0xbe, 0x5e, 0xad, 0xe1, 0xab, 0x35, 0xda, 0xeb, - 0x6a, 0x0a, 0xbe, 0xaa, 0x13, 0xce, 0x6d, 0x11, 0xaf, 0xb4, 0x09, 0xb7, 0x45, 0x6c, 0x07, 0x12, 0x83, 0x74, 0x9e, - 0x9c, 0x76, 0x4e, 0x91, 0x1d, 0x8b, 0x76, 0xf8, 0x51, 0xee, 0x93, 0xad, 0x22, 0x0d, 0x0d, 0x48, 0x52, 0xce, 0x4e, - 0x2e, 0x40, 0xa2, 0xe6, 0xe4, 0xb2, 0xdd, 0x9c, 0xb1, 0xc4, 0x4f, 0xc0, 0xa4, 0xc2, 0x72, 0x96, 0xab, 0xe0, 0x92, - 0x02, 0x40, 0x5c, 0x80, 0x71, 0xd1, 0xfd, 0xd3, 0xde, 0xfd, 0xe4, 0xf4, 0xac, 0x63, 0x89, 0x1e, 0xbf, 0xe8, 0xd4, - 0xd2, 0xcc, 0xd4, 0x93, 0x53, 0x93, 0x06, 0x5d, 0x27, 0xf7, 0x4f, 0xa1, 0x8c, 0x4b, 0x09, 0x4b, 0x41, 0xb0, 0x8d, - 0xaa, 0x18, 0x44, 0xd8, 0x48, 0x6b, 0xb9, 0x67, 0xb5, 0xec, 0xf3, 0x93, 0xe3, 0xfb, 0xa7, 0x21, 0xd4, 0xca, 0x59, - 0x98, 0x85, 0x76, 0x13, 0xf1, 0xb3, 0x83, 0xa5, 0x45, 0x87, 0xc9, 0x69, 0xba, 0x35, 0x41, 0xbb, 0x69, 0x0e, 0x0d, - 0x0e, 0x04, 0x0a, 0xc7, 0xa7, 0xc2, 0xe9, 0x4b, 0x82, 0xfb, 0xb1, 0xca, 0xd0, 0x24, 0x54, 0x38, 0xfb, 0x7b, 0xca, - 0xe0, 0x3d, 0xcd, 0xf0, 0xaa, 0xf2, 0x31, 0x15, 0x5f, 0xa9, 0x7a, 0x43, 0x21, 0x82, 0x88, 0x18, 0x44, 0x2e, 0xbe, - 0x79, 0x3d, 0xf7, 0x27, 0x70, 0x11, 0x66, 0x02, 0x2e, 0x34, 0xbd, 0x12, 0xb4, 0xe2, 0x05, 0x86, 0xa1, 0x43, 0xad, - 0x19, 0x56, 0x8f, 0xa7, 0xce, 0xa4, 0x20, 0xd4, 0x6d, 0x3d, 0xe7, 0xdf, 0x2b, 0x97, 0x94, 0x57, 0xd9, 0xc9, 0x29, - 0x4a, 0xdc, 0x65, 0x79, 0xd2, 0x46, 0x49, 0x60, 0x42, 0xe2, 0x8e, 0xe4, 0x2c, 0x23, 0xfd, 0xe8, 0x36, 0xc2, 0xd1, - 0x5d, 0x84, 0x23, 0xeb, 0xc3, 0xfc, 0x01, 0xfc, 0xb8, 0x23, 0x1c, 0x59, 0x57, 0xe6, 0x08, 0x47, 0x9a, 0x09, 0x08, - 0x2c, 0x16, 0x0d, 0x70, 0x0e, 0xa5, 0x8d, 0x67, 0x75, 0x59, 0xfa, 0xb1, 0xff, 0x2a, 0x5d, 0xaf, 0x6d, 0x4a, 0x20, - 0x65, 0x4e, 0xcd, 0x0e, 0xb5, 0x0f, 0x63, 0x47, 0xd4, 0x33, 0xeb, 0x11, 0x06, 0x01, 0x84, 0xde, 0xf9, 0x87, 0xf5, - 0xaa, 0x98, 0x24, 0xec, 0x18, 0x56, 0x1a, 0x5c, 0xd1, 0xa3, 0xf0, 0x0c, 0x8b, 0xf0, 0x58, 0xf8, 0xc2, 0x20, 0x56, - 0xf8, 0xdf, 0xb9, 0x94, 0x73, 0xff, 0x5b, 0xcb, 0xf2, 0x17, 0x3c, 0xc7, 0xe2, 0x2c, 0x5a, 0xc0, 0x72, 0xcb, 0x86, - 0x40, 0x1a, 0xb2, 0xfa, 0x08, 0xae, 0xc7, 0x2e, 0x4c, 0x1d, 0x48, 0x84, 0xd7, 0x46, 0xa0, 0xf2, 0xf2, 0xe1, 0xb5, - 0x0d, 0x99, 0x64, 0x3e, 0x21, 0x66, 0x1a, 0x84, 0x45, 0x96, 0x70, 0xa1, 0x31, 0x29, 0x98, 0x52, 0x91, 0x8d, 0x25, - 0x18, 0x49, 0xe1, 0x1f, 0x87, 0xf4, 0x29, 0x63, 0x11, 0x99, 0x0e, 0xeb, 0xb3, 0xb5, 0xe2, 0x70, 0x2e, 0x0b, 0x95, - 0xda, 0x97, 0x62, 0x3c, 0x18, 0xe7, 0xe5, 0x33, 0x8c, 0x69, 0x9e, 0xad, 0xb1, 0xbd, 0xc3, 0x2e, 0x0b, 0xb9, 0x2b, - 0xed, 0xb0, 0x54, 0x96, 0xad, 0xbf, 0x35, 0x21, 0x55, 0x9b, 0x51, 0x30, 0xd1, 0x6a, 0x40, 0x55, 0xe0, 0x0e, 0x28, - 0x6c, 0x83, 0xd2, 0xa4, 0xcb, 0xb2, 0x64, 0xba, 0x2c, 0x97, 0xe1, 0xa4, 0xd5, 0x5a, 0xaf, 0x71, 0xc1, 0x4c, 0x20, - 0x97, 0x9d, 0x25, 0x20, 0x5f, 0x4d, 0xe5, 0x4d, 0x90, 0xab, 0xd2, 0x72, 0x96, 0x66, 0x89, 0xa2, 0xc0, 0x08, 0x36, - 0x5a, 0xe3, 0xaf, 0x5c, 0x71, 0x80, 0xa7, 0x9b, 0xdd, 0x50, 0xca, 0x9c, 0x51, 0x88, 0xa1, 0x16, 0x34, 0xb9, 0xc6, - 0x53, 0x3e, 0x62, 0xbb, 0xdb, 0x04, 0x33, 0xe6, 0x7f, 0xaf, 0x45, 0x8f, 0x40, 0x96, 0xdd, 0x33, 0xa8, 0x03, 0x8b, - 0xb8, 0x82, 0x0e, 0x42, 0x19, 0x7c, 0x14, 0xe2, 0x66, 0x4e, 0xef, 0xe4, 0x42, 0x03, 0x5c, 0x16, 0x5a, 0xbe, 0x71, - 0xe1, 0x10, 0xf6, 0x5b, 0xd8, 0x47, 0x46, 0x58, 0x42, 0xc8, 0x80, 0x16, 0xb6, 0x11, 0x31, 0x5a, 0xd8, 0x05, 0x2a, - 0x68, 0x61, 0x13, 0x9e, 0xa2, 0xb5, 0x2e, 0x63, 0x9b, 0x5d, 0x97, 0x4f, 0x6a, 0x56, 0x9b, 0x60, 0xe1, 0xa4, 0x43, - 0x4d, 0x74, 0x70, 0x7b, 0xc8, 0x08, 0x6f, 0xfc, 0x7c, 0xf5, 0xfa, 0x95, 0x8b, 0x5c, 0xcd, 0xc7, 0xe0, 0xb2, 0xe9, - 0x54, 0x63, 0xd7, 0xe6, 0x2d, 0xaa, 0xb8, 0x52, 0x94, 0x5a, 0xe1, 0x14, 0x5a, 0x7e, 0x21, 0x74, 0x9e, 0xd8, 0xcb, - 0x8b, 0x67, 0xb2, 0x98, 0x51, 0x7b, 0x63, 0x84, 0xaf, 0x95, 0x7b, 0x7c, 0xde, 0xbc, 0x6f, 0x53, 0x4d, 0xf2, 0xdd, - 0xe6, 0x55, 0xc4, 0x22, 0x33, 0xf2, 0x2b, 0x68, 0x03, 0x4c, 0xe5, 0xf2, 0xed, 0xe0, 0x82, 0xb8, 0xf8, 0xff, 0x01, - 0x79, 0x79, 0x6b, 0xa9, 0x4b, 0x14, 0x35, 0xb8, 0xc1, 0x4f, 0x56, 0xf0, 0x2c, 0xb8, 0x2e, 0x34, 0xec, 0x91, 0x13, - 0x2f, 0xa2, 0x56, 0x54, 0x7f, 0x7b, 0xd7, 0xa8, 0x12, 0x7c, 0xec, 0xd8, 0x24, 0x97, 0x20, 0x7a, 0x94, 0xcf, 0xfc, - 0x71, 0x10, 0x4d, 0xfc, 0xdd, 0xf3, 0x65, 0xdb, 0xd3, 0xd9, 0xbc, 0x52, 0x27, 0x96, 0x57, 0x26, 0xe0, 0xe1, 0x68, - 0x1f, 0xd2, 0x41, 0x38, 0x48, 0x64, 0xa5, 0xf6, 0xd0, 0xe7, 0xa2, 0x6e, 0x9c, 0x5f, 0xb4, 0x59, 0xf3, 0x64, 0xb5, - 0xca, 0x2f, 0xdb, 0xac, 0x7d, 0x6a, 0x9f, 0xdd, 0x8b, 0x54, 0x06, 0x34, 0x97, 0x8f, 0x79, 0x16, 0x81, 0x76, 0x76, - 0x9c, 0x99, 0x70, 0x0a, 0x3e, 0x50, 0x34, 0x59, 0xe8, 0xaa, 0x2f, 0x09, 0xc6, 0xa5, 0xc4, 0xea, 0xf1, 0x0b, 0xd4, - 0x6b, 0xa7, 0xdb, 0xae, 0xd2, 0xcd, 0xf6, 0x61, 0x70, 0xe1, 0x52, 0x20, 0xdc, 0x81, 0x90, 0x07, 0xa0, 0xdf, 0x5d, - 0x0a, 0x30, 0x0d, 0x02, 0x54, 0x56, 0x20, 0xd2, 0xf2, 0xd9, 0x62, 0xf6, 0xac, 0xa0, 0x66, 0x19, 0x9e, 0xf0, 0x09, - 0xd7, 0x2a, 0xa5, 0x20, 0xdd, 0xee, 0x4a, 0x5f, 0xef, 0x96, 0xa0, 0xb2, 0x5a, 0xfc, 0xdd, 0x44, 0xf3, 0xec, 0x8b, - 0x72, 0x0b, 0x87, 0xb0, 0x59, 0x59, 0x81, 0x33, 0xb4, 0xc6, 0xb9, 0x9c, 0xd0, 0x82, 0xeb, 0xe9, 0xec, 0xdf, 0x5a, - 0x1d, 0xd6, 0xd7, 0x03, 0x73, 0x61, 0x05, 0x20, 0xa1, 0x62, 0xb4, 0x5a, 0xf1, 0xa3, 0xef, 0xdf, 0x27, 0x79, 0x9f, - 0xf0, 0x36, 0xee, 0xe0, 0x63, 0x7c, 0x8a, 0xdb, 0x2d, 0xdc, 0x3e, 0x85, 0xab, 0xfb, 0x2c, 0x5f, 0x8c, 0x98, 0x8a, - 0xe1, 0xfd, 0x35, 0x7d, 0x99, 0x9c, 0x1f, 0x96, 0xaf, 0x0e, 0xe8, 0x22, 0x71, 0xe8, 0x12, 0x04, 0xbf, 0x77, 0x51, - 0x03, 0xa3, 0x28, 0x0c, 0x59, 0x37, 0x0e, 0x55, 0x27, 0xa5, 0x7e, 0xe1, 0xf2, 0xb8, 0x07, 0xf6, 0xdc, 0x76, 0x65, - 0x9b, 0x60, 0xf6, 0x6d, 0x7f, 0xa6, 0xd5, 0xcf, 0xa6, 0x2e, 0x11, 0xc3, 0x43, 0xaf, 0x42, 0x0f, 0x74, 0x49, 0xda, - 0x07, 0x07, 0x60, 0x75, 0x14, 0xcc, 0x86, 0xdb, 0xe8, 0x07, 0xbc, 0x59, 0x4b, 0x83, 0x60, 0x05, 0x60, 0xdc, 0xf9, - 0x86, 0x93, 0xa5, 0x85, 0xad, 0x06, 0x2a, 0xac, 0x8b, 0x30, 0xae, 0x5e, 0x48, 0x2a, 0x8c, 0x10, 0x0d, 0x47, 0x98, - 0x0b, 0x86, 0xb2, 0xdf, 0xc2, 0x72, 0x3c, 0x56, 0x4c, 0xc3, 0xd1, 0x51, 0xb0, 0xaf, 0xac, 0x50, 0xe6, 0x14, 0x19, - 0xb2, 0x09, 0x17, 0x0f, 0xf5, 0x9f, 0xac, 0x90, 0xe6, 0xd3, 0x68, 0x30, 0xd2, 0xc8, 0xac, 0x62, 0x84, 0xb3, 0x9c, - 0xcf, 0xa1, 0xea, 0xa4, 0x00, 0xa7, 0x1f, 0xf8, 0xcb, 0x47, 0x69, 0xd8, 0x26, 0x90, 0xaf, 0x0f, 0x36, 0xa6, 0x0b, - 0x1e, 0x15, 0xf4, 0xe6, 0xb5, 0x78, 0x0c, 0x3b, 0xea, 0x61, 0xc1, 0x28, 0x64, 0x43, 0xd2, 0x3b, 0x68, 0x0a, 0x3e, - 0xa0, 0xcd, 0x97, 0x06, 0x70, 0xe9, 0xb9, 0xf9, 0xb0, 0x15, 0x7d, 0xec, 0xc6, 0xa4, 0x6c, 0xcb, 0x64, 0x9a, 0x53, - 0xba, 0xca, 0xb4, 0x51, 0xa8, 0xca, 0x29, 0xac, 0xb1, 0x8b, 0x7a, 0x12, 0x0e, 0x66, 0x44, 0xd5, 0x34, 0xed, 0x0f, - 0xcc, 0xdf, 0xd7, 0xb6, 0x64, 0x0b, 0xbb, 0x88, 0x33, 0x6b, 0x6c, 0x1e, 0x4e, 0x0d, 0xca, 0xb7, 0x31, 0xdc, 0xc3, - 0xc2, 0xeb, 0x9d, 0x35, 0xf2, 0x79, 0xe2, 0xc9, 0xe6, 0xc9, 0x7a, 0x6d, 0x06, 0xa2, 0x52, 0xd0, 0x03, 0xbd, 0xf5, - 0xdb, 0xa6, 0x05, 0xdb, 0xa3, 0xfc, 0x3a, 0x6d, 0xe1, 0x19, 0x87, 0xc7, 0x48, 0x7d, 0x7b, 0x57, 0xba, 0x90, 0x5f, - 0x1c, 0x48, 0x5a, 0x41, 0x8a, 0x9d, 0x4e, 0xd0, 0xd9, 0x31, 0x0e, 0x46, 0x0e, 0xf4, 0xfc, 0xea, 0x8b, 0x85, 0xb5, - 0xff, 0xfd, 0xa6, 0x2c, 0x68, 0xe2, 0xe9, 0x94, 0x13, 0xca, 0xfc, 0xf9, 0xf9, 0x86, 0x27, 0x15, 0x2a, 0xb8, 0x57, - 0xbc, 0x60, 0x4f, 0xdb, 0x40, 0x9f, 0x33, 0xfa, 0xd9, 0xfe, 0xb0, 0x31, 0x7c, 0x4a, 0x2d, 0x5b, 0x56, 0x48, 0xa5, - 0x1e, 0xda, 0x34, 0x7b, 0xf4, 0xc0, 0x11, 0xf9, 0x12, 0xba, 0x00, 0x5e, 0x7f, 0x54, 0xc8, 0xb9, 0x41, 0x04, 0xf7, - 0xdb, 0x8d, 0xdb, 0xf8, 0x0a, 0x80, 0xb7, 0xc3, 0x5e, 0xf5, 0x4f, 0x0b, 0xd8, 0xdf, 0xa8, 0x2c, 0xe9, 0xc7, 0xdb, - 0xb1, 0xc7, 0x7f, 0x21, 0x21, 0x6a, 0xbc, 0xc5, 0xc3, 0xc4, 0xa1, 0x53, 0xc9, 0x9a, 0x95, 0x3f, 0xb7, 0x4a, 0x02, - 0x86, 0xd5, 0x0b, 0x86, 0x6c, 0xdc, 0x56, 0x71, 0x9b, 0xf9, 0x1f, 0x54, 0x30, 0x58, 0xf0, 0xad, 0x91, 0x54, 0x2c, - 0x8b, 0xdf, 0x3e, 0x75, 0xfe, 0xab, 0xce, 0x71, 0xed, 0xeb, 0xda, 0x4b, 0xa1, 0x43, 0x13, 0xa5, 0x39, 0x42, 0x07, - 0x07, 0x1b, 0x19, 0x74, 0x0c, 0x80, 0x47, 0x8e, 0xfd, 0xf2, 0xcb, 0xe7, 0xd9, 0x31, 0xa3, 0x79, 0x2c, 0xa2, 0x90, - 0xb9, 0xf3, 0xdc, 0x9c, 0x9d, 0xc8, 0x13, 0xaa, 0xa6, 0xbe, 0x30, 0xc0, 0xf1, 0xd1, 0x56, 0x2a, 0xe0, 0x7b, 0xb4, - 0xde, 0x31, 0x81, 0x0d, 0x7e, 0xcb, 0x4e, 0x6a, 0x57, 0x41, 0xbf, 0x40, 0xcb, 0x5d, 0x4c, 0xe5, 0xc6, 0x02, 0x47, - 0x9b, 0x13, 0xd9, 0x39, 0xf4, 0x8d, 0x3a, 0x25, 0xeb, 0xf1, 0x64, 0xb7, 0xd1, 0x97, 0x14, 0xbb, 0x92, 0x2b, 0xda, - 0x36, 0x64, 0xd5, 0x6b, 0xc1, 0xba, 0x32, 0x75, 0xaa, 0xae, 0x79, 0x2b, 0x4b, 0x9b, 0xd2, 0x2e, 0xc9, 0xde, 0x6d, - 0xb1, 0xf0, 0x2a, 0xbc, 0xd1, 0x28, 0x2f, 0x42, 0xc1, 0x1e, 0x4b, 0x0c, 0xba, 0x9c, 0xc0, 0xf5, 0xc2, 0x6a, 0x15, - 0xc3, 0x9f, 0x5d, 0x63, 0xd8, 0x65, 0xba, 0xf4, 0x81, 0x6f, 0xf0, 0x2b, 0x41, 0xc0, 0x62, 0x67, 0x07, 0x09, 0xd6, - 0x5d, 0x6e, 0xd0, 0x70, 0x9c, 0xf8, 0x2f, 0x78, 0x2e, 0x5b, 0x7b, 0x97, 0x83, 0x49, 0xf6, 0x8d, 0x27, 0xf6, 0x4a, - 0xd6, 0xb2, 0x16, 0xed, 0x7e, 0x43, 0x82, 0x21, 0x76, 0x53, 0x3a, 0xc7, 0xad, 0xa4, 0x8d, 0x22, 0x57, 0xac, 0x42, - 0xff, 0x6f, 0x15, 0xc9, 0x6c, 0xe6, 0x7f, 0x9d, 0x9d, 0x9d, 0xb9, 0x14, 0x67, 0xf3, 0xa7, 0x8c, 0x07, 0x9c, 0x49, - 0x60, 0x5f, 0x79, 0xc6, 0x8c, 0x0e, 0xf9, 0x2d, 0x0c, 0x85, 0x08, 0x72, 0x29, 0x1c, 0xbb, 0x04, 0xaf, 0x3d, 0x02, - 0xe5, 0x01, 0xf6, 0xef, 0xc9, 0x46, 0x39, 0xff, 0x5c, 0x94, 0x0f, 0xa7, 0x5c, 0x36, 0xc8, 0xbe, 0x9a, 0xcf, 0xbe, - 0x35, 0x93, 0x81, 0x17, 0x12, 0x22, 0x6c, 0x7f, 0x1b, 0x96, 0xd6, 0x59, 0xca, 0xe0, 0x48, 0xcb, 0x45, 0x36, 0xb5, - 0x9a, 0x7f, 0xf7, 0x61, 0xca, 0xba, 0xa7, 0x86, 0x20, 0x72, 0x17, 0x59, 0xba, 0xa8, 0xa0, 0xd1, 0x8f, 0x65, 0x00, - 0xd0, 0xbd, 0x57, 0x6c, 0xc1, 0x7e, 0xc4, 0x7b, 0x55, 0x0a, 0x7c, 0x3c, 0x2c, 0x38, 0xcd, 0x7f, 0xc4, 0x7b, 0x55, - 0x20, 0x50, 0x70, 0x85, 0x34, 0xb1, 0x34, 0xb1, 0x79, 0x56, 0x3b, 0x8d, 0x04, 0x50, 0xd0, 0x3c, 0x32, 0x07, 0xd9, - 0x73, 0x17, 0xa3, 0x31, 0xe9, 0x60, 0x17, 0x1c, 0xcc, 0x46, 0x84, 0xb5, 0x81, 0xd4, 0x21, 0x6e, 0x5d, 0x39, 0x1b, - 0xf3, 0xf5, 0x68, 0x63, 0x41, 0x8c, 0x32, 0x99, 0x5c, 0x3e, 0xe7, 0xf1, 0xd6, 0x62, 0xa1, 0xb0, 0x5a, 0xb0, 0x40, - 0xb5, 0x2a, 0x55, 0x7a, 0x58, 0x7c, 0xbb, 0x60, 0x16, 0x14, 0x31, 0x5b, 0xef, 0xe1, 0x2d, 0x57, 0x04, 0xa4, 0x64, - 0x97, 0x04, 0x2f, 0xa3, 0x1b, 0x4c, 0x25, 0xcb, 0x99, 0x1c, 0x31, 0x4b, 0xe8, 0x99, 0xd2, 0x11, 0x36, 0x79, 0x0a, - 0x22, 0x89, 0xed, 0xb7, 0xb0, 0x63, 0x8d, 0x5e, 0x08, 0x2f, 0xa4, 0xc0, 0xb9, 0x6a, 0x9a, 0x98, 0x51, 0x6e, 0xa2, - 0x8b, 0x3d, 0x54, 0x73, 0x96, 0x69, 0x8b, 0x00, 0xfb, 0x0e, 0x0d, 0xa5, 0x78, 0x6e, 0x40, 0x61, 0x9e, 0xf4, 0x76, - 0x29, 0x8f, 0x61, 0xf1, 0x82, 0x14, 0x20, 0x6a, 0x5c, 0x4c, 0xca, 0x3a, 0xf3, 0x7c, 0x31, 0xe1, 0xa2, 0x42, 0x86, - 0x82, 0xa9, 0xb9, 0x14, 0xf0, 0xa2, 0x46, 0x59, 0xc4, 0xd0, 0xa1, 0x1a, 0xbe, 0x5b, 0x12, 0x56, 0xd6, 0x31, 0xc7, - 0x14, 0x17, 0x55, 0x0d, 0x60, 0x2e, 0x1e, 0x1a, 0x01, 0xd1, 0x87, 0x97, 0x7d, 0x2d, 0xde, 0xc9, 0x79, 0x95, 0xef, - 0x69, 0x9c, 0x0f, 0x5c, 0xef, 0xec, 0x86, 0xd1, 0xda, 0x3c, 0x7a, 0x15, 0x6c, 0xdf, 0x0f, 0xbc, 0x7a, 0x08, 0x6e, - 0x6d, 0x9e, 0xcd, 0x2a, 0xb3, 0x86, 0xac, 0x7c, 0x23, 0xa2, 0x6a, 0xaf, 0x5e, 0x55, 0x0a, 0x5b, 0x11, 0xa0, 0x52, - 0xf0, 0xd1, 0x56, 0xfe, 0x13, 0x6d, 0xf3, 0xed, 0x39, 0x54, 0x86, 0x07, 0xf2, 0x64, 0xa8, 0xea, 0x01, 0x17, 0xe5, - 0x87, 0x00, 0x16, 0x3f, 0x32, 0xf1, 0x83, 0x77, 0x5d, 0x20, 0x73, 0xa6, 0x62, 0x89, 0x97, 0x7d, 0x3a, 0x48, 0xad, - 0x3c, 0x94, 0x4a, 0xb0, 0xed, 0xb9, 0x29, 0xb8, 0xf6, 0x81, 0x8a, 0x71, 0x9f, 0x0d, 0xd2, 0x65, 0x3d, 0x98, 0xb1, - 0x0d, 0xa7, 0xec, 0xcd, 0x39, 0x4d, 0xf4, 0x5f, 0x3a, 0xc0, 0x39, 0x01, 0xdb, 0x63, 0xcf, 0x9e, 0xbe, 0x89, 0x33, - 0xd4, 0xab, 0x73, 0xf8, 0xcb, 0x35, 0xce, 0x71, 0x86, 0xd2, 0x87, 0x31, 0x5c, 0x60, 0xad, 0x31, 0x80, 0x2f, 0xb3, - 0xa4, 0x0a, 0x3c, 0x52, 0x33, 0x23, 0xb1, 0xba, 0x8b, 0x40, 0xb4, 0xd4, 0xe1, 0xed, 0x38, 0xf3, 0xe1, 0xc0, 0x0d, - 0xf7, 0xfa, 0xcc, 0x08, 0x87, 0x93, 0x2c, 0xae, 0x9d, 0x33, 0x9c, 0x5c, 0xee, 0xf3, 0xda, 0x89, 0x09, 0xd6, 0xde, - 0xe1, 0xa9, 0x02, 0x7a, 0x34, 0x38, 0x55, 0x2c, 0x0d, 0x81, 0x98, 0x09, 0xe0, 0xcd, 0x1c, 0x1e, 0x6d, 0x01, 0xce, - 0x47, 0x6b, 0x1c, 0x7c, 0xa5, 0xb5, 0xae, 0x36, 0x95, 0x28, 0xeb, 0x35, 0xee, 0x4f, 0x33, 0x3c, 0xca, 0xf0, 0x3c, - 0x1b, 0x04, 0xc7, 0xcd, 0x2c, 0x0b, 0x4d, 0xba, 0x56, 0xab, 0xa7, 0xce, 0x8c, 0x10, 0xd9, 0x9f, 0x96, 0xfe, 0xa0, - 0x1e, 0x20, 0x7c, 0x0a, 0x59, 0x40, 0x4b, 0x7a, 0xee, 0x6f, 0xc3, 0xbe, 0x16, 0x8e, 0x1a, 0x31, 0x4f, 0x2c, 0x19, - 0xe9, 0xf9, 0x1f, 0x65, 0x96, 0x6d, 0xad, 0x11, 0xcd, 0x6f, 0xf7, 0xa2, 0x86, 0x6f, 0x2f, 0xd0, 0xb2, 0x95, 0x66, - 0x3b, 0x80, 0x28, 0xd6, 0x38, 0x49, 0x07, 0x6b, 0x24, 0x57, 0xab, 0xd8, 0xa6, 0x10, 0x9e, 0xcc, 0x18, 0x55, 0x8b, - 0xc2, 0x3c, 0xa0, 0x17, 0x2b, 0x94, 0x18, 0x7e, 0x17, 0x3b, 0x1b, 0x51, 0x78, 0xaf, 0x4e, 0x82, 0xe1, 0x46, 0x2c, - 0x88, 0xac, 0x89, 0xdc, 0xc3, 0xac, 0xb2, 0x0c, 0x12, 0x44, 0x18, 0x91, 0xdf, 0x5e, 0x97, 0x0a, 0xfb, 0x44, 0x9f, - 0xfd, 0x63, 0x7c, 0x01, 0xe1, 0xe6, 0x6d, 0x42, 0x8b, 0x21, 0x9d, 0x00, 0x1b, 0x0b, 0x71, 0x08, 0xb7, 0x12, 0x56, - 0xab, 0xfe, 0xa0, 0x2b, 0x0c, 0x79, 0x76, 0x0f, 0x08, 0x96, 0x0d, 0xed, 0x6e, 0x00, 0xae, 0xba, 0x2d, 0x35, 0xd7, - 0x46, 0xf7, 0x43, 0xcd, 0x1b, 0x67, 0xdc, 0x25, 0xb9, 0x67, 0x4a, 0xaa, 0x97, 0xc8, 0x6b, 0x16, 0xe0, 0x26, 0x74, - 0x15, 0x1e, 0xe1, 0x85, 0xb5, 0xe1, 0x34, 0x0f, 0x5a, 0x51, 0xf3, 0x8e, 0x15, 0x3c, 0x9f, 0x4d, 0x58, 0x3f, 0x1b, - 0xe0, 0x91, 0x0f, 0x77, 0xbe, 0xff, 0x36, 0x1e, 0x21, 0x54, 0x10, 0x03, 0x53, 0xeb, 0xb2, 0x3d, 0xaa, 0xec, 0xf6, - 0x4d, 0xa6, 0x61, 0x18, 0x8c, 0x11, 0xf3, 0x28, 0x34, 0x62, 0xce, 0x1b, 0x0d, 0xb4, 0x20, 0x23, 0x30, 0x62, 0x5e, - 0x04, 0xad, 0x2d, 0xec, 0x63, 0xa7, 0x41, 0x7b, 0x0b, 0x84, 0xba, 0x1c, 0x68, 0x9a, 0x86, 0x67, 0x4d, 0xaa, 0x67, - 0xe5, 0xfd, 0x23, 0x5b, 0x47, 0x1d, 0x50, 0x24, 0x8c, 0x2f, 0xfd, 0x24, 0xac, 0x6b, 0xb8, 0x1d, 0xf7, 0xd8, 0x8c, - 0xdb, 0xd9, 0x36, 0xa8, 0xbe, 0xec, 0x67, 0x83, 0x41, 0x57, 0x7a, 0x2b, 0x89, 0x16, 0x1e, 0x57, 0x0f, 0xa1, 0x54, - 0x8b, 0xf7, 0x55, 0x6f, 0x5e, 0x79, 0x73, 0xff, 0xbe, 0xea, 0xe6, 0x79, 0x0c, 0x1c, 0xd0, 0x3e, 0xdc, 0x0f, 0x55, - 0xf1, 0xc1, 0x8e, 0x3a, 0x10, 0x05, 0x2d, 0x6d, 0xd5, 0x04, 0x52, 0x6b, 0x66, 0x17, 0xeb, 0xa6, 0x42, 0x87, 0x02, - 0xc2, 0x90, 0xa9, 0xaa, 0xbb, 0x3b, 0x15, 0xa8, 0x86, 0x38, 0x9c, 0xfa, 0x8f, 0xad, 0x11, 0x6b, 0x1c, 0x75, 0x46, - 0x91, 0x31, 0x92, 0xb4, 0xcb, 0x07, 0x6f, 0x1f, 0x81, 0x95, 0x80, 0x8f, 0x41, 0x6d, 0x92, 0x8c, 0x21, 0xc1, 0x5b, - 0x96, 0x69, 0xc3, 0x87, 0x70, 0x87, 0xa0, 0x3c, 0xb1, 0x41, 0x69, 0x5d, 0x25, 0x0b, 0xb9, 0xaa, 0xcb, 0xeb, 0x00, - 0x3d, 0xef, 0xca, 0xdf, 0xd8, 0x70, 0x64, 0xc1, 0xc0, 0xb2, 0xad, 0x7d, 0x02, 0x1e, 0xf9, 0xb8, 0x42, 0x10, 0xbf, - 0x14, 0x3a, 0x31, 0xf1, 0xba, 0xaf, 0x60, 0x83, 0xe2, 0x39, 0x38, 0x08, 0x3a, 0x09, 0x0e, 0x83, 0x77, 0x99, 0xd5, - 0x24, 0x1b, 0xdc, 0x9a, 0x91, 0x78, 0xbe, 0x5a, 0xb5, 0xd0, 0xe1, 0xdf, 0xe6, 0x49, 0xea, 0x71, 0xa9, 0x70, 0x1f, - 0x57, 0x0a, 0x77, 0xb0, 0x04, 0x24, 0xe3, 0x40, 0xd7, 0x8e, 0x65, 0xa8, 0x46, 0x87, 0x68, 0xe9, 0x2f, 0x20, 0x76, - 0xb6, 0x3b, 0x96, 0x40, 0xcf, 0xbe, 0x55, 0xc0, 0xea, 0xda, 0xcb, 0x12, 0xc8, 0x08, 0xee, 0x7e, 0x13, 0x18, 0x15, - 0xa2, 0xf1, 0xf9, 0x33, 0xaf, 0x5a, 0xf0, 0xc4, 0xf9, 0x73, 0xcd, 0x0c, 0xeb, 0x5e, 0xd0, 0x1b, 0xd3, 0x7c, 0x3c, - 0xc6, 0xcd, 0xb1, 0x05, 0xe7, 0x51, 0x07, 0x7e, 0x5a, 0x88, 0x1e, 0x75, 0xb0, 0x4b, 0xc5, 0xe3, 0x12, 0xc8, 0x21, - 0x7a, 0x3a, 0x03, 0x29, 0x60, 0xa5, 0x63, 0xab, 0x45, 0x9a, 0xa0, 0xd5, 0x6a, 0x72, 0x41, 0x5a, 0x08, 0x2d, 0xd5, - 0x0d, 0xd7, 0xd9, 0x14, 0x7c, 0xa4, 0x41, 0x31, 0xf0, 0x86, 0xea, 0x69, 0x8c, 0xf0, 0x18, 0x2d, 0x47, 0x6c, 0x4c, - 0x17, 0xb9, 0x4e, 0x55, 0x8f, 0x27, 0x36, 0x70, 0x2f, 0xb3, 0x91, 0xe0, 0x8e, 0x3a, 0x78, 0x62, 0xf8, 0xcb, 0xf7, - 0xc6, 0x1c, 0xa4, 0xc8, 0x4c, 0xf2, 0xc4, 0x24, 0x60, 0x9e, 0x64, 0xb9, 0x54, 0xcc, 0x36, 0xd3, 0xb5, 0xb6, 0xe5, - 0x10, 0x92, 0x3c, 0xd2, 0x05, 0x37, 0x56, 0x94, 0x51, 0x3a, 0x25, 0xaa, 0xa7, 0x8e, 0x3a, 0xe9, 0x04, 0xf3, 0x04, - 0x38, 0xbd, 0x77, 0x32, 0x66, 0x8d, 0xf2, 0x56, 0x74, 0x86, 0x0e, 0xa7, 0x58, 0x54, 0x97, 0xa8, 0x33, 0x74, 0x38, - 0x41, 0x78, 0xd6, 0x20, 0xb9, 0x02, 0x8f, 0x61, 0x2e, 0xfe, 0x8f, 0x94, 0xff, 0xe6, 0xb0, 0x21, 0xc4, 0xf4, 0x5b, - 0xd8, 0x29, 0x6c, 0x14, 0xa5, 0x39, 0x01, 0xaf, 0xc5, 0xf6, 0x19, 0xce, 0xc8, 0xa4, 0x99, 0xfb, 0x80, 0x7b, 0xa6, - 0x95, 0xc6, 0xad, 0x46, 0x87, 0x19, 0x1e, 0x6d, 0x26, 0xc5, 0x66, 0xae, 0xcd, 0x3c, 0xcd, 0xe0, 0x7c, 0xaf, 0x46, - 0xe1, 0xca, 0x2f, 0x36, 0x93, 0xc2, 0xf2, 0x0e, 0xb8, 0xcd, 0x11, 0x16, 0x4d, 0x8a, 0x73, 0x3c, 0x6b, 0xbe, 0xc2, - 0xb3, 0xe6, 0x87, 0x32, 0xa3, 0xb1, 0xc0, 0x02, 0x82, 0xf7, 0x41, 0x22, 0x9e, 0x55, 0xc9, 0x23, 0x2c, 0x1a, 0xa6, - 0x3c, 0x9e, 0x35, 0xaa, 0xd2, 0xcd, 0x05, 0x16, 0x0d, 0x53, 0xba, 0xf1, 0x01, 0xcf, 0x1a, 0xaf, 0xfe, 0xc5, 0xa4, - 0xa3, 0x14, 0xd0, 0x65, 0x8e, 0x96, 0x99, 0x1d, 0xe2, 0xd5, 0x6f, 0x6f, 0xdf, 0xb5, 0xaf, 0x3b, 0x87, 0x13, 0xec, - 0xd7, 0x2f, 0x33, 0x38, 0x96, 0xe9, 0x98, 0x35, 0x01, 0xa2, 0x19, 0xee, 0x1c, 0x4e, 0x71, 0xe7, 0x30, 0x73, 0x4d, - 0xad, 0x67, 0x0d, 0x72, 0xab, 0x43, 0x28, 0xea, 0x28, 0x0d, 0xe1, 0xe3, 0x27, 0x9b, 0x4e, 0x50, 0x0d, 0x94, 0xe8, - 0x70, 0x52, 0x03, 0x15, 0x7c, 0x2f, 0x6a, 0xdf, 0x55, 0xbd, 0x0a, 0x83, 0x2c, 0x94, 0x50, 0xb8, 0xe6, 0x06, 0x3c, - 0xb5, 0x14, 0x03, 0x99, 0x30, 0xc5, 0x02, 0xe5, 0x3b, 0xa0, 0x30, 0xca, 0x13, 0x33, 0xf4, 0x60, 0x3a, 0x26, 0xf1, - 0xff, 0xe7, 0xc9, 0x94, 0x43, 0x2f, 0xb7, 0xcc, 0xd6, 0xf4, 0xdc, 0x64, 0xc2, 0xe1, 0x03, 0x8f, 0xf5, 0x7f, 0xed, - 0x40, 0xb1, 0x01, 0x29, 0xfe, 0xbf, 0x74, 0x74, 0x21, 0x18, 0x21, 0x2b, 0x4a, 0x0b, 0x87, 0xf8, 0xdf, 0x1f, 0x56, - 0xd0, 0x7d, 0xb1, 0xd5, 0x7d, 0x61, 0xba, 0x0f, 0x9b, 0x36, 0xaa, 0x9c, 0xb4, 0xaa, 0x64, 0xc9, 0x7f, 0x9d, 0x6e, - 0x6d, 0x81, 0x46, 0xd4, 0xe8, 0xd9, 0x24, 0x6c, 0x70, 0xbf, 0x9d, 0xee, 0x40, 0xe6, 0x35, 0xb7, 0x2f, 0xa4, 0xc2, - 0xe1, 0x1b, 0xdc, 0xa9, 0x5e, 0xb6, 0xc0, 0x7b, 0x53, 0x19, 0x7d, 0x65, 0x1c, 0x5a, 0x0e, 0xd2, 0x4d, 0x53, 0x6e, - 0x63, 0x2c, 0x9d, 0x9c, 0x62, 0xe3, 0x8a, 0x08, 0x95, 0x6e, 0x2f, 0x41, 0x29, 0x3e, 0xd6, 0x4d, 0x66, 0xbe, 0x2e, - 0x74, 0x62, 0x2e, 0xa1, 0x1a, 0xe6, 0xf3, 0xee, 0x52, 0x27, 0x5a, 0xce, 0x6d, 0xde, 0xdd, 0x05, 0xf4, 0x09, 0x1a, - 0xd6, 0x46, 0x60, 0xb7, 0xcf, 0x0a, 0xa7, 0xdf, 0xa9, 0x0e, 0xc1, 0xf0, 0x00, 0x72, 0xa4, 0xc5, 0xf6, 0x81, 0x4d, - 0x6b, 0xd8, 0x75, 0xd1, 0x2c, 0x13, 0x6d, 0xab, 0x4d, 0x93, 0x6b, 0xf7, 0x30, 0x9f, 0x87, 0x3c, 0x05, 0x2f, 0xac, - 0x7e, 0x7c, 0x07, 0xbb, 0x71, 0x5b, 0x63, 0x24, 0xea, 0x4a, 0xa6, 0x12, 0xfa, 0xc9, 0x2d, 0x66, 0xc9, 0x9d, 0xf1, - 0x62, 0x54, 0xc6, 0xdf, 0xc7, 0xc4, 0xe5, 0x8f, 0x2a, 0x49, 0x0e, 0x2c, 0xfb, 0x1b, 0x2c, 0xb9, 0x05, 0xf3, 0xc4, - 0xb2, 0x9a, 0xc4, 0x3a, 0xb9, 0x0b, 0x16, 0x51, 0x9a, 0x46, 0xd6, 0x86, 0x01, 0x35, 0xcd, 0x58, 0xf5, 0xe0, 0x3e, - 0x04, 0x7a, 0xe8, 0x95, 0xa5, 0xb4, 0xeb, 0x2c, 0xad, 0x75, 0xaf, 0x4d, 0xf7, 0x9b, 0x03, 0x0a, 0xf8, 0xc2, 0x80, - 0x6b, 0xfa, 0x57, 0x93, 0x48, 0x86, 0xec, 0x1f, 0xce, 0x8a, 0xc7, 0x8b, 0xc2, 0x60, 0x9a, 0xe8, 0xe9, 0x24, 0x9b, - 0xb7, 0xc1, 0x54, 0x2f, 0x9b, 0x77, 0x6e, 0xb1, 0xfb, 0xbe, 0xb3, 0xdf, 0x77, 0x58, 0xf4, 0x98, 0xc9, 0x48, 0x99, - 0x29, 0xe6, 0xbf, 0xef, 0xec, 0xf7, 0x1d, 0xde, 0x1e, 0xcc, 0x8d, 0xbf, 0x50, 0x2c, 0xd9, 0x19, 0x2e, 0xc1, 0x84, - 0x3c, 0xe0, 0x6e, 0x6a, 0x59, 0x26, 0x08, 0x6c, 0x2d, 0x01, 0xe2, 0x7c, 0x3e, 0x8d, 0x2b, 0x5e, 0x0d, 0x01, 0xf7, - 0xe9, 0x5d, 0xdb, 0xab, 0x54, 0xe0, 0x31, 0x41, 0x23, 0x62, 0x62, 0xdb, 0x98, 0xd7, 0xcd, 0x80, 0xcb, 0x23, 0xba, - 0xd4, 0x93, 0x24, 0xc0, 0xab, 0x1a, 0x95, 0xb7, 0x29, 0x52, 0x7e, 0x91, 0x20, 0xc7, 0x17, 0x7b, 0x44, 0x15, 0x03, - 0x58, 0x95, 0x25, 0x7d, 0x02, 0xa9, 0xe7, 0x07, 0x13, 0xfd, 0xb2, 0x89, 0x3c, 0xf6, 0x9d, 0xdf, 0x2f, 0x4c, 0x4f, - 0x0b, 0xb9, 0x98, 0x4c, 0xc1, 0x87, 0x16, 0x58, 0x86, 0xc2, 0xd4, 0xab, 0x6c, 0xfd, 0x6b, 0x92, 0x9b, 0x00, 0x0a, - 0xa7, 0x9b, 0x32, 0xa1, 0x99, 0x5e, 0xd0, 0xdc, 0x58, 0x92, 0x72, 0x31, 0x79, 0x24, 0x6f, 0x5f, 0x02, 0x76, 0x53, - 0xa2, 0x1b, 0x3b, 0xf2, 0xde, 0xc2, 0x0e, 0xc0, 0x19, 0x61, 0xbb, 0x2a, 0x3e, 0x54, 0xa0, 0xf3, 0xc7, 0x39, 0x61, - 0xbb, 0xaa, 0x3e, 0x61, 0x36, 0x7b, 0x4a, 0x36, 0x86, 0xdb, 0x8b, 0xb3, 0x46, 0x8e, 0x8e, 0x3a, 0x69, 0xde, 0xf5, - 0xc4, 0xc0, 0x02, 0x34, 0x00, 0xee, 0xd6, 0xf6, 0x2c, 0xef, 0x6e, 0x08, 0xe8, 0x5d, 0x32, 0x69, 0xaf, 0xcb, 0x4d, - 0xca, 0x6a, 0xd5, 0xa9, 0xa8, 0x60, 0x81, 0xa7, 0xc1, 0x5e, 0xa0, 0xf6, 0x6b, 0x07, 0xc5, 0xb9, 0xca, 0x36, 0x4d, - 0xcf, 0xcb, 0xbe, 0xbb, 0x3b, 0x16, 0x19, 0xdb, 0xb4, 0xb7, 0x3b, 0x88, 0x84, 0xe5, 0x84, 0x75, 0xc0, 0x09, 0x57, - 0xb5, 0x03, 0x02, 0x74, 0x1d, 0x88, 0xdc, 0x58, 0x92, 0xe5, 0xba, 0x32, 0xba, 0x0f, 0xfc, 0x6e, 0x29, 0x91, 0x6e, - 0xb4, 0x25, 0xc1, 0xf4, 0x09, 0x46, 0x4d, 0x67, 0x9e, 0xa6, 0xae, 0xbd, 0xba, 0xbc, 0x29, 0xda, 0xfa, 0x37, 0xa0, - 0xb1, 0xd9, 0x1e, 0x26, 0x86, 0x32, 0x88, 0x81, 0xde, 0x47, 0xbc, 0xdb, 0x68, 0x64, 0x08, 0x14, 0x32, 0xd9, 0x00, - 0xcb, 0xc4, 0x6b, 0xd1, 0x0f, 0x0e, 0x0c, 0x3c, 0xaa, 0x04, 0x84, 0x29, 0x08, 0x21, 0x61, 0xd7, 0x06, 0x61, 0xc3, - 0xe5, 0xaa, 0xe5, 0xc2, 0x46, 0xaa, 0x0d, 0x1d, 0xfc, 0xbf, 0xc2, 0x65, 0xab, 0x67, 0x96, 0x8b, 0x62, 0x70, 0x33, - 0x37, 0x60, 0x91, 0x20, 0x3d, 0xda, 0x6c, 0x0f, 0xc5, 0xdd, 0xb9, 0xd8, 0x6c, 0x08, 0x48, 0xcc, 0x61, 0x82, 0xa2, - 0xe1, 0xdc, 0x18, 0x63, 0x95, 0x54, 0x5a, 0xd6, 0x9a, 0xc4, 0x1c, 0xf8, 0xd2, 0x85, 0xeb, 0xbe, 0xbc, 0x4d, 0x19, - 0xbe, 0x4b, 0x05, 0xbe, 0x01, 0x4f, 0x9a, 0x54, 0x62, 0xf7, 0x78, 0x41, 0xb1, 0x26, 0xba, 0xeb, 0xd9, 0xdb, 0x02, - 0xd6, 0xd9, 0xec, 0x11, 0x11, 0xfc, 0xae, 0x7e, 0xb5, 0xc1, 0x77, 0x0b, 0xbf, 0x02, 0xeb, 0xe7, 0xe0, 0x24, 0xc5, - 0xa2, 0x21, 0x9b, 0x85, 0x3b, 0x32, 0xa0, 0x5c, 0xc5, 0x2f, 0x87, 0xa9, 0x5b, 0xc5, 0x70, 0xed, 0xe3, 0x15, 0xfe, - 0xb0, 0xd1, 0x6e, 0x43, 0x95, 0xc5, 0xed, 0xde, 0x14, 0x0d, 0x59, 0x35, 0xbd, 0x23, 0x73, 0x23, 0xa5, 0xfe, 0xf5, - 0x01, 0xb7, 0xb6, 0xda, 0xf7, 0xd3, 0x7c, 0xeb, 0xd1, 0xb9, 0x6a, 0xda, 0xa7, 0xd6, 0x8a, 0xe0, 0xe0, 0x67, 0x0b, - 0x37, 0xb7, 0x06, 0x1c, 0xc0, 0xcf, 0xdf, 0xd1, 0x3c, 0xce, 0x20, 0x3a, 0xbd, 0xd5, 0x8c, 0xaf, 0xe2, 0xbf, 0x46, - 0x8d, 0xb8, 0x97, 0xfe, 0x95, 0xfc, 0x35, 0x6a, 0xa0, 0x1e, 0x8a, 0xe7, 0xb7, 0x2b, 0x36, 0x5b, 0x41, 0xb0, 0xb5, - 0x7b, 0x47, 0xf8, 0x75, 0x58, 0x92, 0x6b, 0x9a, 0xf3, 0x6c, 0xe5, 0x1e, 0x04, 0x5c, 0xb9, 0x57, 0x89, 0x56, 0xe6, - 0x8d, 0xab, 0x55, 0x2c, 0x87, 0x39, 0x04, 0x16, 0x8e, 0xf7, 0x9a, 0xbd, 0x7e, 0xab, 0xf9, 0x60, 0x60, 0xff, 0x35, - 0x11, 0xee, 0x51, 0x2d, 0x62, 0xdb, 0x9b, 0x8d, 0xad, 0x1f, 0x83, 0x61, 0x07, 0x84, 0x02, 0x07, 0xb9, 0xf4, 0x71, - 0x86, 0xac, 0xef, 0xc9, 0x6a, 0xc5, 0x5c, 0x34, 0x6b, 0xa7, 0xc1, 0x2f, 0x63, 0x33, 0x1d, 0xb6, 0x93, 0x4e, 0xd7, - 0x8b, 0xb1, 0xa4, 0x01, 0x91, 0xa6, 0x31, 0x83, 0x40, 0x52, 0x4b, 0xc3, 0x61, 0xcd, 0x6f, 0xa3, 0xb4, 0xba, 0x3f, - 0x82, 0x94, 0x1f, 0xa2, 0x94, 0x1f, 0x11, 0x08, 0xa0, 0x6d, 0x99, 0xa3, 0xb2, 0x21, 0xef, 0xbb, 0x74, 0xcf, 0x38, - 0x33, 0x34, 0xf8, 0x6a, 0xd5, 0xaa, 0x86, 0x29, 0x8a, 0xfa, 0x30, 0x97, 0x6b, 0x2c, 0xc8, 0x1b, 0xd0, 0x35, 0x2b, - 0x22, 0x7a, 0xa1, 0xab, 0x3c, 0xbc, 0x87, 0x8c, 0x25, 0x01, 0x27, 0xfd, 0x9e, 0xe8, 0x15, 0xe4, 0xf2, 0x61, 0x0c, - 0x3e, 0x66, 0x98, 0xf7, 0x75, 0xbf, 0x18, 0x0c, 0x50, 0xea, 0x9c, 0xce, 0x52, 0x13, 0x71, 0x25, 0xf0, 0x4b, 0x2e, - 0xc0, 0x2f, 0x59, 0x21, 0xd6, 0x2f, 0x06, 0xe4, 0x5e, 0x16, 0x4b, 0x70, 0xca, 0xdf, 0xe1, 0xf3, 0xf8, 0x30, 0x34, - 0x30, 0x35, 0xc3, 0x32, 0x17, 0xd9, 0x60, 0x31, 0x67, 0x2d, 0x81, 0xe0, 0x66, 0xc0, 0x5d, 0x6a, 0x43, 0xa2, 0xb1, - 0x06, 0x8a, 0x6e, 0xa3, 0xd0, 0xcc, 0xe8, 0xe9, 0x56, 0x1b, 0xfd, 0xc8, 0xe1, 0x85, 0xb9, 0x86, 0xb1, 0x08, 0x64, - 0x2e, 0x57, 0x3d, 0xf6, 0x97, 0x1f, 0x36, 0x2b, 0x0c, 0x5e, 0x91, 0xe9, 0xd0, 0x1d, 0xc7, 0x8c, 0xaf, 0xf2, 0xc4, - 0x31, 0x04, 0x99, 0x58, 0x2a, 0xdd, 0x70, 0x4c, 0x5c, 0x49, 0x9f, 0x89, 0x21, 0xdb, 0x0d, 0xcf, 0xcc, 0x85, 0x6e, - 0xb6, 0x7f, 0x38, 0xb7, 0x73, 0x4e, 0xb8, 0xd1, 0x4a, 0x1a, 0x6d, 0xd4, 0x33, 0x43, 0x55, 0x5d, 0x30, 0xbf, 0x87, - 0x4e, 0x4b, 0x8b, 0x9d, 0xab, 0x77, 0x37, 0x7c, 0x9d, 0xaf, 0x8c, 0xbf, 0xc5, 0xaa, 0xd0, 0x8a, 0x0c, 0xb7, 0x5b, - 0xc8, 0x9b, 0x33, 0x3d, 0xf4, 0x8a, 0x5c, 0xa8, 0x0e, 0x7f, 0x51, 0x57, 0x98, 0x07, 0x3b, 0xa3, 0x86, 0xf0, 0xe8, - 0xf7, 0x3a, 0x03, 0xe5, 0x1f, 0x4c, 0x4c, 0xe6, 0x2c, 0xb9, 0xa1, 0x85, 0x88, 0x7f, 0x7c, 0x21, 0x4c, 0xac, 0xaa, - 0x3d, 0x18, 0xc8, 0x9e, 0xa9, 0xb8, 0x07, 0xb7, 0x26, 0x7c, 0xcc, 0xd9, 0x28, 0xdd, 0x8b, 0x7e, 0x6c, 0x88, 0xc6, - 0x8f, 0xd1, 0x8f, 0xe0, 0xee, 0xec, 0x5e, 0x87, 0x2c, 0xe3, 0x42, 0xf8, 0x7b, 0xac, 0x87, 0xa5, 0x4a, 0x19, 0x6b, - 0xaf, 0x5b, 0x0e, 0x2f, 0xa4, 0xde, 0x64, 0xf1, 0x43, 0x47, 0xac, 0x6d, 0x0a, 0xd6, 0x21, 0x25, 0x85, 0x67, 0x57, - 0xcc, 0xad, 0x16, 0x73, 0x97, 0x5a, 0xc2, 0x5f, 0x5f, 0x3d, 0x2c, 0x55, 0xd0, 0x70, 0x10, 0xba, 0xd2, 0x16, 0x12, - 0x60, 0xe0, 0x52, 0xfa, 0x74, 0xba, 0x33, 0x89, 0xcc, 0xb2, 0x18, 0xde, 0x3d, 0xa8, 0x60, 0xfe, 0x3b, 0xdb, 0x08, - 0xab, 0x02, 0x97, 0x2b, 0x55, 0xd4, 0x4b, 0x49, 0x20, 0x00, 0x7d, 0xe9, 0x3d, 0x28, 0x2f, 0x8a, 0x6e, 0xa3, 0x21, - 0x41, 0x0b, 0x4b, 0xcd, 0xb5, 0x2a, 0xa6, 0xfb, 0xe1, 0xab, 0x86, 0xc1, 0x87, 0x77, 0x48, 0xdb, 0x78, 0x5a, 0x94, - 0x12, 0x6a, 0x77, 0xd0, 0x3e, 0x58, 0x65, 0x07, 0xe5, 0xdf, 0xc6, 0x14, 0xd9, 0xfc, 0x3e, 0xfb, 0x81, 0xba, 0x0e, - 0x07, 0xae, 0x60, 0xd5, 0x4b, 0x19, 0x05, 0x03, 0x56, 0x4e, 0x81, 0xda, 0x3b, 0xc9, 0x68, 0x36, 0x65, 0xa0, 0xee, - 0xb7, 0x45, 0xab, 0xb9, 0x3d, 0xa9, 0xfb, 0x0d, 0x19, 0x67, 0x1f, 0x61, 0x9c, 0x7d, 0x14, 0x78, 0xb1, 0x48, 0xf2, - 0x87, 0x8c, 0x35, 0x8e, 0x55, 0x53, 0xa0, 0xa3, 0x0e, 0x70, 0x67, 0xe0, 0xc0, 0x03, 0xb6, 0x28, 0x07, 0x07, 0xd4, - 0x59, 0xdc, 0xd3, 0x46, 0xe6, 0xbd, 0x3d, 0xa1, 0x76, 0x11, 0x0b, 0xdc, 0xac, 0x99, 0x69, 0x41, 0x6b, 0x85, 0x71, - 0x1e, 0x0f, 0x78, 0x9b, 0x67, 0xb5, 0xf8, 0x09, 0x1b, 0xd6, 0x54, 0xf5, 0x1b, 0x68, 0x8e, 0x6a, 0x41, 0x6e, 0x9e, - 0x18, 0x6f, 0x55, 0xd2, 0x8f, 0xa2, 0x81, 0xe5, 0x54, 0x88, 0x21, 0x19, 0xfd, 0xd6, 0x20, 0xb8, 0xd5, 0x5e, 0xad, - 0xb8, 0x47, 0x7c, 0x51, 0xf3, 0x56, 0x33, 0xb7, 0x00, 0xb4, 0x88, 0xa3, 0xf2, 0xde, 0x24, 0x02, 0xef, 0xdb, 0x32, - 0x42, 0xda, 0xb2, 0x6f, 0x9f, 0xae, 0x2c, 0x15, 0x9b, 0xef, 0xe8, 0x64, 0x90, 0x46, 0x76, 0x44, 0x11, 0xbe, 0x2e, - 0x21, 0x09, 0x57, 0x49, 0xd7, 0x2a, 0x93, 0x73, 0xa6, 0x52, 0x8e, 0xaf, 0x0b, 0x29, 0xf5, 0x95, 0xfd, 0x92, 0xb8, - 0xba, 0x93, 0x11, 0xf8, 0x7a, 0xc2, 0xf4, 0x3b, 0x5a, 0x4c, 0x18, 0xf8, 0x15, 0xf9, 0xdb, 0xb1, 0x94, 0x92, 0xcb, - 0x27, 0x22, 0xee, 0x53, 0x0c, 0xef, 0xae, 0x0e, 0xb0, 0x36, 0x21, 0x50, 0x4a, 0x5c, 0x84, 0x0b, 0xa2, 0x37, 0x85, - 0xbc, 0xbd, 0x8b, 0x0b, 0xec, 0x1c, 0x00, 0x4b, 0xa7, 0x49, 0x80, 0x7f, 0xf9, 0x98, 0x8f, 0xd5, 0x98, 0x53, 0xa3, - 0xeb, 0x77, 0xbf, 0x93, 0x6b, 0xa0, 0xb7, 0xa5, 0xa3, 0x60, 0xbf, 0x35, 0x80, 0x5c, 0xb8, 0x0b, 0x83, 0x8b, 0xaf, - 0xb0, 0xb6, 0x2c, 0x8c, 0x37, 0x16, 0x40, 0xef, 0x73, 0x06, 0x16, 0x6c, 0x98, 0x63, 0x0a, 0x8f, 0xd6, 0x4e, 0x98, - 0x0e, 0xa2, 0x82, 0x3c, 0x29, 0x9f, 0x25, 0xad, 0xd5, 0x7e, 0xcb, 0xc6, 0x70, 0x87, 0x91, 0x7c, 0xbb, 0x70, 0xe2, - 0xc0, 0x03, 0x32, 0x4d, 0x66, 0x9b, 0x7d, 0xe3, 0x23, 0x8f, 0xbc, 0x1e, 0xc7, 0xbb, 0x5a, 0x0a, 0xf3, 0xcd, 0x8a, - 0xae, 0x31, 0x84, 0xa2, 0x08, 0xfb, 0xfd, 0xaa, 0x62, 0x8a, 0x2a, 0x83, 0x36, 0x68, 0x58, 0xde, 0x88, 0x5f, 0xe0, - 0x8c, 0xa1, 0xf5, 0x42, 0xf6, 0x8e, 0xce, 0x3a, 0x9c, 0x39, 0xcc, 0x98, 0x12, 0x18, 0x95, 0x96, 0x05, 0x9d, 0x80, - 0xa3, 0x73, 0xf5, 0x41, 0x54, 0x5c, 0x1d, 0x2b, 0x00, 0x4f, 0x32, 0x85, 0x7f, 0xf2, 0x4d, 0xb0, 0xee, 0xb7, 0x6a, - 0x86, 0xa9, 0xbf, 0xe8, 0x6d, 0xd7, 0xf2, 0x65, 0x88, 0x23, 0x6d, 0x0c, 0xa1, 0x75, 0x6e, 0xef, 0x00, 0x45, 0x5c, - 0xd0, 0x8b, 0x54, 0xe3, 0x6b, 0xb5, 0x18, 0x9a, 0xf5, 0x35, 0xae, 0x63, 0xda, 0x20, 0x8a, 0x75, 0xd7, 0xc4, 0xd7, - 0xd5, 0x2b, 0xb0, 0x2a, 0x55, 0x70, 0x06, 0x09, 0x84, 0x55, 0x79, 0xd9, 0x90, 0x4a, 0x72, 0x69, 0x3a, 0x95, 0xa6, - 0xd3, 0x0a, 0xa1, 0x5c, 0x7a, 0x52, 0xde, 0xbf, 0x42, 0x08, 0x03, 0x53, 0x66, 0x07, 0x56, 0xa9, 0x2d, 0xac, 0x82, - 0x57, 0x2f, 0x36, 0xb0, 0x4a, 0xc2, 0xf1, 0x5c, 0xa2, 0x51, 0x51, 0xe1, 0x90, 0x21, 0x7d, 0x21, 0x16, 0x41, 0x02, - 0x60, 0xd1, 0xbb, 0xcc, 0xe5, 0x7d, 0x0f, 0x87, 0xc2, 0x9e, 0x64, 0x12, 0x4e, 0x37, 0xa1, 0x39, 0x3c, 0x0f, 0xac, - 0x7a, 0x1e, 0x21, 0x60, 0xe9, 0x39, 0x86, 0x67, 0xa1, 0xbf, 0xff, 0x1c, 0xad, 0xb3, 0x20, 0x4f, 0xff, 0x25, 0x4a, - 0x42, 0x63, 0xff, 0x39, 0x1e, 0x3a, 0x24, 0x0c, 0x07, 0xbe, 0x39, 0xc2, 0x0a, 0x07, 0xb7, 0x8a, 0xf8, 0x0c, 0xee, - 0xf0, 0xb1, 0x0e, 0x3d, 0x00, 0x2c, 0xa1, 0x38, 0x04, 0xf9, 0x06, 0x8a, 0x19, 0x1c, 0xd0, 0x64, 0x19, 0x5e, 0xe0, - 0x82, 0xd5, 0x42, 0x79, 0x7f, 0xdb, 0xf2, 0x52, 0x5a, 0xed, 0x92, 0xd7, 0x98, 0x03, 0x95, 0x9f, 0xe1, 0x85, 0xaf, - 0x30, 0xef, 0x55, 0xbb, 0x2f, 0x7c, 0xed, 0x80, 0x9e, 0x42, 0xc0, 0x48, 0xf7, 0x7b, 0x4d, 0xb8, 0xa7, 0xe8, 0x65, - 0x2e, 0x0e, 0xdb, 0x0e, 0xba, 0x17, 0x98, 0xab, 0xab, 0x2a, 0x6b, 0x0e, 0xa6, 0xd0, 0xe0, 0xa0, 0x0a, 0x67, 0x04, - 0xe6, 0xea, 0x45, 0x59, 0x70, 0x0e, 0xe2, 0x7d, 0x4f, 0x98, 0x9c, 0x32, 0x1a, 0xc0, 0x8b, 0xac, 0x7c, 0x74, 0xaa, - 0xc7, 0xc1, 0x65, 0xdc, 0xb0, 0x89, 0x2f, 0x84, 0x4f, 0x05, 0x56, 0xd2, 0x1a, 0x87, 0x46, 0x74, 0x44, 0xe7, 0x60, - 0xb6, 0x01, 0x14, 0xdc, 0x9d, 0x0f, 0x1b, 0x0b, 0x15, 0x3c, 0xc9, 0x5b, 0x7b, 0x41, 0x9b, 0x10, 0x67, 0xd2, 0x14, - 0xdc, 0x6d, 0x1b, 0x64, 0xf0, 0xe6, 0xb7, 0xff, 0x56, 0x58, 0x24, 0x18, 0x50, 0xa9, 0x49, 0x82, 0xf0, 0x04, 0xa5, - 0x91, 0x6e, 0xe5, 0x66, 0x02, 0xe9, 0x44, 0xd4, 0x8c, 0xba, 0x37, 0xce, 0x57, 0x47, 0x0d, 0x44, 0x45, 0x0d, 0x54, - 0x40, 0x0d, 0x64, 0x7d, 0xfb, 0x17, 0xb0, 0x10, 0x36, 0x42, 0x95, 0x08, 0x02, 0x22, 0xcc, 0xb5, 0xe1, 0x03, 0x8a, - 0x24, 0x84, 0xbc, 0x01, 0x54, 0x4c, 0xc9, 0x4b, 0x30, 0x1a, 0x87, 0xd7, 0x7b, 0xc0, 0xfd, 0xd2, 0x32, 0x0c, 0x9e, - 0x53, 0x30, 0xf9, 0x6f, 0x7d, 0x3e, 0x54, 0x2f, 0x57, 0x07, 0x21, 0xfc, 0x02, 0x62, 0x45, 0x38, 0xfe, 0xe2, 0x17, - 0x20, 0x9b, 0x0a, 0xcb, 0x83, 0x03, 0x09, 0x02, 0x3f, 0x44, 0x11, 0x0e, 0x78, 0x86, 0x97, 0xd9, 0x06, 0xd1, 0xf3, - 0xb3, 0x52, 0xd5, 0xac, 0x64, 0x30, 0xab, 0xc2, 0xd3, 0x38, 0xba, 0x26, 0x0c, 0x04, 0x17, 0x6a, 0xf7, 0x0d, 0x42, - 0xa0, 0x6c, 0xb9, 0x31, 0x74, 0xe9, 0x29, 0x98, 0x8f, 0xc6, 0xd1, 0x5b, 0x06, 0x0f, 0x0b, 0x1b, 0x93, 0x7f, 0xa6, - 0x59, 0xa6, 0x0d, 0xf3, 0xd8, 0x08, 0x9c, 0xd4, 0x29, 0x4a, 0x3e, 0x4b, 0x2e, 0xe2, 0xa8, 0x79, 0x19, 0xa1, 0x06, - 0xfc, 0xdb, 0xe0, 0xa8, 0x4b, 0x13, 0x3a, 0x1a, 0xf9, 0xe0, 0x37, 0x19, 0x31, 0x9b, 0x6c, 0xb5, 0x12, 0x15, 0x41, - 0x4f, 0xec, 0x06, 0x03, 0x56, 0xe2, 0x05, 0xb0, 0x0f, 0x96, 0x83, 0x25, 0xef, 0x44, 0xac, 0xfc, 0x29, 0x85, 0xc1, - 0xea, 0x39, 0x43, 0x08, 0x67, 0x41, 0xcc, 0xc6, 0xff, 0x7c, 0xa6, 0xe1, 0xfa, 0xf9, 0xf9, 0x3a, 0x46, 0x44, 0xfa, - 0x20, 0x72, 0x35, 0x76, 0x44, 0x04, 0x61, 0xcb, 0x74, 0xdf, 0x95, 0xf9, 0xc1, 0x5b, 0x57, 0x0f, 0x6c, 0xb8, 0x38, - 0x30, 0xa0, 0x46, 0x81, 0xd1, 0x0a, 0xce, 0x49, 0x39, 0x70, 0x50, 0x42, 0x68, 0x56, 0xc4, 0x53, 0x72, 0x09, 0x91, - 0xf0, 0x32, 0xd4, 0x05, 0xc3, 0x82, 0x40, 0x82, 0x9a, 0x82, 0x04, 0x95, 0xf9, 0xda, 0x23, 0x98, 0x75, 0x6e, 0x66, - 0x3b, 0x45, 0x5d, 0x17, 0xe4, 0xe7, 0x17, 0x1d, 0x8f, 0x80, 0xa5, 0x3d, 0x38, 0x28, 0x20, 0x82, 0x18, 0x50, 0xf0, - 0x52, 0x02, 0x0c, 0xc2, 0xf1, 0x15, 0x1b, 0x1a, 0xf0, 0xb9, 0x36, 0x5e, 0x07, 0xc6, 0xd6, 0xa7, 0x0c, 0x72, 0xf1, - 0xac, 0xda, 0xd3, 0x84, 0x90, 0xfd, 0x56, 0x4f, 0xa7, 0xdb, 0x11, 0x12, 0x7b, 0x1f, 0xb5, 0x09, 0x34, 0xe6, 0x48, - 0x77, 0xb5, 0x31, 0x5f, 0xd5, 0xf4, 0x88, 0xd5, 0x24, 0xa4, 0x0b, 0xd2, 0xe5, 0xf9, 0xb4, 0x67, 0x70, 0xc5, 0x2a, - 0x8d, 0x1c, 0x5c, 0x80, 0x3e, 0x1b, 0x10, 0xa0, 0x40, 0xa5, 0xa9, 0x44, 0x51, 0xc4, 0x45, 0x52, 0xb2, 0x61, 0x98, - 0x41, 0x98, 0xc2, 0x6a, 0x25, 0xe8, 0xc6, 0x1a, 0x00, 0xef, 0xcc, 0xec, 0x9f, 0xd2, 0x07, 0x9b, 0xae, 0xbd, 0x79, - 0x04, 0x10, 0x90, 0xfd, 0x76, 0xc9, 0xae, 0x8b, 0x8d, 0xca, 0x2c, 0xac, 0x65, 0x6c, 0xe5, 0xb6, 0x3d, 0xc6, 0xde, - 0x89, 0x6d, 0x3e, 0x01, 0x42, 0xd4, 0x96, 0x4c, 0x23, 0x44, 0x48, 0x2c, 0x62, 0x5d, 0x1b, 0xb2, 0xd1, 0x86, 0xc2, - 0x53, 0x89, 0x1c, 0xb8, 0x44, 0x13, 0x24, 0xdf, 0x71, 0x09, 0x0e, 0xe1, 0x85, 0x47, 0xf8, 0x5b, 0x60, 0x91, 0x0a, - 0xcc, 0xb0, 0x5c, 0xad, 0xa0, 0x9e, 0xc7, 0xfb, 0x6c, 0x33, 0x38, 0xa9, 0xdc, 0x18, 0xbb, 0xb4, 0x13, 0x8f, 0xcb, - 0x26, 0x24, 0xce, 0xa0, 0x5f, 0x5f, 0x11, 0xf5, 0xf6, 0xdb, 0xe9, 0x13, 0xff, 0x5e, 0x99, 0xdb, 0x81, 0xd8, 0xb0, - 0xde, 0x60, 0xf5, 0x01, 0xb4, 0xfc, 0x9f, 0xcc, 0x3f, 0x54, 0x16, 0xdc, 0x24, 0xa8, 0xcd, 0x45, 0xec, 0xb2, 0x2e, - 0x62, 0xa4, 0xb6, 0xb8, 0x3b, 0x84, 0xf8, 0x7f, 0xb6, 0xa2, 0x18, 0xf0, 0xa4, 0xe2, 0x9f, 0x63, 0xd4, 0x85, 0x50, - 0xd4, 0xd6, 0xc3, 0x06, 0x28, 0xed, 0x72, 0x5d, 0x89, 0x91, 0x21, 0x81, 0x7c, 0xeb, 0xc2, 0x0b, 0x9a, 0x93, 0x48, - 0x81, 0x9c, 0x1c, 0x44, 0x25, 0xcd, 0x36, 0x84, 0xb9, 0xee, 0x16, 0x8e, 0x99, 0xab, 0x0d, 0x5a, 0xc4, 0x2f, 0x80, - 0x9d, 0xe1, 0x46, 0xb2, 0x74, 0xe0, 0x53, 0x35, 0xf0, 0xf9, 0x35, 0x37, 0x14, 0x45, 0xa1, 0xde, 0x3b, 0xfb, 0xc8, - 0x1c, 0xfc, 0x4e, 0x03, 0xf1, 0x91, 0x3a, 0x1d, 0xc9, 0x46, 0xa8, 0x35, 0x67, 0xc7, 0xcb, 0x36, 0x23, 0x0c, 0x0a, - 0x1b, 0xbd, 0xaf, 0x42, 0x56, 0xb1, 0xb3, 0x53, 0x11, 0xcc, 0xe9, 0xab, 0xaa, 0x9c, 0x53, 0xb9, 0x65, 0x54, 0x4b, - 0x4d, 0x03, 0x44, 0xb8, 0xf2, 0x89, 0xe4, 0x51, 0x66, 0xc2, 0x3f, 0x18, 0x8c, 0xab, 0x47, 0x0a, 0x7f, 0xb4, 0x2b, - 0x76, 0xc8, 0x76, 0x74, 0xb8, 0x8d, 0xa0, 0x79, 0xa1, 0x82, 0x07, 0x1c, 0x95, 0x2c, 0x21, 0x52, 0xe4, 0x72, 0x5f, - 0xd5, 0x4c, 0xd9, 0xae, 0x23, 0x84, 0x90, 0xf6, 0x38, 0xeb, 0x86, 0x56, 0x0f, 0x3d, 0x52, 0x45, 0x39, 0xdc, 0xa2, - 0xb9, 0x2e, 0x40, 0x85, 0x11, 0x48, 0x97, 0x5f, 0xd8, 0x5d, 0x2a, 0x21, 0x7a, 0xf9, 0xda, 0x85, 0x30, 0x76, 0x56, - 0x96, 0xb8, 0x30, 0xa3, 0xb6, 0x61, 0x74, 0xdd, 0xc6, 0x70, 0x36, 0x30, 0x66, 0x1a, 0x94, 0xb4, 0x20, 0xd4, 0x75, - 0x97, 0x5e, 0x64, 0x26, 0xd0, 0x63, 0x4e, 0x68, 0x83, 0xe1, 0x29, 0xd1, 0x60, 0xd9, 0x54, 0x80, 0x05, 0xdf, 0xb2, - 0x48, 0xad, 0xcd, 0x26, 0x8b, 0x3f, 0xea, 0xd8, 0x3c, 0xed, 0x97, 0x57, 0xcc, 0x73, 0xe1, 0xa8, 0xdb, 0xf3, 0xcc, - 0xc7, 0xa3, 0x7b, 0xfa, 0xe6, 0xea, 0xc5, 0xcb, 0xd7, 0xaf, 0x56, 0xab, 0x36, 0x6b, 0xb6, 0x4f, 0xf0, 0x4f, 0xba, - 0x8c, 0x07, 0x5b, 0x46, 0x01, 0x3a, 0x38, 0xd8, 0xe7, 0xc6, 0x85, 0xe7, 0x0b, 0x9f, 0x43, 0xdc, 0x20, 0x3d, 0xc0, - 0x59, 0x51, 0xc6, 0x04, 0xb9, 0x8d, 0x7a, 0xd1, 0x5d, 0x04, 0x4a, 0xa8, 0x8a, 0xfc, 0x7d, 0xd8, 0x9c, 0xfd, 0x1e, - 0x04, 0x26, 0x82, 0xfa, 0x10, 0x01, 0x04, 0xe2, 0x95, 0xe2, 0x82, 0x30, 0x9f, 0x00, 0x51, 0xbc, 0x17, 0xc0, 0x99, - 0x9a, 0xa8, 0x55, 0x0b, 0x15, 0x17, 0x40, 0x12, 0x6d, 0x38, 0x4a, 0x7a, 0x64, 0x02, 0x78, 0x43, 0x50, 0x4a, 0xfb, - 0xab, 0x9b, 0x3b, 0x77, 0xa9, 0x1c, 0xf5, 0x5a, 0x69, 0x8e, 0xa7, 0xee, 0x73, 0x0a, 0x9f, 0xd3, 0xae, 0x3f, 0x1d, - 0xc4, 0x61, 0x8e, 0x17, 0x44, 0x1c, 0xfa, 0x67, 0x11, 0x97, 0xf3, 0x82, 0x7d, 0xe5, 0x72, 0xa1, 0xd2, 0xe5, 0x6d, - 0x2a, 0x93, 0xdb, 0xe6, 0xe8, 0x30, 0x2e, 0x92, 0xdb, 0xa6, 0x4a, 0x6e, 0x11, 0xbe, 0x4b, 0x65, 0x72, 0x67, 0x53, - 0xee, 0x9a, 0x0a, 0x6e, 0xbe, 0xb0, 0x80, 0x43, 0xd1, 0x16, 0x6d, 0x2c, 0x36, 0x8b, 0xda, 0x14, 0x57, 0x34, 0xc0, - 0xe0, 0xdf, 0x77, 0x6c, 0xfc, 0x30, 0x7c, 0x09, 0x2e, 0x4d, 0x9a, 0xc8, 0x4f, 0x20, 0xfd, 0xb4, 0x2a, 0x03, 0xf7, - 0x29, 0x69, 0x75, 0xa7, 0x17, 0xa2, 0xd9, 0xee, 0x36, 0x1a, 0x53, 0xd8, 0xbb, 0x19, 0xc9, 0x7d, 0xb1, 0x69, 0xc3, - 0xc4, 0xd7, 0xd9, 0xcf, 0x56, 0xab, 0xfd, 0x1c, 0x99, 0x0d, 0x37, 0x61, 0xb1, 0xee, 0x4f, 0x07, 0xb8, 0x85, 0x9f, - 0x67, 0x08, 0x2d, 0x59, 0x7f, 0x3a, 0x20, 0xac, 0x3f, 0x6d, 0xb4, 0x07, 0xd6, 0xd0, 0xce, 0x6c, 0xc5, 0x35, 0x84, - 0xd0, 0x9c, 0x0e, 0x8e, 0x4c, 0x49, 0xe9, 0xf2, 0xed, 0x17, 0xad, 0x02, 0xfa, 0xa9, 0x5a, 0xf0, 0x32, 0x89, 0x3b, - 0xd0, 0x17, 0xbd, 0xb0, 0x4f, 0xb7, 0x16, 0xe4, 0xf8, 0xa8, 0x72, 0xb5, 0xa7, 0x08, 0x9b, 0x9e, 0xd4, 0x61, 0x71, - 0x68, 0x9a, 0x71, 0x5d, 0x4a, 0xf7, 0x1d, 0x6a, 0x46, 0x3e, 0x3a, 0x58, 0x00, 0x82, 0x54, 0xf0, 0xc8, 0x0a, 0x17, - 0x4e, 0x29, 0x84, 0x8b, 0x83, 0xca, 0x16, 0x4c, 0x72, 0xd2, 0xea, 0xe6, 0xc6, 0xd2, 0x3f, 0x77, 0x11, 0x4d, 0x29, - 0xa6, 0x24, 0xf3, 0x25, 0x73, 0x03, 0x16, 0xba, 0x49, 0x79, 0xa6, 0xa0, 0x57, 0x1a, 0xe0, 0x11, 0x81, 0x78, 0x48, - 0xdd, 0xc2, 0x18, 0x78, 0xc5, 0xd3, 0x66, 0xd1, 0x67, 0x03, 0x74, 0x74, 0x8c, 0x69, 0xff, 0x53, 0x36, 0x6f, 0xc3, - 0x63, 0x81, 0x9f, 0x06, 0x64, 0xda, 0x94, 0x65, 0x82, 0x80, 0x84, 0x51, 0x53, 0x1e, 0xc2, 0x5e, 0x42, 0x38, 0xb3, - 0x15, 0xb3, 0x3e, 0x1b, 0x34, 0xa7, 0x65, 0xc5, 0x8e, 0xaf, 0xd8, 0x90, 0x65, 0x82, 0xad, 0xd8, 0x70, 0x15, 0xc3, - 0xd7, 0x19, 0x0c, 0x08, 0x42, 0x00, 0x30, 0x00, 0x80, 0x46, 0x41, 0x34, 0x5f, 0xac, 0x88, 0xdf, 0xec, 0xf6, 0x1e, - 0xbf, 0x05, 0x16, 0x68, 0xb5, 0xfd, 0xbf, 0x0b, 0x65, 0xc0, 0x9e, 0xb2, 0x30, 0x31, 0x73, 0x0b, 0xab, 0xa2, 0x03, - 0xa8, 0x94, 0x08, 0x53, 0x18, 0xc8, 0xec, 0x67, 0x06, 0x6a, 0x81, 0xd6, 0x20, 0xef, 0xeb, 0x41, 0x33, 0x83, 0x23, - 0x06, 0xde, 0xa1, 0x21, 0x53, 0x63, 0x4c, 0x18, 0xe7, 0x30, 0xc5, 0xcc, 0x80, 0x67, 0x9a, 0xb6, 0xd6, 0xd2, 0xc8, - 0x72, 0xbd, 0xbc, 0xf7, 0xb7, 0x8e, 0x55, 0xbf, 0x68, 0xb6, 0x07, 0x68, 0x9f, 0x10, 0xfb, 0x31, 0x80, 0x4d, 0xe6, - 0x52, 0x1b, 0xe6, 0xfb, 0xa8, 0x93, 0xda, 0x4f, 0xf8, 0x33, 0x58, 0x9b, 0x1d, 0x00, 0x3a, 0x32, 0x6c, 0xd6, 0x5f, - 0xd6, 0x54, 0x5e, 0x1f, 0x77, 0x46, 0xa9, 0xdc, 0xf5, 0xee, 0x74, 0xa0, 0x29, 0x0e, 0xbd, 0xf5, 0x70, 0xf9, 0x50, - 0x0f, 0x01, 0x33, 0x06, 0x73, 0xcb, 0x8c, 0xbe, 0x17, 0x22, 0xb9, 0x20, 0x12, 0x4b, 0x83, 0x35, 0x0c, 0xf6, 0xd6, - 0xc1, 0x81, 0xa9, 0xc6, 0x1a, 0xf0, 0x3c, 0x29, 0x02, 0xc1, 0xc0, 0x47, 0x50, 0x06, 0x34, 0x51, 0xe6, 0x36, 0x9c, - 0x7c, 0x64, 0xee, 0x17, 0x2e, 0x6f, 0x1f, 0x0b, 0xa7, 0x6d, 0x35, 0xd7, 0xe3, 0x65, 0x81, 0xbb, 0xf2, 0x5e, 0xd2, - 0x2a, 0xb8, 0x91, 0xbd, 0xc9, 0x53, 0xe6, 0x6e, 0xdd, 0x97, 0xea, 0xec, 0x6e, 0xa6, 0x53, 0x36, 0xd3, 0xd9, 0x6e, - 0x26, 0xd4, 0xcc, 0x7c, 0xcb, 0x2a, 0xd2, 0x9c, 0xac, 0x89, 0x9a, 0x53, 0xf1, 0x13, 0x9d, 0x83, 0x76, 0x94, 0xdb, - 0x7b, 0x55, 0x38, 0xb9, 0x72, 0x72, 0xb9, 0x9f, 0x1b, 0xe2, 0x8a, 0xcc, 0x85, 0x3a, 0x04, 0x78, 0x79, 0x51, 0x3e, - 0x3e, 0xc0, 0xa5, 0xf8, 0x55, 0x8e, 0x5c, 0x94, 0x53, 0x21, 0xb5, 0x14, 0x2c, 0x42, 0x06, 0x55, 0x5d, 0x0c, 0xec, - 0xa5, 0xdd, 0x7b, 0xa2, 0xc7, 0xfb, 0x55, 0xc4, 0xbc, 0x81, 0x79, 0xee, 0xe3, 0x7b, 0x9a, 0x62, 0xa7, 0x26, 0xce, - 0xc8, 0x87, 0x2c, 0xce, 0x41, 0x36, 0xeb, 0x57, 0xaf, 0xfd, 0x36, 0xda, 0xb8, 0x68, 0xc6, 0xa2, 0x67, 0x9e, 0x38, - 0xf9, 0xa1, 0x30, 0xc6, 0x01, 0xd6, 0xd1, 0x1f, 0x61, 0x6a, 0xc1, 0x9e, 0x25, 0x9e, 0x42, 0x27, 0xb7, 0x36, 0xed, - 0x2e, 0x4c, 0xbb, 0x33, 0x69, 0x1d, 0x28, 0x07, 0xa4, 0xd9, 0x95, 0xe9, 0xdc, 0xf9, 0xef, 0x3b, 0x78, 0xe9, 0x76, - 0x0d, 0x91, 0xb8, 0xe7, 0x8f, 0x8c, 0x31, 0xc4, 0x1b, 0xb0, 0x11, 0x55, 0x07, 0x07, 0x7f, 0x38, 0xef, 0xdb, 0x4a, - 0xee, 0xfb, 0x56, 0x38, 0xb0, 0x0d, 0xa6, 0xd2, 0xe5, 0x8d, 0x64, 0xb6, 0x00, 0xbb, 0xce, 0xfd, 0x6f, 0xc4, 0xc3, - 0x17, 0x21, 0xd3, 0x62, 0x5d, 0xc5, 0x5f, 0xc9, 0x51, 0xe9, 0x21, 0xaa, 0x21, 0x02, 0x69, 0x65, 0x5d, 0x1a, 0x9a, - 0x8e, 0x5e, 0x4d, 0xe9, 0x48, 0xde, 0xbc, 0x95, 0x52, 0x0f, 0xec, 0x8b, 0xdc, 0x3a, 0x81, 0x47, 0x0b, 0x6b, 0x0c, - 0xcd, 0x5d, 0xe9, 0x9d, 0x64, 0x03, 0xa2, 0xd6, 0xc7, 0x1d, 0x4a, 0x22, 0xb1, 0xa8, 0xee, 0x42, 0x38, 0xdc, 0x85, - 0x60, 0x5e, 0x06, 0x6d, 0x83, 0xd8, 0xed, 0x2e, 0x68, 0x1b, 0x38, 0x75, 0xdb, 0xc0, 0xed, 0xc1, 0x60, 0x61, 0xef, - 0xc3, 0xcb, 0xb1, 0x1c, 0x0b, 0x7f, 0x4d, 0x66, 0x1f, 0x00, 0x02, 0xb5, 0x0f, 0x2b, 0x9e, 0x38, 0x10, 0x24, 0xce, - 0x70, 0xf4, 0x3d, 0x67, 0x37, 0xd6, 0x72, 0x78, 0x36, 0x5f, 0x68, 0x36, 0x32, 0x77, 0xd4, 0xa0, 0xe2, 0xab, 0xfb, - 0x79, 0xfd, 0x94, 0xd5, 0x74, 0xe3, 0xf7, 0x20, 0x8c, 0x84, 0x53, 0x76, 0x18, 0x85, 0x84, 0x0d, 0x66, 0x55, 0xc6, - 0x6b, 0xfb, 0x0d, 0xe2, 0x3d, 0x68, 0x13, 0x4e, 0xb0, 0xa8, 0x5d, 0x50, 0x45, 0xd8, 0xc6, 0x1b, 0x0b, 0xa2, 0x3c, - 0xbc, 0xd9, 0x32, 0x9a, 0x5e, 0xae, 0x21, 0xd0, 0x71, 0x2f, 0x6a, 0x46, 0x0d, 0x96, 0xba, 0xa0, 0xcc, 0x3e, 0xc2, - 0xb8, 0xba, 0x38, 0x31, 0x71, 0xda, 0x4b, 0xbd, 0xfa, 0x6f, 0x19, 0x18, 0xe0, 0x0b, 0xf0, 0x12, 0x0b, 0xa3, 0xbb, - 0xf6, 0x75, 0x03, 0xea, 0xcb, 0x06, 0x1b, 0xa0, 0xd5, 0xaa, 0x55, 0x3e, 0x03, 0xe5, 0xae, 0xb9, 0x84, 0xbd, 0xe6, - 0x12, 0xee, 0x9a, 0x4b, 0xf8, 0x6b, 0x2e, 0x61, 0xae, 0xb9, 0x84, 0xbf, 0xe6, 0xf2, 0x20, 0xfc, 0x33, 0x88, 0xe3, - 0x18, 0x73, 0x88, 0xab, 0xa8, 0x6d, 0x64, 0x3c, 0xb8, 0xf0, 0xdc, 0x67, 0x89, 0x2a, 0x97, 0x3f, 0x8c, 0x21, 0xb7, - 0x65, 0x2b, 0x61, 0xdc, 0xa6, 0x98, 0x82, 0xc8, 0xe9, 0x07, 0x07, 0xa5, 0xbb, 0x33, 0xf8, 0xa8, 0xa7, 0x1c, 0x2f, - 0xad, 0x13, 0xed, 0x1f, 0xa0, 0x93, 0x37, 0xbf, 0x3e, 0xa6, 0x72, 0x4d, 0x84, 0x33, 0xb9, 0xdf, 0x6f, 0x7b, 0x4a, - 0xf1, 0x67, 0x66, 0xc2, 0x93, 0xf3, 0x44, 0x1b, 0x11, 0x04, 0x21, 0x4a, 0x14, 0xce, 0x88, 0xfc, 0x7f, 0xd9, 0x7b, - 0xd7, 0xe5, 0xb6, 0x91, 0x2c, 0x5d, 0xf4, 0x55, 0x24, 0x86, 0xcd, 0x02, 0xcc, 0x24, 0x45, 0x79, 0xef, 0x99, 0x88, - 0x03, 0x2a, 0xc5, 0xf0, 0xa5, 0xdc, 0xe5, 0xee, 0xf2, 0xa5, 0x2d, 0x77, 0x75, 0x55, 0x33, 0x78, 0x58, 0x10, 0x90, - 0x14, 0xe0, 0x02, 0x01, 0x16, 0x00, 0x4a, 0xa4, 0x49, 0xbc, 0xfb, 0x8e, 0xb5, 0x56, 0x5e, 0x41, 0x50, 0x76, 0xcf, - 0xec, 0xf9, 0x75, 0xce, 0x1f, 0x5b, 0x4c, 0x24, 0x12, 0x79, 0xcf, 0x95, 0xeb, 0xf2, 0x7d, 0xb4, 0xde, 0x55, 0x28, - 0x3c, 0xaa, 0xa2, 0x94, 0x5b, 0xc9, 0xab, 0x0c, 0x82, 0xd8, 0xd1, 0x0b, 0xc3, 0x9f, 0x40, 0x08, 0x41, 0x84, 0x09, - 0xbf, 0x0e, 0x33, 0xda, 0xce, 0x22, 0x9d, 0xf4, 0xdb, 0x30, 0xc3, 0x0d, 0xac, 0xe4, 0xe7, 0xaa, 0xcf, 0xf6, 0xdb, - 0x20, 0x64, 0xbb, 0x20, 0x62, 0xb7, 0xc5, 0x36, 0x28, 0x6d, 0x5f, 0x10, 0x65, 0xf8, 0x5b, 0x7a, 0xbd, 0x3c, 0x84, - 0x78, 0x9f, 0x5e, 0x9a, 0x9f, 0xa5, 0xad, 0x28, 0xc0, 0x7d, 0x84, 0x1e, 0xd5, 0x81, 0x60, 0x27, 0x3c, 0xe1, 0x01, - 0x9c, 0xac, 0x66, 0x15, 0x7f, 0x92, 0x82, 0x38, 0x51, 0x70, 0x08, 0xb8, 0xda, 0xde, 0xa4, 0x5f, 0xc1, 0xf0, 0xa5, - 0x83, 0x2d, 0x87, 0xb7, 0xc5, 0xb6, 0xc7, 0x4a, 0xfe, 0x11, 0xd8, 0xb7, 0x7a, 0x32, 0x56, 0xb7, 0x07, 0xce, 0xba, - 0x94, 0xa2, 0xe3, 0x4d, 0x71, 0x78, 0x7b, 0x3e, 0xdb, 0x6f, 0x83, 0x88, 0xed, 0x82, 0x0c, 0x6b, 0x9d, 0x34, 0xfc, - 0xaf, 0xb4, 0x75, 0xb0, 0x18, 0x61, 0xff, 0x97, 0xf5, 0xc0, 0x4b, 0x48, 0x0d, 0x05, 0x2e, 0x06, 0x1b, 0x8e, 0xd6, - 0x76, 0x99, 0x06, 0x6e, 0x6a, 0xd0, 0xeb, 0x7b, 0x0a, 0x51, 0x5e, 0x32, 0x9a, 0x1b, 0xc1, 0xba, 0x31, 0xe4, 0xe2, - 0x70, 0xdc, 0x2c, 0x87, 0xbc, 0xa4, 0xe9, 0x34, 0x08, 0xa5, 0x3b, 0xcb, 0x1a, 0x92, 0x28, 0xfb, 0x20, 0xd4, 0xae, - 0x2d, 0xfb, 0x6d, 0x60, 0xfb, 0xf2, 0x47, 0xc3, 0xd8, 0xbf, 0x58, 0x3e, 0x13, 0xd2, 0x45, 0x3c, 0x07, 0x41, 0xd4, - 0x7e, 0x9e, 0x0d, 0x37, 0xfe, 0xc5, 0xfa, 0x99, 0x50, 0x7e, 0xe3, 0xb9, 0x2d, 0x87, 0xd4, 0x59, 0x0b, 0x5f, 0x18, - 0x0f, 0x0f, 0xae, 0x0c, 0x6d, 0x87, 0x83, 0xd0, 0x7f, 0x9b, 0x35, 0x82, 0x1b, 0x1b, 0xda, 0xe7, 0x0b, 0x1f, 0xb6, - 0x36, 0x1a, 0x6b, 0x8a, 0xe9, 0x16, 0xfa, 0x37, 0x99, 0x2d, 0xed, 0x69, 0x54, 0xf2, 0xe2, 0xd4, 0x34, 0x62, 0x21, - 0x0c, 0x18, 0xfa, 0xc9, 0x7c, 0x04, 0xd5, 0xdc, 0xf1, 0x08, 0x64, 0xf2, 0x81, 0x1e, 0xac, 0x49, 0xad, 0xfa, 0x6b, - 0x98, 0xc9, 0xff, 0x23, 0x15, 0x16, 0xa3, 0xbb, 0x6d, 0x98, 0xa9, 0x3f, 0x22, 0xf9, 0x07, 0xcb, 0xf9, 0x2e, 0xf5, - 0x42, 0xed, 0xc7, 0xc2, 0x0a, 0x0c, 0x4a, 0x54, 0x0d, 0xe8, 0x81, 0x08, 0xaa, 0x32, 0x48, 0x33, 0xac, 0xce, 0x41, - 0xbf, 0x7b, 0x5a, 0x75, 0x24, 0x87, 0xb4, 0x56, 0x43, 0x2a, 0x98, 0x2a, 0x35, 0xc8, 0x0f, 0x87, 0xbb, 0x94, 0xe9, - 0x32, 0xe0, 0x92, 0x7e, 0x97, 0x2a, 0xa5, 0xf0, 0x9f, 0x08, 0x40, 0xe7, 0xe0, 0x1e, 0x5f, 0x8e, 0x81, 0x34, 0xc3, - 0xc2, 0x6f, 0xcd, 0x8e, 0xaf, 0x49, 0xb8, 0x4d, 0x82, 0x8b, 0x01, 0xce, 0xd1, 0x55, 0x58, 0xde, 0xa5, 0x10, 0x41, - 0x55, 0x42, 0x7d, 0x2b, 0xd3, 0xa0, 0xb4, 0xd5, 0x20, 0xac, 0x49, 0xa8, 0x33, 0xc9, 0x46, 0xa5, 0xed, 0x46, 0x61, - 0xb6, 0x88, 0xeb, 0x19, 0x61, 0xcd, 0xd9, 0x4c, 0x35, 0x30, 0x69, 0x38, 0x6e, 0x1a, 0xad, 0x45, 0x85, 0x9a, 0xc2, - 0xbc, 0xc6, 0x55, 0xa5, 0xaa, 0xbb, 0x39, 0xb5, 0x94, 0x96, 0xed, 0x55, 0x37, 0xc9, 0x86, 0x5c, 0x86, 0x32, 0x0c, - 0x36, 0x72, 0x04, 0x13, 0x48, 0x92, 0x33, 0x7f, 0x23, 0xff, 0x50, 0x9b, 0xae, 0x05, 0xcc, 0x31, 0x66, 0xd9, 0xb0, - 0xa0, 0x57, 0xe0, 0x1e, 0x68, 0xa5, 0xe7, 0xd3, 0xec, 0x22, 0x0f, 0x92, 0x61, 0xa1, 0x97, 0x4d, 0xc6, 0xff, 0x14, - 0x46, 0x9a, 0xcc, 0x58, 0xc9, 0x22, 0xdb, 0xd5, 0x29, 0x71, 0x1e, 0x27, 0xb0, 0x3d, 0x9a, 0xde, 0xf2, 0x7d, 0x06, - 0x51, 0x41, 0xa0, 0x60, 0xc6, 0x7c, 0xd9, 0xc5, 0x73, 0xdf, 0x67, 0x96, 0xa9, 0xfb, 0x70, 0x30, 0x66, 0x6c, 0xbf, - 0xdf, 0xcf, 0xfb, 0x7d, 0x35, 0xdf, 0xfa, 0xfd, 0xe4, 0xda, 0xfc, 0xed, 0x01, 0x83, 0x82, 0x9c, 0x88, 0xa6, 0x42, - 0x04, 0xff, 0x90, 0x3c, 0x43, 0x32, 0xba, 0xe3, 0x3e, 0xb7, 0x9c, 0x2d, 0xab, 0x23, 0x10, 0xcc, 0xc3, 0xe1, 0x52, - 0x81, 0x5d, 0x4b, 0x14, 0x09, 0x59, 0xfe, 0x33, 0x30, 0x9e, 0xb9, 0x0f, 0xb0, 0x64, 0x00, 0xc2, 0x56, 0x79, 0xba, - 0xde, 0xf3, 0x55, 0xf0, 0x4e, 0xc7, 0xbb, 0xc6, 0x8a, 0x0c, 0xc4, 0x2d, 0xb0, 0x11, 0x6b, 0xed, 0x01, 0x39, 0x53, - 0x80, 0xe3, 0xc5, 0xe1, 0x70, 0x2e, 0x7f, 0xe9, 0x66, 0xeb, 0x04, 0x2a, 0x05, 0x6e, 0x8f, 0x4e, 0x0e, 0xfe, 0x3b, - 0xd0, 0x0c, 0xca, 0x61, 0x5e, 0x6f, 0x7f, 0x67, 0x4e, 0x7e, 0x7a, 0x8a, 0x7f, 0xc2, 0x43, 0x74, 0xfa, 0xed, 0xde, - 0xfc, 0x41, 0x51, 0x79, 0x38, 0xa8, 0xc5, 0x7f, 0xce, 0x79, 0x05, 0xbf, 0xf0, 0x4d, 0x60, 0x36, 0x99, 0x7a, 0x27, - 0xdf, 0xe4, 0x39, 0x53, 0xaf, 0xf1, 0x8a, 0xc9, 0x77, 0x38, 0x9c, 0x8b, 0x51, 0xbd, 0x1d, 0x39, 0xd1, 0x4e, 0x39, - 0xc6, 0xc1, 0xe0, 0xbf, 0x88, 0xb6, 0x09, 0x01, 0x86, 0xd4, 0x2d, 0x69, 0x66, 0xe3, 0xca, 0x12, 0xcf, 0xd2, 0xf9, - 0xe5, 0xa4, 0x2e, 0x77, 0x5a, 0xf1, 0xb4, 0x07, 0x16, 0xb7, 0x35, 0x78, 0x01, 0xdc, 0x5b, 0x6c, 0x5d, 0x29, 0x38, - 0x5c, 0x40, 0x9c, 0xe2, 0x04, 0x44, 0xd0, 0x7e, 0x5f, 0xe2, 0xbd, 0x82, 0x3e, 0xe9, 0x47, 0x08, 0x86, 0xfc, 0x59, - 0x02, 0xee, 0x7a, 0xbd, 0x1a, 0xe3, 0x7b, 0x29, 0x04, 0xd7, 0x67, 0x1a, 0x80, 0x16, 0xfc, 0x2e, 0x1f, 0xcb, 0xe9, - 0x37, 0x11, 0x78, 0xb6, 0xec, 0x4d, 0x94, 0xbb, 0x0d, 0x4f, 0xfb, 0x47, 0x0b, 0x01, 0x58, 0x8a, 0x67, 0x4a, 0xb0, - 0x20, 0xa7, 0x98, 0x8b, 0xff, 0x17, 0x7c, 0xc4, 0x7c, 0x4f, 0xba, 0x88, 0xad, 0xb7, 0x4f, 0x2e, 0x0c, 0x24, 0xd0, - 0x74, 0x00, 0x7e, 0xbc, 0x0a, 0xe8, 0xca, 0xf8, 0xf9, 0x59, 0xd6, 0x63, 0x7d, 0xfc, 0xa7, 0xe0, 0x3e, 0xfd, 0x4c, - 0xe1, 0xa3, 0xc3, 0x71, 0x95, 0x8e, 0x76, 0x94, 0x82, 0xe8, 0xe8, 0xf6, 0xf9, 0x94, 0x67, 0xdf, 0x55, 0x40, 0x6e, - 0x39, 0x6a, 0x4f, 0x05, 0x60, 0xb1, 0xa5, 0x23, 0xf0, 0x69, 0x96, 0x4f, 0xc8, 0xf7, 0x7a, 0x2a, 0xae, 0x2e, 0x75, - 0xba, 0xb8, 0x1e, 0x4f, 0xe1, 0x7f, 0x20, 0xf6, 0xb0, 0x4c, 0x91, 0x1d, 0xbb, 0x2e, 0x7e, 0x10, 0x6f, 0x6b, 0x3b, - 0xfa, 0x63, 0x07, 0x91, 0x8e, 0x7b, 0x72, 0xa1, 0xbe, 0x84, 0x54, 0x72, 0xa1, 0x6e, 0x20, 0x76, 0xa1, 0xc6, 0x3b, - 0x2e, 0x62, 0xad, 0xbf, 0xab, 0x51, 0xb0, 0x12, 0x70, 0xa6, 0xbd, 0x03, 0x83, 0x0d, 0xac, 0x5b, 0x96, 0xc1, 0xdf, - 0x70, 0x4d, 0x13, 0xb8, 0x61, 0x91, 0xf5, 0xde, 0x60, 0x2b, 0xbd, 0x03, 0x47, 0xcb, 0xc4, 0xb9, 0x94, 0x64, 0x65, - 0x8b, 0x8c, 0xab, 0x47, 0x21, 0x55, 0xd3, 0xfd, 0xad, 0xa8, 0x1f, 0x84, 0xc8, 0x83, 0x55, 0xca, 0xa2, 0x62, 0x05, - 0x32, 0x7b, 0xf0, 0xf7, 0x90, 0x91, 0xa3, 0x1c, 0x38, 0x0a, 0xfd, 0xbd, 0x09, 0x74, 0x9e, 0xbf, 0x86, 0x3a, 0x8f, - 0x04, 0x5b, 0xa9, 0x87, 0xc2, 0xca, 0x0b, 0x88, 0x0e, 0xb6, 0x30, 0x56, 0x79, 0x12, 0x2a, 0x36, 0x65, 0x22, 0x8f, - 0x83, 0x5a, 0x02, 0xc6, 0x0a, 0x82, 0x39, 0xcb, 0xa5, 0x0b, 0x52, 0xd5, 0xe8, 0x61, 0x91, 0xb9, 0x9f, 0x0a, 0xca, - 0xff, 0x54, 0xe5, 0x84, 0xeb, 0xcb, 0x10, 0xe0, 0x68, 0x9f, 0x82, 0x28, 0x31, 0xd6, 0x2f, 0x5a, 0xbc, 0x93, 0x99, - 0xb3, 0xa9, 0xed, 0x25, 0xc8, 0xd8, 0x0e, 0xbf, 0x42, 0x68, 0xb5, 0x50, 0x64, 0xd1, 0x70, 0xc1, 0x74, 0x7b, 0x4a, - 0xab, 0xee, 0x61, 0xc3, 0xb3, 0xd2, 0x43, 0xa5, 0xbe, 0x8d, 0x09, 0x2c, 0xab, 0x94, 0xe1, 0xdb, 0x09, 0x55, 0x27, - 0x06, 0x15, 0xeb, 0x86, 0x2d, 0xe1, 0x10, 0x8b, 0x49, 0x63, 0x9d, 0x0d, 0x78, 0xc4, 0x12, 0xf8, 0x67, 0xc3, 0xc7, - 0x6c, 0xc9, 0xa3, 0xc9, 0xe6, 0x6a, 0xd9, 0xef, 0x97, 0x5e, 0xe8, 0xd5, 0xb3, 0xec, 0x69, 0x34, 0x9f, 0xe5, 0x73, - 0x1f, 0x15, 0x17, 0x93, 0xc1, 0x60, 0xe3, 0x67, 0xc3, 0x21, 0x4b, 0x86, 0xc3, 0x49, 0xf6, 0x14, 0x5e, 0x7b, 0xca, - 0x23, 0xb5, 0xa4, 0x92, 0xab, 0x0c, 0xf6, 0xf7, 0x01, 0x8f, 0x7c, 0xd6, 0xf9, 0x69, 0xd9, 0x74, 0xe9, 0x7e, 0x66, - 0x75, 0x40, 0xa9, 0x3b, 0xc0, 0xc6, 0xdb, 0x06, 0x1d, 0xf9, 0xb7, 0x3b, 0xa4, 0xd4, 0x4d, 0x06, 0x60, 0x37, 0x1a, - 0xe0, 0x90, 0xa9, 0x5e, 0x8a, 0xac, 0x5e, 0xca, 0x54, 0x2f, 0xc9, 0xca, 0x25, 0x58, 0x48, 0x4c, 0x95, 0xdb, 0xc8, - 0xca, 0x2d, 0x1b, 0xae, 0x87, 0x83, 0xad, 0x15, 0x97, 0xcd, 0x1d, 0xdc, 0x17, 0x56, 0x14, 0xf8, 0x7f, 0xcb, 0x16, - 0xec, 0x5e, 0x1e, 0x03, 0xef, 0xd0, 0x31, 0x09, 0x2e, 0x10, 0xf7, 0xec, 0x16, 0xec, 0xb0, 0xf0, 0x17, 0x5c, 0x27, - 0xc7, 0x6c, 0x87, 0x8f, 0x42, 0xaf, 0x60, 0xb7, 0x3e, 0x01, 0xed, 0x82, 0xad, 0x01, 0xb2, 0xb1, 0x2d, 0x3e, 0xba, - 0x3b, 0x1c, 0xde, 0x79, 0x3e, 0x7b, 0xc0, 0x1f, 0xe7, 0x77, 0x87, 0xc3, 0xce, 0x33, 0xea, 0xbd, 0x1b, 0x9e, 0xb0, - 0x0f, 0x3c, 0x99, 0xdc, 0x5c, 0xf1, 0x78, 0x32, 0x18, 0xdc, 0xf8, 0x0b, 0x5e, 0xcf, 0x6e, 0x40, 0x3b, 0x70, 0xbe, - 0x90, 0xba, 0x66, 0xef, 0x96, 0x67, 0xde, 0x02, 0xc7, 0xe6, 0x16, 0x8e, 0xde, 0x7e, 0xdf, 0xbb, 0xe3, 0x91, 0x77, - 0x4b, 0x2a, 0xa6, 0x15, 0x57, 0x1c, 0x6f, 0x5b, 0xdc, 0x4f, 0x57, 0x3c, 0x84, 0x47, 0x58, 0x95, 0xe9, 0x4d, 0xf0, - 0xc1, 0x67, 0x2b, 0xcd, 0x02, 0xf7, 0x80, 0x39, 0xd6, 0x64, 0x27, 0x34, 0x13, 0x7f, 0x85, 0xfd, 0x73, 0xa3, 0xfa, - 0x87, 0xe6, 0x7f, 0xa9, 0xfb, 0x09, 0xdc, 0xbe, 0xc8, 0x82, 0xc4, 0x3e, 0xf0, 0x1b, 0x76, 0xcf, 0x0d, 0xdb, 0xec, - 0x99, 0x29, 0xfb, 0x44, 0xa9, 0xf1, 0x23, 0xa5, 0xae, 0x2d, 0xc3, 0x4a, 0xe6, 0xee, 0xcb, 0x08, 0x1c, 0x0e, 0xc8, - 0x4f, 0x77, 0x88, 0x83, 0xd0, 0xba, 0xc9, 0x6a, 0xae, 0x28, 0xe7, 0x42, 0x5b, 0x66, 0x5e, 0x0e, 0x2c, 0x66, 0x29, - 0x85, 0xc6, 0x02, 0x00, 0xc1, 0xa4, 0xd0, 0xda, 0x7b, 0x19, 0x40, 0x4e, 0xd0, 0xf0, 0xc7, 0xe6, 0xaa, 0x28, 0x6b, - 0xd9, 0x92, 0x10, 0x65, 0xbb, 0x1e, 0x5e, 0x22, 0x64, 0x5a, 0xbf, 0x7f, 0x4e, 0x24, 0x6b, 0x93, 0xea, 0xaa, 0x46, - 0x4b, 0x40, 0x45, 0x96, 0x80, 0x89, 0x5f, 0x69, 0x3e, 0x01, 0x78, 0xd2, 0xf1, 0xa0, 0x7a, 0xca, 0x6b, 0x26, 0x88, - 0x6c, 0xa3, 0xf2, 0x27, 0xc5, 0x35, 0x92, 0x11, 0x14, 0x4f, 0x6b, 0x95, 0xb1, 0x30, 0xcc, 0x03, 0x05, 0xe4, 0xdd, - 0xbb, 0x53, 0xdf, 0xda, 0x1f, 0x3b, 0xf6, 0x6c, 0xad, 0x42, 0x2d, 0xd4, 0x14, 0x2e, 0x39, 0x44, 0x57, 0x90, 0x81, - 0x42, 0xc6, 0x93, 0xd7, 0x83, 0xcb, 0x49, 0x74, 0xc5, 0x05, 0x3a, 0xe3, 0xeb, 0x9b, 0x6e, 0x3a, 0x8b, 0x9e, 0x56, - 0xf3, 0x09, 0x29, 0xc9, 0x0e, 0x87, 0x6c, 0x54, 0xd5, 0xc5, 0x7a, 0x1a, 0xca, 0x9f, 0x1e, 0x82, 0xaf, 0x17, 0xd4, - 0x6b, 0xb2, 0x4a, 0xf5, 0x53, 0xaa, 0x94, 0x17, 0x0d, 0x2f, 0xfd, 0xa7, 0x95, 0xdc, 0xf7, 0x80, 0xb4, 0x96, 0x97, - 0x5c, 0xbe, 0x1f, 0x21, 0xc6, 0x88, 0x1f, 0x78, 0x25, 0x8f, 0x58, 0xa8, 0xa6, 0x70, 0xcd, 0x23, 0x04, 0x79, 0xcb, - 0x74, 0xf0, 0xb7, 0x9e, 0x38, 0xdd, 0x9f, 0x28, 0xed, 0xe2, 0x0b, 0x8b, 0xba, 0x27, 0x6b, 0xeb, 0x06, 0xe4, 0x60, - 0xc3, 0x74, 0x51, 0x90, 0x6d, 0x4a, 0x23, 0x68, 0xa3, 0xe5, 0xc0, 0x86, 0x53, 0xa9, 0x0d, 0x67, 0xae, 0x21, 0xb8, - 0xcf, 0xcf, 0xd3, 0xd1, 0x02, 0x3e, 0xa4, 0xba, 0xbd, 0xc4, 0xcf, 0x87, 0x0d, 0x8f, 0x80, 0xcc, 0x8e, 0xf8, 0xcc, - 0x26, 0x92, 0x4e, 0xea, 0x5c, 0x01, 0xbb, 0x9d, 0xbd, 0x03, 0x39, 0x62, 0xe6, 0xbe, 0x42, 0xf5, 0x2d, 0x1a, 0x70, - 0x65, 0xac, 0x7d, 0x4d, 0x32, 0x16, 0x5e, 0x95, 0xd3, 0x70, 0x00, 0x30, 0x74, 0x19, 0x7d, 0x6d, 0xb9, 0xc9, 0xb2, - 0x9f, 0x0b, 0x08, 0x82, 0x28, 0x89, 0xc7, 0x07, 0xbc, 0x2f, 0xab, 0xa1, 0x46, 0xc9, 0xc7, 0xb2, 0x91, 0x4a, 0xaf, - 0x44, 0x7f, 0x37, 0xe6, 0x12, 0x03, 0xbe, 0xab, 0xda, 0x82, 0xc2, 0x79, 0x7e, 0x38, 0x9c, 0xe7, 0x23, 0xe3, 0x59, - 0x06, 0xaa, 0x95, 0x69, 0x1d, 0xc4, 0x66, 0xbe, 0x58, 0xf8, 0x8b, 0x9d, 0x93, 0x88, 0x28, 0x08, 0xec, 0x48, 0x78, - 0x10, 0xa9, 0x5f, 0x55, 0x9e, 0xee, 0x54, 0x9f, 0xed, 0x17, 0x36, 0x91, 0x5e, 0x50, 0x32, 0xf9, 0x24, 0xd8, 0xab, - 0xfe, 0x0e, 0xc2, 0x86, 0xf0, 0xe6, 0x55, 0xaf, 0xb3, 0x4c, 0xcd, 0x4a, 0x90, 0x30, 0x63, 0x8e, 0xe0, 0x71, 0xd8, - 0x69, 0x6c, 0xc3, 0x63, 0x0b, 0x8e, 0xce, 0x5b, 0xb3, 0x3b, 0xb6, 0x62, 0xb7, 0xaa, 0x4e, 0x0b, 0x1e, 0x4e, 0x87, - 0x97, 0x01, 0xae, 0xbe, 0xf5, 0x39, 0xe7, 0x77, 0x74, 0x82, 0xad, 0x07, 0x3c, 0x9a, 0x88, 0xd9, 0xfa, 0x69, 0xa4, - 0x16, 0xcf, 0x7a, 0xc8, 0x17, 0xb4, 0xfe, 0xc4, 0xec, 0xce, 0x24, 0xdf, 0x0d, 0xf8, 0x62, 0xb2, 0x7e, 0x1a, 0xc1, - 0xab, 0x4f, 0xc1, 0x8a, 0x91, 0x39, 0xb3, 0x6c, 0xfd, 0x34, 0xc2, 0x31, 0xbb, 0x7b, 0x1a, 0xd1, 0xa8, 0xad, 0xe4, - 0xbe, 0x74, 0xdb, 0x80, 0xb0, 0x72, 0xcb, 0x62, 0x78, 0x0d, 0xc4, 0x33, 0x6d, 0x24, 0x5d, 0x4b, 0x43, 0x6f, 0xcc, - 0xc3, 0x69, 0x1c, 0xac, 0xa9, 0x15, 0xf2, 0xcc, 0x10, 0xb3, 0xf8, 0x69, 0x34, 0x67, 0x2b, 0xac, 0xc8, 0x86, 0xc7, - 0x83, 0xcb, 0xc9, 0xe6, 0x8a, 0xaf, 0x81, 0xfc, 0x6c, 0xb2, 0x31, 0x5b, 0xd4, 0x2d, 0x17, 0xb3, 0xcd, 0xd3, 0x68, - 0x3e, 0x59, 0x41, 0xcf, 0xda, 0x03, 0xe6, 0xbd, 0x01, 0x11, 0x4a, 0x42, 0x6a, 0xca, 0x4d, 0xaf, 0xc7, 0xd6, 0xe3, - 0xe0, 0x8e, 0xad, 0x2f, 0x83, 0x5b, 0xb6, 0x1e, 0x03, 0x11, 0x07, 0xf5, 0xbb, 0xb7, 0x81, 0xc5, 0x17, 0xb1, 0xf5, - 0xa5, 0x49, 0xdb, 0x3c, 0x8d, 0x98, 0x3b, 0x38, 0x0d, 0x5c, 0xb0, 0x36, 0x99, 0xb7, 0x62, 0x70, 0x09, 0x59, 0x7a, - 0x31, 0xdb, 0x0c, 0x2f, 0xd9, 0x7a, 0x84, 0x53, 0x3d, 0xf1, 0xd9, 0x1d, 0xbf, 0x65, 0x09, 0x5f, 0x35, 0xf1, 0xd5, - 0x06, 0x34, 0xa2, 0x47, 0x19, 0xf4, 0x15, 0xd4, 0xcc, 0x9c, 0x57, 0x16, 0x46, 0xe5, 0xbe, 0x05, 0x07, 0x14, 0xa4, - 0x6d, 0x80, 0x20, 0x89, 0x67, 0xf7, 0x2a, 0x5c, 0xdf, 0x48, 0x61, 0xc0, 0x4d, 0x60, 0x06, 0x0c, 0x4c, 0x3f, 0x83, - 0x1f, 0x56, 0xba, 0x44, 0x88, 0xb3, 0x9f, 0x52, 0x92, 0xcc, 0xf3, 0xd7, 0x22, 0xcd, 0xdd, 0xc2, 0x75, 0x0a, 0xb3, - 0xa2, 0x40, 0xf5, 0x53, 0x52, 0x1a, 0x58, 0xa8, 0x44, 0xa6, 0x52, 0xf0, 0xcb, 0xe6, 0x3c, 0xca, 0x8e, 0xd1, 0xb9, - 0xce, 0x2f, 0x27, 0xce, 0xe9, 0xa4, 0xef, 0x3f, 0x70, 0x0c, 0x5b, 0xc8, 0xc0, 0x85, 0x3f, 0xf5, 0x84, 0x71, 0x6a, - 0x05, 0x62, 0x2a, 0x79, 0xf6, 0x14, 0x3e, 0x13, 0x5a, 0x1d, 0x5d, 0xf8, 0x7e, 0x50, 0x68, 0x93, 0x74, 0x0b, 0x92, - 0x14, 0x3c, 0x45, 0xcf, 0x39, 0x6f, 0x03, 0x95, 0x62, 0x44, 0x0b, 0x22, 0x6d, 0x2d, 0x33, 0x07, 0x69, 0x4b, 0xf3, - 0x5d, 0x13, 0x3f, 0x87, 0x05, 0x5c, 0x44, 0x0b, 0x5b, 0xc3, 0xa3, 0x2a, 0x56, 0xee, 0x4d, 0x9e, 0x23, 0x9c, 0xd1, - 0xa5, 0x4c, 0x00, 0x5c, 0xef, 0xd7, 0x61, 0xad, 0xf0, 0x8a, 0x9a, 0x45, 0x5e, 0xd4, 0xf4, 0xc9, 0x16, 0xb8, 0x8f, - 0x45, 0x89, 0x02, 0x67, 0x2d, 0x18, 0xb0, 0x15, 0x96, 0xec, 0xa4, 0xb0, 0x29, 0x5a, 0x42, 0x6f, 0x8f, 0x9f, 0x0e, - 0x6a, 0x26, 0x03, 0x68, 0x02, 0x68, 0x3c, 0xfe, 0x05, 0xa0, 0xa6, 0x37, 0xb5, 0x58, 0x57, 0x41, 0xa9, 0x94, 0x9b, - 0xf0, 0x33, 0x30, 0xcc, 0xf0, 0x43, 0x21, 0xb7, 0x89, 0x12, 0x39, 0x3f, 0x16, 0xa5, 0x58, 0x96, 0xa2, 0x4a, 0xda, - 0x0d, 0x05, 0x8f, 0x08, 0xb7, 0x41, 0x63, 0xe6, 0xf6, 0x44, 0x17, 0xad, 0x08, 0xe5, 0xd8, 0xac, 0x63, 0xa4, 0x51, - 0x66, 0x27, 0xbb, 0x4e, 0x16, 0xda, 0xef, 0xab, 0x1c, 0xb2, 0x0e, 0x58, 0x23, 0xf9, 0x7a, 0xcd, 0xa1, 0xdb, 0x46, - 0x79, 0xf1, 0xe0, 0xf9, 0x0a, 0x4e, 0x73, 0x3c, 0xb1, 0xbb, 0x5e, 0x77, 0x8a, 0x44, 0xbc, 0xc2, 0x49, 0x95, 0x8f, - 0x64, 0xe1, 0xb8, 0x73, 0xa7, 0xb5, 0x58, 0x55, 0x2e, 0xeb, 0xa9, 0xc5, 0x11, 0x81, 0x4f, 0xe5, 0xd1, 0x5e, 0x68, - 0x5b, 0x14, 0x0b, 0x61, 0xf4, 0xe8, 0x84, 0x9f, 0x94, 0xc0, 0xfa, 0x3a, 0x1c, 0x96, 0x7e, 0xc4, 0xd1, 0xef, 0x34, - 0x1a, 0x2d, 0x08, 0x69, 0x78, 0xea, 0x45, 0xa3, 0x45, 0x5d, 0xd4, 0x61, 0x76, 0x9d, 0xeb, 0x81, 0xc2, 0x30, 0x02, - 0xf5, 0x83, 0xab, 0x0c, 0x3e, 0x8b, 0x10, 0x35, 0x0f, 0x4c, 0xb3, 0x21, 0x1c, 0x75, 0x81, 0x87, 0x56, 0xd0, 0x62, - 0x66, 0x3e, 0x0a, 0x31, 0x7c, 0x48, 0x17, 0xe7, 0x4f, 0xc8, 0xca, 0x07, 0xd8, 0x1d, 0xba, 0x0b, 0xe5, 0x9c, 0xa9, - 0x18, 0xe0, 0x47, 0x01, 0xf9, 0x28, 0x01, 0x37, 0x03, 0x64, 0x8f, 0x2c, 0x01, 0xc4, 0x8a, 0xd1, 0xd1, 0xe4, 0x73, - 0xdf, 0x8b, 0x14, 0xbc, 0xb3, 0xcf, 0x72, 0x35, 0x61, 0x28, 0x7c, 0x62, 0xa0, 0x9b, 0xdf, 0xf8, 0xed, 0x79, 0x0b, - 0x46, 0x76, 0x49, 0x8a, 0xd7, 0x9a, 0xe1, 0x7e, 0x03, 0x6e, 0x47, 0x40, 0x59, 0x53, 0x1d, 0x93, 0x6c, 0xd3, 0x10, - 0xc9, 0x80, 0x19, 0x31, 0x22, 0xa8, 0x2c, 0x17, 0xfe, 0x77, 0x2f, 0x8b, 0x02, 0x07, 0x70, 0x35, 0x93, 0xc1, 0x6b, - 0x17, 0x46, 0x05, 0xc0, 0x39, 0x0d, 0x9d, 0xd2, 0x5e, 0x55, 0x1d, 0x92, 0x55, 0xf3, 0x83, 0xd9, 0xbc, 0x69, 0x98, - 0x18, 0x11, 0x44, 0x17, 0xe1, 0x04, 0xd3, 0x2b, 0xd2, 0xd7, 0x4a, 0x4e, 0x47, 0xab, 0x8e, 0xd6, 0x12, 0x13, 0x73, - 0x45, 0xf1, 0xd7, 0x80, 0xc7, 0x0d, 0x5e, 0x9d, 0xa4, 0xe9, 0x44, 0xf5, 0xe8, 0xf1, 0xeb, 0x34, 0x9d, 0x94, 0xb8, - 0x2b, 0xfc, 0x06, 0x5c, 0x34, 0xdb, 0x7c, 0xe8, 0xc7, 0x2f, 0x28, 0xe2, 0xa2, 0x06, 0x57, 0xde, 0xa9, 0xbe, 0x52, - 0x7d, 0x04, 0xb5, 0xf0, 0xc4, 0xc8, 0x5a, 0x78, 0x72, 0xc9, 0x5a, 0x0b, 0x82, 0x99, 0xcd, 0x81, 0x0b, 0xf9, 0x95, - 0x52, 0xc4, 0x9b, 0x48, 0xa8, 0xc5, 0xa0, 0xf5, 0x98, 0x39, 0xab, 0x46, 0x0b, 0x95, 0x19, 0xa1, 0x7d, 0x5b, 0x8b, - 0xce, 0x6f, 0xe4, 0xa7, 0x3c, 0xb5, 0x2f, 0xdb, 0xe3, 0x7c, 0xbc, 0x47, 0x77, 0xd5, 0x59, 0x66, 0x52, 0xc6, 0x27, - 0xb3, 0x04, 0x85, 0xbb, 0x04, 0x1b, 0x90, 0x64, 0xbf, 0xd5, 0x01, 0x32, 0x6a, 0xaf, 0xfd, 0xae, 0xb3, 0x7c, 0x75, - 0xb3, 0x35, 0x14, 0x95, 0x5a, 0x49, 0x8a, 0x83, 0x0c, 0xd7, 0x6d, 0xe5, 0xc3, 0xc5, 0x05, 0xf4, 0x8c, 0x91, 0xc8, - 0x3c, 0x7f, 0x22, 0x5f, 0x82, 0x73, 0xc6, 0x59, 0x21, 0x30, 0x61, 0xac, 0xde, 0xb5, 0x96, 0x4a, 0x43, 0x8a, 0xb1, - 0xa3, 0x51, 0x96, 0x55, 0x96, 0x2e, 0xb3, 0xb5, 0x84, 0x2d, 0xab, 0xc8, 0x2d, 0x6c, 0x99, 0xc9, 0x6a, 0x7e, 0xa8, - 0xb8, 0x83, 0xf2, 0xcd, 0xd6, 0x19, 0xdf, 0x4b, 0x64, 0xef, 0x36, 0x50, 0xc2, 0xf5, 0xe8, 0x3f, 0x90, 0x7e, 0x9b, - 0x61, 0x9c, 0x72, 0x5b, 0x49, 0x0b, 0x70, 0xfa, 0x87, 0xc3, 0x87, 0x0a, 0x83, 0x06, 0x47, 0x18, 0x47, 0xd6, 0xef, - 0xdf, 0x56, 0x5e, 0x8d, 0x89, 0x3a, 0x3e, 0xab, 0xdf, 0xaf, 0xe8, 0xe1, 0xb4, 0x1a, 0xad, 0xd2, 0x2d, 0xb2, 0x13, - 0xda, 0x58, 0xf9, 0x41, 0xad, 0x80, 0xd9, 0x5b, 0x9f, 0x4f, 0x07, 0xa0, 0x63, 0x01, 0x12, 0xcd, 0x66, 0x22, 0x31, - 0x27, 0xdd, 0x93, 0xf0, 0xf8, 0xc0, 0x02, 0x07, 0x98, 0x8a, 0xff, 0x43, 0x78, 0x33, 0xb0, 0x41, 0xa3, 0x44, 0x5f, - 0xa3, 0xab, 0xda, 0xdc, 0xe8, 0x78, 0xe9, 0x29, 0x24, 0xb2, 0x82, 0x55, 0x73, 0x5f, 0x6e, 0xe0, 0xb4, 0x87, 0x9a, - 0x43, 0x65, 0x09, 0xfe, 0xf6, 0xcb, 0xfc, 0x70, 0x58, 0x67, 0x50, 0xd8, 0x6e, 0x2d, 0xb4, 0x37, 0x66, 0xa9, 0x86, - 0x8a, 0x70, 0xd0, 0xf9, 0x4a, 0xcc, 0xea, 0x11, 0xfd, 0x3d, 0x3f, 0x1c, 0x56, 0x04, 0x06, 0x1c, 0x96, 0x32, 0x13, - 0x2d, 0x14, 0x4b, 0xeb, 0x6c, 0x46, 0x75, 0xe0, 0x81, 0x89, 0x39, 0x0b, 0x77, 0x00, 0xda, 0xa4, 0x56, 0x81, 0x5e, - 0x45, 0xf4, 0x13, 0xf7, 0x6b, 0xfb, 0xf5, 0x7a, 0x64, 0x96, 0x8e, 0xdc, 0x18, 0x0b, 0x00, 0x0e, 0x3c, 0xaf, 0x49, - 0x9e, 0x93, 0xaf, 0xa1, 0xdd, 0x93, 0x0b, 0xf9, 0x13, 0x94, 0x2d, 0x3c, 0x57, 0x4d, 0x2b, 0x8b, 0x15, 0x57, 0xd5, - 0xab, 0x0b, 0x5e, 0x99, 0x4c, 0xab, 0xb4, 0x12, 0x95, 0x12, 0x0c, 0xa8, 0x4b, 0xbc, 0xd6, 0x34, 0xa3, 0xd4, 0x46, - 0x9d, 0x89, 0x1a, 0xb0, 0xc1, 0x7e, 0xaa, 0x36, 0x3a, 0x39, 0x97, 0xcf, 0x2f, 0x8d, 0xc3, 0xa7, 0x5d, 0xbd, 0x99, - 0xa9, 0x1c, 0xf8, 0x6b, 0xe5, 0x43, 0xab, 0xc7, 0x40, 0x07, 0xe4, 0xf4, 0xc7, 0xb0, 0x98, 0xd8, 0x1d, 0x9a, 0xb7, - 0xbb, 0xcb, 0xea, 0x22, 0xbd, 0xd3, 0x94, 0xcc, 0xea, 0x2d, 0x9f, 0x59, 0x3d, 0x3a, 0xe0, 0xc5, 0x63, 0xbd, 0x57, - 0x98, 0x49, 0x04, 0x17, 0x43, 0x35, 0x89, 0xec, 0x0e, 0xb4, 0xe6, 0x51, 0xc5, 0x04, 0xf8, 0x41, 0xa9, 0x35, 0xbd, - 0xb7, 0xbb, 0x42, 0x9d, 0x52, 0x78, 0xdc, 0x5a, 0xf2, 0x03, 0x73, 0xa7, 0x5d, 0xeb, 0x7c, 0x3c, 0xbf, 0xf4, 0xfd, - 0x46, 0x9e, 0xd0, 0x66, 0x67, 0x72, 0xfa, 0x27, 0x6f, 0xf5, 0x0f, 0x53, 0x7d, 0x0b, 0xdd, 0x09, 0xfa, 0x0c, 0x5d, - 0x55, 0xdd, 0x95, 0xd8, 0xc2, 0x50, 0x4f, 0x2c, 0xf2, 0x42, 0x9e, 0xb4, 0xc6, 0x8e, 0x83, 0xbd, 0x01, 0x4e, 0xfc, - 0xf2, 0x70, 0x10, 0x57, 0xb9, 0xcf, 0xce, 0xbb, 0x46, 0x56, 0x0e, 0x60, 0x05, 0x51, 0x30, 0x6e, 0xcd, 0xc7, 0x36, - 0x48, 0x97, 0xb8, 0x1a, 0x1f, 0xbf, 0xa1, 0x58, 0x26, 0x9b, 0x88, 0x8b, 0x8b, 0xfc, 0xe9, 0x73, 0x20, 0x2d, 0xeb, - 0xf7, 0xa3, 0xeb, 0xcb, 0xe9, 0xf3, 0x61, 0x14, 0x80, 0x63, 0x97, 0xbd, 0xbc, 0x8c, 0xf9, 0xea, 0x92, 0x59, 0xa6, - 0xb0, 0xc8, 0x37, 0x03, 0xaa, 0x4b, 0x56, 0x4b, 0xd7, 0x2b, 0xc0, 0xd2, 0xe5, 0x37, 0x0f, 0x61, 0x6a, 0x40, 0x23, - 0x6b, 0xee, 0x4e, 0x73, 0x2d, 0x50, 0xea, 0x79, 0x3f, 0x33, 0xe4, 0xeb, 0x32, 0xe8, 0x0a, 0xd2, 0x3d, 0x8f, 0x48, - 0x2f, 0xf7, 0xd2, 0xe9, 0x7e, 0x5f, 0x0a, 0xb0, 0xd4, 0x97, 0xe2, 0x0b, 0x28, 0x2c, 0x1a, 0xdf, 0x08, 0xd0, 0xd6, - 0x50, 0x4d, 0x7b, 0xa5, 0xa8, 0x7a, 0x41, 0xaf, 0x14, 0x5f, 0x7a, 0x7a, 0xa8, 0xcc, 0x97, 0xa5, 0xa3, 0xff, 0x09, - 0x35, 0x17, 0x9c, 0x10, 0x33, 0x31, 0x07, 0x50, 0x09, 0xda, 0xf8, 0x56, 0x47, 0x1b, 0x9f, 0xea, 0x55, 0xdc, 0xf4, - 0x79, 0x6d, 0x2d, 0x73, 0x42, 0xd8, 0x74, 0x2f, 0x01, 0x2a, 0xf2, 0x4a, 0x78, 0x04, 0xcb, 0x2f, 0x7f, 0xc8, 0xd3, - 0x15, 0xa2, 0x75, 0xdc, 0xb3, 0xcc, 0xa5, 0xb1, 0x7f, 0x63, 0x30, 0x7d, 0x7d, 0xbb, 0x2d, 0xf2, 0x53, 0x13, 0x13, - 0xd6, 0x63, 0x45, 0xdf, 0xbc, 0x0f, 0x57, 0x02, 0x05, 0x0e, 0x25, 0x12, 0xdb, 0x54, 0xa1, 0x88, 0x07, 0x49, 0x9f, - 0x2e, 0x5a, 0x9f, 0x06, 0x98, 0x5a, 0xcb, 0x81, 0x39, 0x84, 0xab, 0xb8, 0xf0, 0xd1, 0xd3, 0xb7, 0x98, 0x85, 0xf3, - 0x89, 0xf7, 0xc9, 0x2b, 0x46, 0xe6, 0xe3, 0x3e, 0x2a, 0x95, 0xf4, 0xcf, 0xc3, 0x61, 0x56, 0xcd, 0x7d, 0x87, 0x3e, - 0xd2, 0x43, 0x95, 0x0b, 0xca, 0xde, 0x18, 0x93, 0x08, 0x94, 0xc6, 0x78, 0x1f, 0x07, 0xc7, 0x79, 0x9f, 0x06, 0x90, - 0xda, 0x27, 0x3e, 0x90, 0x92, 0xc3, 0x73, 0x8e, 0x39, 0xa1, 0xb4, 0x22, 0xac, 0xe2, 0x8b, 0x0c, 0xe5, 0xba, 0x53, - 0x0a, 0x26, 0x39, 0x24, 0x18, 0xfe, 0xaa, 0x79, 0x13, 0x2b, 0x10, 0x76, 0xcd, 0xbc, 0x1a, 0x3d, 0xa9, 0x92, 0xb0, - 0x14, 0x70, 0x54, 0x66, 0x9e, 0x61, 0x6f, 0x78, 0x62, 0x18, 0x39, 0x58, 0xee, 0x8f, 0xea, 0x44, 0xe4, 0x1e, 0x5d, - 0x60, 0x54, 0x16, 0x9e, 0x37, 0x74, 0xa5, 0x41, 0x25, 0xd9, 0xf1, 0x57, 0x5c, 0x03, 0x6a, 0x6b, 0x8c, 0x18, 0x0a, - 0x18, 0x05, 0xaf, 0xed, 0x0f, 0x21, 0x8b, 0xb2, 0xf5, 0x1b, 0x1c, 0xf3, 0x59, 0xc9, 0x5d, 0xef, 0x70, 0x16, 0x5a, - 0x42, 0x9e, 0xdc, 0x31, 0x48, 0xd3, 0x58, 0x1a, 0x01, 0x27, 0x22, 0xd9, 0xc6, 0x52, 0x38, 0x02, 0x08, 0x08, 0x74, - 0x53, 0x66, 0x18, 0xd3, 0xc1, 0xc8, 0xf3, 0xa4, 0x67, 0xbc, 0x57, 0xe1, 0x29, 0xa4, 0xc9, 0xf6, 0xf5, 0xfc, 0xbd, - 0x11, 0x64, 0xe5, 0x96, 0x73, 0x3c, 0x2c, 0xbe, 0x71, 0xf6, 0x55, 0x4e, 0x9e, 0x62, 0x96, 0x91, 0xde, 0x29, 0xe6, - 0x05, 0xfc, 0xa9, 0x2c, 0xf5, 0x39, 0x4a, 0x6f, 0x99, 0x4f, 0x56, 0x91, 0x74, 0xe9, 0x6d, 0xfa, 0xfd, 0x78, 0xa4, - 0x0e, 0x35, 0x7f, 0x1f, 0x8f, 0xe4, 0x19, 0xb6, 0x61, 0x09, 0x0b, 0xad, 0x82, 0x31, 0x80, 0x24, 0x36, 0x22, 0x1a, - 0x8c, 0xf6, 0xe6, 0x70, 0x38, 0xdf, 0x98, 0xb3, 0x64, 0x0f, 0xae, 0xaf, 0x3c, 0x31, 0xef, 0xc0, 0x97, 0x79, 0x4c, - 0x10, 0xb1, 0x99, 0xb7, 0x61, 0x35, 0x78, 0xb0, 0x83, 0xeb, 0x23, 0xb6, 0x28, 0xd6, 0x3a, 0x96, 0xca, 0x3a, 0x38, - 0xad, 0x63, 0xd3, 0x8c, 0x94, 0x22, 0xfb, 0x1c, 0xfb, 0x7b, 0x37, 0xb8, 0xba, 0x36, 0x06, 0xb5, 0xc6, 0x1d, 0xe6, - 0xce, 0xa9, 0x80, 0x7a, 0x4c, 0x57, 0x50, 0x3d, 0xab, 0xc8, 0x97, 0xdf, 0xda, 0x39, 0x20, 0x68, 0x04, 0x02, 0x17, - 0x0d, 0xb4, 0x6a, 0x97, 0x72, 0xde, 0x05, 0x84, 0xf8, 0x2e, 0x05, 0x7d, 0x3a, 0x83, 0x4d, 0x6c, 0x3e, 0x81, 0x58, - 0x34, 0xdd, 0xe7, 0x5a, 0x33, 0x5f, 0x8c, 0x68, 0x67, 0xd6, 0xdd, 0x22, 0xb7, 0x5a, 0x88, 0x64, 0xf4, 0x6c, 0x33, - 0xe1, 0xa2, 0x43, 0x39, 0x23, 0x01, 0x13, 0xb4, 0xb6, 0x52, 0xf2, 0xb9, 0xee, 0x75, 0x82, 0xf6, 0x40, 0xd2, 0xba, - 0x7f, 0xb3, 0xe8, 0x8c, 0x92, 0x93, 0xeb, 0x4d, 0xce, 0x20, 0x05, 0x0b, 0xb6, 0x97, 0x39, 0xe1, 0x06, 0xf8, 0xc4, - 0x66, 0xc9, 0x69, 0x1a, 0xe4, 0xb1, 0x30, 0x1e, 0x79, 0x6d, 0x7e, 0x59, 0x40, 0x87, 0x92, 0x45, 0x23, 0xc4, 0x03, - 0xec, 0x1c, 0x92, 0xab, 0x02, 0x75, 0xd3, 0x40, 0x57, 0xae, 0x9c, 0x29, 0xa6, 0xc0, 0x85, 0x50, 0x10, 0xb5, 0xa3, - 0x93, 0xa8, 0x9c, 0xf7, 0x49, 0x75, 0x99, 0x4f, 0x0b, 0x69, 0x1a, 0xc8, 0xa7, 0x95, 0x63, 0x1e, 0xd8, 0xd9, 0xc6, - 0x35, 0x81, 0x81, 0x4e, 0xed, 0x6b, 0x51, 0xce, 0xb1, 0x8a, 0xe8, 0x7d, 0xfe, 0xb1, 0xb2, 0xa7, 0x0f, 0x22, 0x6c, - 0x54, 0xa0, 0xb1, 0x94, 0x18, 0x1b, 0x39, 0xfe, 0x2d, 0x51, 0x36, 0x64, 0x08, 0x08, 0x21, 0x6d, 0xe4, 0xf4, 0xc3, - 0xfa, 0xf2, 0x36, 0xd3, 0xfe, 0x9f, 0x24, 0x7e, 0x1b, 0xec, 0xe5, 0xd4, 0x9f, 0x7a, 0xc4, 0xe3, 0xb5, 0x46, 0x8f, - 0x29, 0xe9, 0x36, 0xc8, 0x53, 0xe5, 0x29, 0x48, 0x26, 0x8c, 0x25, 0x04, 0x8b, 0x72, 0xc1, 0x73, 0x5e, 0x71, 0x09, - 0xf7, 0x51, 0xcb, 0x8a, 0x08, 0x55, 0x89, 0x9c, 0x3e, 0x5f, 0x01, 0xcf, 0x04, 0x04, 0x3a, 0xc6, 0x48, 0xa3, 0x0a, - 0xbe, 0x04, 0xc6, 0x3a, 0x50, 0x76, 0x9a, 0x91, 0xe0, 0xb2, 0x7b, 0x83, 0x44, 0xa9, 0xaf, 0x49, 0x49, 0xfa, 0x4e, - 0xd4, 0x78, 0x25, 0x56, 0x11, 0x09, 0x64, 0xa8, 0x21, 0x62, 0x55, 0x3d, 0x75, 0xaf, 0x8a, 0xc9, 0x60, 0x50, 0xf9, - 0x72, 0x7a, 0xe2, 0x0d, 0x0d, 0x95, 0x77, 0x5d, 0xd1, 0x4e, 0x4f, 0xb4, 0x52, 0xde, 0x42, 0x5a, 0x82, 0xa6, 0x61, - 0xa4, 0x39, 0x94, 0xba, 0x92, 0xee, 0xc6, 0x20, 0xbe, 0x64, 0xa2, 0x67, 0x3b, 0xb5, 0xa3, 0xb4, 0x25, 0xed, 0x21, - 0xa4, 0xe7, 0x2e, 0xf9, 0x98, 0x85, 0x5c, 0xdd, 0x29, 0x27, 0xe5, 0x55, 0x88, 0x4e, 0xee, 0x7b, 0x0c, 0x89, 0x40, - 0x9f, 0x73, 0x0c, 0xeb, 0xa2, 0xa1, 0xce, 0x61, 0x85, 0x98, 0x2d, 0x94, 0x30, 0x5f, 0x32, 0x9e, 0x4a, 0x06, 0x0d, - 0x80, 0x0c, 0xf8, 0xe2, 0x65, 0x60, 0xf9, 0x2b, 0x88, 0x1f, 0x6d, 0x7c, 0x38, 0xfc, 0x59, 0x53, 0x88, 0xed, 0x9f, - 0xb0, 0x19, 0xc2, 0xa3, 0x7a, 0xc0, 0x33, 0xdf, 0xc4, 0x09, 0x5a, 0x01, 0x49, 0x99, 0x1d, 0x4d, 0x64, 0xaf, 0x7a, - 0x08, 0xa7, 0xb2, 0x02, 0x75, 0x94, 0x75, 0x56, 0xc2, 0x8f, 0x30, 0xd5, 0xad, 0xc4, 0x5a, 0xa0, 0xcd, 0xd5, 0x8a, - 0xb5, 0x00, 0x0e, 0xfc, 0x1c, 0x82, 0x27, 0xf2, 0x39, 0xb8, 0x18, 0x14, 0xe0, 0x73, 0x00, 0xbc, 0xc8, 0x5d, 0x78, - 0x30, 0x8f, 0x2c, 0xab, 0x11, 0x86, 0xa3, 0x8a, 0x58, 0xbf, 0x66, 0x3b, 0xf2, 0x81, 0xdb, 0x31, 0x3e, 0xd7, 0x1e, - 0x4b, 0x96, 0x83, 0x51, 0xe6, 0x5e, 0x2d, 0xd1, 0xf3, 0x26, 0x8d, 0x9b, 0xd1, 0x93, 0x7d, 0x2d, 0xff, 0x17, 0xf4, - 0x32, 0xe8, 0x6f, 0xe1, 0x96, 0xd7, 0xfc, 0x6e, 0x41, 0xa4, 0x99, 0x5e, 0x41, 0xa4, 0x8c, 0x1a, 0x91, 0x31, 0x84, - 0x4d, 0xaa, 0x9b, 0xdb, 0xa4, 0xba, 0x10, 0xf0, 0x74, 0x44, 0xaa, 0x6b, 0x21, 0x6d, 0xe4, 0xd3, 0x3a, 0x90, 0xb1, - 0x48, 0xef, 0x7f, 0xfc, 0xcb, 0x8b, 0xcf, 0x6f, 0x7f, 0xf9, 0x71, 0xf1, 0xf6, 0xfd, 0x9b, 0xb7, 0xef, 0xdf, 0x7e, - 0xfe, 0x8d, 0x20, 0x3c, 0xa6, 0x42, 0x65, 0xf8, 0xf8, 0xe1, 0xe6, 0xad, 0x93, 0xc1, 0xf6, 0x66, 0xc8, 0xda, 0x37, - 0x72, 0x30, 0x04, 0x22, 0x1b, 0x84, 0x0c, 0xb2, 0x53, 0x32, 0xc7, 0x4c, 0xcc, 0x31, 0xf6, 0x4e, 0x60, 0xb2, 0x05, - 0x9c, 0x63, 0x99, 0x97, 0x8c, 0xc8, 0x55, 0xa1, 0xf5, 0x03, 0x5a, 0xf0, 0x0e, 0x5c, 0x64, 0xd2, 0xfc, 0xee, 0x17, - 0x82, 0xd8, 0xa7, 0x95, 0x94, 0xfb, 0x6a, 0x5b, 0xf3, 0x7c, 0x7b, 0xbf, 0x97, 0x70, 0xfe, 0x73, 0x69, 0x44, 0x2d, - 0xc0, 0x01, 0xf8, 0x1c, 0xfe, 0xb8, 0xd2, 0x96, 0x34, 0x99, 0x45, 0xfb, 0x19, 0x43, 0xd0, 0xa5, 0xc1, 0x07, 0xb1, - 0x47, 0x5e, 0xea, 0x93, 0x85, 0x04, 0xee, 0x88, 0xe1, 0xd3, 0x8a, 0xa0, 0x57, 0x8c, 0x28, 0x2e, 0xb9, 0x42, 0xa5, - 0x94, 0xfc, 0x1b, 0x65, 0x17, 0x15, 0x72, 0x56, 0xb0, 0x7b, 0x45, 0x8e, 0x8c, 0x1f, 0x04, 0x13, 0x5f, 0x0e, 0xee, - 0xbf, 0xc4, 0x3b, 0x9c, 0x29, 0x8e, 0xe4, 0x84, 0x3f, 0x64, 0x18, 0xd8, 0x9f, 0x83, 0xcf, 0xab, 0xc3, 0xbc, 0xbc, - 0xd1, 0xa7, 0xdc, 0x92, 0x8f, 0x27, 0xcb, 0x2b, 0x30, 0xd8, 0x2f, 0x55, 0x73, 0xd7, 0xbc, 0x9e, 0x2d, 0xe7, 0x6c, - 0x3f, 0x8b, 0xe6, 0xc1, 0x1d, 0x9b, 0x65, 0xf3, 0x60, 0xd5, 0xf0, 0x35, 0xbb, 0xe5, 0x6b, 0xab, 0x6a, 0x6b, 0xbb, - 0x6a, 0x93, 0x0d, 0xbf, 0x05, 0x09, 0xe1, 0x26, 0xf3, 0x80, 0xf7, 0xf8, 0xce, 0x67, 0x1b, 0x90, 0x68, 0x57, 0x6c, - 0x03, 0x17, 0xb1, 0x35, 0xff, 0xb1, 0xf2, 0x36, 0xac, 0x64, 0xe7, 0x63, 0x96, 0xe3, 0xfc, 0xf3, 0xe1, 0x01, 0xed, - 0x85, 0xfa, 0xd9, 0xa5, 0x7a, 0x36, 0x51, 0x76, 0xb3, 0xcd, 0x68, 0x71, 0x9f, 0x56, 0x9b, 0x30, 0x43, 0xcf, 0x72, - 0xf8, 0x68, 0x2b, 0x05, 0x3f, 0xbd, 0xc0, 0x2f, 0xd9, 0x51, 0x5b, 0x69, 0xdb, 0xae, 0x4a, 0x6c, 0x05, 0x2d, 0x8a, - 0xac, 0x56, 0x78, 0x60, 0xce, 0xaf, 0x61, 0x01, 0x63, 0xcf, 0x71, 0xce, 0x6b, 0x7f, 0x84, 0x8c, 0xf7, 0x0e, 0x00, - 0x5a, 0xe6, 0x38, 0xc0, 0x23, 0x56, 0x8c, 0xa2, 0xc1, 0x3b, 0xbf, 0x54, 0x56, 0x2b, 0xcd, 0x49, 0x68, 0x1b, 0xb1, - 0x6a, 0x39, 0x52, 0x35, 0x23, 0xd2, 0x07, 0xe9, 0x79, 0xdf, 0x23, 0xaa, 0xc1, 0x9e, 0xcc, 0xeb, 0xc0, 0x3e, 0xbd, - 0x6a, 0xad, 0xea, 0xce, 0xef, 0xa9, 0xd2, 0x25, 0x47, 0xb6, 0xfc, 0x74, 0x19, 0x3e, 0xa8, 0x3f, 0x25, 0xd7, 0x87, - 0x02, 0x47, 0x78, 0xac, 0x02, 0xce, 0xd7, 0x2b, 0xd1, 0xee, 0x44, 0xd8, 0x95, 0x4b, 0x40, 0x88, 0x2f, 0x69, 0x9a, - 0xe3, 0x71, 0x44, 0x13, 0x11, 0x36, 0x31, 0xfa, 0x0b, 0xbb, 0x0f, 0x25, 0x96, 0xf3, 0x5c, 0x83, 0x92, 0x4b, 0x06, - 0xef, 0x49, 0x7b, 0x0d, 0x9a, 0xe5, 0x55, 0xa9, 0xc9, 0x44, 0x0e, 0xca, 0x87, 0x43, 0x01, 0x7b, 0xa9, 0xf1, 0xd3, - 0x84, 0x9f, 0xb0, 0xbc, 0xb5, 0xb7, 0xa6, 0x14, 0x95, 0x34, 0x40, 0x05, 0x3e, 0x66, 0xf0, 0xbf, 0x3b, 0x43, 0x2c, - 0x98, 0xa2, 0xe3, 0x87, 0x33, 0x31, 0xb7, 0x9e, 0x5b, 0x65, 0x1d, 0x65, 0x6b, 0x94, 0x13, 0xf0, 0x6f, 0xa9, 0x8e, - 0x93, 0x44, 0x38, 0xf5, 0x1e, 0x71, 0x51, 0xf7, 0x72, 0x88, 0xba, 0x61, 0x6f, 0x2b, 0x1d, 0x6c, 0x39, 0x4d, 0x83, - 0x23, 0xf1, 0x2b, 0xf5, 0xd9, 0x87, 0xcc, 0xe2, 0x51, 0x47, 0x36, 0xa2, 0x24, 0x8d, 0x63, 0x91, 0xc3, 0xf6, 0xbe, - 0x90, 0xfb, 0x7f, 0xbf, 0x0f, 0xe1, 0xa4, 0x55, 0x90, 0x94, 0x9e, 0x40, 0x44, 0x38, 0x3a, 0xfc, 0x88, 0xf0, 0x44, - 0xaa, 0x0a, 0x9f, 0xd4, 0x27, 0x6e, 0xcc, 0xee, 0x85, 0x39, 0xaa, 0xb7, 0x00, 0xc3, 0x58, 0x6f, 0x2d, 0x42, 0x12, - 0xad, 0x34, 0xa3, 0xad, 0x07, 0xc4, 0x88, 0x0f, 0x6b, 0x8b, 0x0c, 0xc6, 0xda, 0x92, 0x48, 0x00, 0xbf, 0x23, 0x21, - 0x43, 0xdb, 0x46, 0x60, 0xc6, 0xf0, 0x76, 0x56, 0x5c, 0xba, 0x0e, 0xdb, 0x9c, 0xc3, 0x17, 0xb2, 0xd0, 0xac, 0x23, - 0x4a, 0x13, 0x84, 0xfc, 0x03, 0x4e, 0x16, 0x0a, 0xa3, 0x79, 0x7d, 0x94, 0x4e, 0x12, 0xeb, 0x87, 0xae, 0x52, 0xc1, - 0x66, 0x73, 0x83, 0xfa, 0xb2, 0xa3, 0xe4, 0x57, 0xe0, 0xa4, 0xe3, 0x24, 0x8b, 0x1c, 0x44, 0x2d, 0x2a, 0xe7, 0x26, - 0x09, 0x4b, 0xbb, 0x3a, 0xd5, 0x66, 0xbd, 0x2e, 0xca, 0xba, 0x7a, 0x2d, 0x22, 0x45, 0xef, 0xa3, 0x1e, 0x3d, 0x91, - 0x90, 0x0a, 0xad, 0x4a, 0xed, 0xf2, 0x08, 0xdc, 0x36, 0xb5, 0x62, 0x5b, 0x2e, 0x61, 0x89, 0x1a, 0xff, 0x19, 0xfa, - 0x28, 0x17, 0x0f, 0x32, 0x40, 0xa3, 0xe3, 0xa9, 0x79, 0xeb, 0x91, 0x57, 0x8e, 0xf2, 0x4b, 0xab, 0x4d, 0xfa, 0x15, - 0x90, 0x19, 0xed, 0x1f, 0x2d, 0x25, 0x90, 0x19, 0x98, 0x49, 0x4b, 0x43, 0x22, 0x47, 0x31, 0x4b, 0xf3, 0x3f, 0x70, - 0xc5, 0x56, 0x88, 0x34, 0xac, 0xe6, 0x1e, 0x7f, 0x51, 0x79, 0xb5, 0x5c, 0xcb, 0x4c, 0x73, 0xb3, 0xc4, 0xb1, 0x62, - 0x71, 0x51, 0xaf, 0x2b, 0x91, 0x05, 0x42, 0x1c, 0x61, 0x1a, 0xeb, 0xa9, 0x37, 0x4a, 0xab, 0x8f, 0x48, 0x28, 0xf3, - 0x23, 0xf6, 0x76, 0xec, 0xf5, 0x20, 0x0b, 0x71, 0x6c, 0x39, 0xd8, 0x6c, 0xbd, 0xcf, 0x65, 0x2a, 0xe2, 0xb3, 0xba, - 0x38, 0xdb, 0x54, 0xe2, 0xac, 0x4e, 0xc4, 0xd9, 0x0f, 0x90, 0xf3, 0x87, 0x33, 0x2a, 0xfa, 0xec, 0x21, 0xad, 0x93, - 0x62, 0x53, 0xd3, 0x93, 0x37, 0x58, 0xc6, 0x0f, 0x67, 0xc4, 0x55, 0x73, 0x46, 0x23, 0x19, 0x8f, 0xce, 0x3e, 0x66, - 0x40, 0xf2, 0x7a, 0x96, 0xae, 0x60, 0xf0, 0xce, 0xc2, 0x3c, 0x3e, 0x2b, 0xc5, 0x1d, 0x58, 0x9c, 0xca, 0xce, 0xf7, - 0x20, 0xc3, 0x2a, 0xfc, 0x43, 0x9c, 0x01, 0xb4, 0xeb, 0x59, 0x5a, 0x9f, 0xa5, 0xd5, 0x59, 0x5e, 0xd4, 0x67, 0x4a, - 0x0a, 0x87, 0x30, 0x7e, 0x78, 0x4f, 0x5f, 0xd9, 0xe5, 0x6d, 0x16, 0x77, 0x59, 0xe4, 0x4f, 0xd1, 0xab, 0x88, 0x98, - 0x34, 0x2a, 0xe1, 0xb5, 0xfb, 0xdb, 0xe6, 0xfe, 0xe1, 0x75, 0x63, 0xf7, 0xb3, 0x3b, 0x46, 0x74, 0x41, 0x3d, 0x5e, - 0x49, 0x4a, 0x05, 0x05, 0x04, 0x4e, 0x34, 0x6b, 0x3c, 0xb8, 0xe3, 0x80, 0x57, 0x03, 0x5b, 0xb2, 0xb5, 0xcf, 0xaf, - 0x63, 0x19, 0xa6, 0xbd, 0x09, 0xf0, 0xaf, 0xb2, 0x37, 0x5d, 0x07, 0x4b, 0xbc, 0x6f, 0x21, 0xdb, 0xd0, 0xdb, 0xd7, - 0xfc, 0x85, 0x97, 0xab, 0xbf, 0xd9, 0x3f, 0x00, 0x08, 0x03, 0x62, 0x56, 0x7d, 0x34, 0x71, 0xef, 0xac, 0x2c, 0x3b, - 0x27, 0xcb, 0xae, 0x87, 0x7e, 0x4d, 0x62, 0x54, 0x5a, 0x59, 0x4a, 0x27, 0x4b, 0x09, 0x59, 0xc0, 0x27, 0x46, 0x53, - 0x1b, 0x01, 0x84, 0xed, 0x28, 0x95, 0x2f, 0x54, 0x5e, 0x44, 0xe1, 0x9c, 0xe0, 0x79, 0x22, 0x46, 0xf7, 0x56, 0x32, - 0x60, 0x38, 0x84, 0x60, 0x0e, 0xda, 0x62, 0x6f, 0xe8, 0x26, 0xe2, 0xaf, 0x37, 0x45, 0xf9, 0x36, 0x26, 0x9f, 0x82, - 0xdd, 0xc9, 0xc7, 0x25, 0x3c, 0x2e, 0x4f, 0x3e, 0x0e, 0xd1, 0x23, 0xe1, 0xe4, 0x63, 0xf0, 0x3d, 0x92, 0xf3, 0xba, - 0xeb, 0x71, 0x82, 0xdc, 0x42, 0xba, 0xbf, 0x1d, 0x93, 0x00, 0xcd, 0x6b, 0x58, 0x8e, 0x9a, 0x8a, 0x6b, 0x66, 0xc6, - 0x78, 0xde, 0xe8, 0xfd, 0xb1, 0xe3, 0x2d, 0x53, 0x28, 0x66, 0x31, 0xaf, 0xe1, 0xf7, 0xac, 0x0a, 0xd4, 0x5d, 0x6f, - 0x93, 0xdc, 0x32, 0xab, 0xe7, 0x68, 0xf7, 0xfd, 0x50, 0x27, 0x82, 0xda, 0xdf, 0x61, 0xcf, 0x33, 0xeb, 0x5d, 0x15, - 0x03, 0x97, 0x2a, 0xd9, 0x21, 0x53, 0xd5, 0xf4, 0x40, 0xa5, 0x34, 0x78, 0x7a, 0x69, 0x5d, 0xbe, 0x54, 0xda, 0xc8, - 0x33, 0xcd, 0x6f, 0x00, 0x2f, 0xa6, 0x2e, 0x8b, 0xdd, 0x37, 0xf7, 0x15, 0xdc, 0xc6, 0xfb, 0xfd, 0x65, 0xe5, 0x99, - 0x9f, 0xb8, 0x00, 0xec, 0x4d, 0x85, 0xd6, 0x09, 0x94, 0x1a, 0xd6, 0xe1, 0xab, 0x44, 0x44, 0x7f, 0xb4, 0xcb, 0x75, - 0xe6, 0x3a, 0x60, 0x44, 0x11, 0xbf, 0x8d, 0x47, 0x7f, 0x80, 0xe2, 0xda, 0xd8, 0x03, 0xc2, 0x3a, 0x24, 0xf4, 0x19, - 0x01, 0x48, 0x3d, 0xe6, 0x28, 0x01, 0xcd, 0x8a, 0xe6, 0x8e, 0xc9, 0xcf, 0xf5, 0x95, 0xd2, 0xdf, 0x2f, 0x2b, 0x8f, - 0xcc, 0x29, 0x6d, 0x33, 0x8d, 0xd5, 0x9a, 0x4a, 0x20, 0xbc, 0xa2, 0x92, 0x55, 0xf8, 0x6c, 0xde, 0x88, 0x7e, 0x5f, - 0x1e, 0xe1, 0x69, 0xf5, 0xe3, 0x16, 0xe3, 0x5b, 0x01, 0xd1, 0x48, 0x00, 0xf4, 0x13, 0xc0, 0xbc, 0xc8, 0x66, 0x76, - 0x1f, 0x07, 0x54, 0x29, 0xd1, 0x34, 0xce, 0xe6, 0xf9, 0x3d, 0xbd, 0x29, 0x3b, 0xe8, 0xd4, 0xa9, 0x02, 0x17, 0x5c, - 0x95, 0x8c, 0x57, 0xd6, 0x13, 0xf9, 0xfc, 0xe6, 0x76, 0x93, 0x66, 0xf1, 0x87, 0xf2, 0x1f, 0x38, 0xb6, 0xba, 0x0e, - 0x8f, 0x4c, 0x9d, 0xae, 0x9d, 0x47, 0x5a, 0x7b, 0x21, 0x20, 0xa2, 0x5d, 0x43, 0xad, 0x17, 0x16, 0x7a, 0xa4, 0x27, - 0xc2, 0x39, 0x49, 0xd4, 0xb4, 0x03, 0x2d, 0x8d, 0xd0, 0xd7, 0xd7, 0x9c, 0xfe, 0xc2, 0x60, 0xed, 0xf3, 0x31, 0x03, - 0xb2, 0x12, 0xfd, 0x58, 0x3d, 0x34, 0x36, 0x73, 0xe8, 0x59, 0xab, 0xf2, 0xcc, 0xab, 0x0e, 0x07, 0xc4, 0x87, 0xd1, - 0x5f, 0xf2, 0xfb, 0xfd, 0xd7, 0x34, 0xff, 0x98, 0x50, 0xe3, 0x67, 0x9b, 0x01, 0xba, 0xf6, 0x5d, 0x79, 0x20, 0xea, - 0xb9, 0x56, 0x09, 0x42, 0xbc, 0x41, 0x4c, 0x34, 0x23, 0xe6, 0xe0, 0xb4, 0x43, 0xcd, 0x3f, 0x49, 0x0d, 0x08, 0x51, - 0xe2, 0x75, 0x4c, 0x59, 0x90, 0xd3, 0x26, 0x8e, 0xf4, 0xa3, 0x70, 0x22, 0x3f, 0x89, 0xaa, 0xc8, 0xee, 0xe1, 0x82, - 0xc1, 0xd4, 0x7b, 0xda, 0x2f, 0xd1, 0x6f, 0x09, 0x47, 0xce, 0xd1, 0xaa, 0x10, 0x44, 0x4e, 0x08, 0x6b, 0x0d, 0x61, - 0x82, 0xd8, 0x20, 0x5e, 0xf6, 0x5d, 0x92, 0xe1, 0x48, 0xc1, 0x65, 0x1d, 0x3b, 0xc6, 0x5c, 0x1d, 0x55, 0xaf, 0x01, - 0x8c, 0x57, 0x8e, 0xa0, 0xd9, 0x28, 0xb2, 0x4b, 0x88, 0x2a, 0x72, 0x3c, 0x01, 0xb5, 0x83, 0xd2, 0xd8, 0x4c, 0xcf, - 0xc7, 0x41, 0x3e, 0x5a, 0x54, 0xa8, 0x73, 0x62, 0x19, 0xaf, 0x01, 0x58, 0x3b, 0x57, 0xfd, 0x3c, 0xab, 0xc1, 0x93, - 0x86, 0xf8, 0x7c, 0x8c, 0xb6, 0x57, 0x36, 0x07, 0xd5, 0x76, 0x3a, 0x2b, 0xaf, 0x98, 0x2e, 0x07, 0xc6, 0x7d, 0xc3, - 0x2b, 0x8a, 0x33, 0xfc, 0xe4, 0xc1, 0x16, 0xe7, 0x4f, 0x37, 0xd4, 0x7e, 0xcc, 0x8d, 0x7a, 0x18, 0x68, 0x2d, 0x78, - 0x53, 0x10, 0xeb, 0xef, 0xc7, 0x8e, 0x6c, 0x1f, 0xb4, 0xc8, 0x68, 0xf2, 0xd9, 0xcf, 0x3f, 0x96, 0xe9, 0x2a, 0x85, - 0xfb, 0x92, 0x93, 0x45, 0x33, 0x0f, 0x81, 0xbd, 0x21, 0x86, 0xeb, 0xa3, 0xc2, 0x23, 0xca, 0xfa, 0x7d, 0xf8, 0x7d, - 0x95, 0x81, 0x29, 0x06, 0xae, 0x2b, 0x04, 0xe3, 0x21, 0x10, 0xc4, 0xc3, 0x34, 0x3a, 0x19, 0xd4, 0xa0, 0x0d, 0xdf, - 0x00, 0x64, 0x06, 0x78, 0x64, 0x2e, 0x3d, 0x02, 0xee, 0x02, 0xd7, 0x9e, 0x8c, 0xc7, 0xfe, 0xc4, 0x34, 0x34, 0x6a, - 0x4a, 0x33, 0x3d, 0x37, 0x7e, 0xd3, 0x51, 0x2d, 0xd7, 0xce, 0x7f, 0x7c, 0xc9, 0x6f, 0xd0, 0x0b, 0x5a, 0x5e, 0xee, - 0x23, 0x75, 0xb9, 0xcf, 0x28, 0x2e, 0x13, 0xc9, 0x61, 0x41, 0x2c, 0x4b, 0x38, 0xf0, 0x18, 0x95, 0x2c, 0xb6, 0xf4, - 0x58, 0x15, 0x2d, 0x5f, 0x94, 0x1b, 0xa4, 0x43, 0x27, 0x04, 0x4b, 0x54, 0x10, 0x2c, 0x81, 0x71, 0x11, 0x6b, 0xbe, - 0x19, 0xe4, 0x2c, 0x9e, 0x6d, 0xe6, 0x1c, 0x09, 0xeb, 0x92, 0xc3, 0xa1, 0x90, 0x60, 0x33, 0xd9, 0x6c, 0x3d, 0x67, - 0x6b, 0x9f, 0x81, 0x12, 0xa0, 0x94, 0x69, 0x82, 0xd2, 0xb4, 0x62, 0x2b, 0x6e, 0x5a, 0x83, 0xd5, 0x6a, 0xca, 0x56, - 0x35, 0x65, 0xe7, 0x34, 0xe5, 0xa8, 0x82, 0x92, 0x13, 0x4a, 0x51, 0x86, 0x01, 0x8c, 0xd8, 0x24, 0xba, 0xca, 0xd0, - 0xc7, 0x3b, 0xe1, 0x11, 0x54, 0x11, 0x91, 0x4f, 0x18, 0x42, 0x60, 0x22, 0x8a, 0x0b, 0x55, 0x28, 0x06, 0xc8, 0x88, - 0x04, 0x82, 0x89, 0x4a, 0x9d, 0x02, 0xf3, 0xd1, 0x54, 0x31, 0x6c, 0xda, 0x13, 0xe5, 0x7b, 0xea, 0xb8, 0x47, 0xd9, - 0xe6, 0x6f, 0x62, 0x17, 0x84, 0xc8, 0xdd, 0xb8, 0x53, 0x3f, 0x23, 0xde, 0xdb, 0x1d, 0x61, 0xfc, 0x64, 0xc7, 0x2d, - 0xc2, 0x15, 0xc1, 0x96, 0x6a, 0x0e, 0xb1, 0x98, 0x57, 0x93, 0x04, 0xb5, 0x2c, 0x89, 0xbf, 0xe1, 0xc9, 0x20, 0x67, - 0x4b, 0xf0, 0xa0, 0x9d, 0xb3, 0x0c, 0xf0, 0x57, 0xac, 0x16, 0xfd, 0x56, 0x7b, 0x4b, 0x90, 0x9f, 0x36, 0x76, 0xa3, - 0x30, 0x31, 0x82, 0x44, 0xdd, 0xae, 0x0c, 0xe4, 0x87, 0x8f, 0x38, 0x1d, 0x8f, 0x3d, 0x65, 0xcc, 0xad, 0x4c, 0x2f, - 0xd3, 0xb9, 0x92, 0x6f, 0xe4, 0x5e, 0xfa, 0xd8, 0x4b, 0xb0, 0x73, 0xc0, 0x1b, 0x48, 0x1b, 0x78, 0x03, 0xdb, 0x85, - 0xd7, 0x06, 0x09, 0x33, 0x02, 0x6c, 0x71, 0x7c, 0x8c, 0x94, 0xc0, 0x10, 0x8e, 0xb3, 0x14, 0x80, 0x69, 0xf4, 0x65, - 0xb6, 0xb2, 0x2f, 0xb3, 0x5a, 0xb3, 0xa5, 0x72, 0xba, 0x77, 0x6e, 0xdd, 0xce, 0x27, 0x12, 0x00, 0x4c, 0xea, 0x1c, - 0x88, 0x33, 0x13, 0xec, 0xd2, 0x24, 0xb2, 0x7c, 0x0a, 0xf3, 0x3b, 0xf1, 0xa6, 0x2c, 0x56, 0xaa, 0x2b, 0xda, 0x3e, - 0x33, 0xf9, 0x8c, 0x74, 0x12, 0x2a, 0xa0, 0xa0, 0x90, 0x6b, 0x7d, 0xfa, 0x3e, 0x7c, 0x1f, 0x14, 0x1a, 0x98, 0xad, - 0xc2, 0x3d, 0x4d, 0xd6, 0x48, 0xbd, 0x51, 0xf5, 0xfb, 0xe4, 0x1a, 0x48, 0x75, 0xe6, 0xd0, 0xb2, 0x27, 0x15, 0x06, - 0x88, 0x1d, 0xf5, 0x19, 0x09, 0x75, 0x20, 0xf5, 0x80, 0x21, 0x44, 0xdb, 0xf4, 0xf1, 0x27, 0x43, 0xa2, 0x0b, 0xb0, - 0x85, 0x68, 0x03, 0x3f, 0xfe, 0x04, 0xfb, 0x2c, 0x08, 0x8f, 0x69, 0xfe, 0x0e, 0x92, 0x8e, 0x0d, 0x9c, 0x56, 0x9f, - 0x82, 0x0f, 0x92, 0x1c, 0x4c, 0xd4, 0xc1, 0xcb, 0xfd, 0xa5, 0xdf, 0x87, 0x2d, 0x3b, 0x97, 0x52, 0x1d, 0x2b, 0xf5, - 0xb6, 0xad, 0xfd, 0x20, 0xda, 0x82, 0x23, 0x8b, 0xf8, 0x87, 0x0c, 0x11, 0xc1, 0xcc, 0x20, 0xc2, 0xae, 0x85, 0xba, - 0xdb, 0x53, 0x6a, 0x59, 0xd4, 0xdb, 0x9e, 0x52, 0xea, 0x36, 0x0c, 0xdf, 0x4d, 0x30, 0x53, 0xdc, 0xf0, 0x3f, 0x32, - 0x2f, 0xd4, 0x1b, 0x8f, 0x45, 0x81, 0xee, 0xf9, 0xfb, 0x25, 0xaf, 0x66, 0x1b, 0x65, 0xc2, 0xbc, 0xe3, 0xcb, 0x59, - 0x28, 0xbb, 0x5a, 0x1a, 0x77, 0xbe, 0x78, 0x4b, 0x35, 0x1f, 0xfc, 0xc3, 0x21, 0x81, 0x78, 0xa3, 0xf8, 0xea, 0xae, - 0x91, 0x5b, 0xd7, 0x64, 0x73, 0x55, 0x02, 0xea, 0xf7, 0xf9, 0x1a, 0xf7, 0x5b, 0xac, 0x7f, 0xf7, 0x34, 0xc8, 0x58, - 0xcd, 0x70, 0xc5, 0x14, 0x3e, 0x05, 0x80, 0xc1, 0xe1, 0x54, 0x90, 0x16, 0x78, 0xc3, 0xcb, 0xe1, 0xe5, 0x64, 0x43, - 0x26, 0xdd, 0x8d, 0x8f, 0xdc, 0x59, 0xa0, 0xea, 0xfd, 0x8e, 0xe2, 0xa4, 0x41, 0xa2, 0xb1, 0xd7, 0xe0, 0x8b, 0x2c, - 0xa3, 0x5c, 0x34, 0x71, 0x1f, 0x93, 0xaf, 0xf4, 0x00, 0xe6, 0x2a, 0x94, 0x00, 0xd1, 0x6f, 0x2c, 0x8b, 0x8d, 0x68, - 0x5b, 0x6c, 0x60, 0x29, 0x55, 0x73, 0xbd, 0x9a, 0xbe, 0x78, 0x25, 0x9a, 0xf7, 0xd1, 0x8c, 0x53, 0x1a, 0x0d, 0x38, - 0x4e, 0xa3, 0x70, 0xfb, 0xe1, 0x5e, 0x94, 0xcb, 0x0c, 0x2c, 0xd9, 0x2a, 0x9c, 0xe2, 0xb2, 0x51, 0x67, 0xc4, 0x8b, - 0x3c, 0x56, 0x00, 0x1d, 0x8f, 0x09, 0x80, 0xea, 0x82, 0x80, 0x8a, 0x68, 0x29, 0xbd, 0x15, 0x5a, 0x2c, 0xd4, 0x1b, - 0x8e, 0x52, 0xf8, 0x23, 0xfd, 0x79, 0x90, 0x4f, 0x01, 0x88, 0x5d, 0x1f, 0x47, 0x6f, 0x8a, 0x92, 0x3e, 0x55, 0xcc, - 0x72, 0x39, 0x98, 0xc0, 0xae, 0x4e, 0x64, 0xa8, 0x15, 0xe4, 0xad, 0xba, 0xf2, 0x56, 0x26, 0x6f, 0x63, 0x9c, 0x92, - 0x1f, 0xb9, 0xe9, 0x58, 0x23, 0x06, 0x5e, 0x79, 0x5a, 0xa7, 0x09, 0xd2, 0xe4, 0x02, 0x18, 0x86, 0xf8, 0x36, 0xf3, - 0x5e, 0x78, 0x8e, 0x54, 0x05, 0xc9, 0x6c, 0x97, 0x79, 0xea, 0x22, 0xaa, 0xaf, 0x9c, 0x5a, 0x3a, 0x73, 0xfa, 0x11, - 0xc0, 0x7b, 0x4c, 0x4d, 0x1a, 0xf2, 0x11, 0x6e, 0x4b, 0xf1, 0xf5, 0x56, 0x5d, 0xe3, 0xa5, 0xd1, 0xb9, 0x7b, 0xf9, - 0xd2, 0x9d, 0x06, 0xfd, 0x14, 0x04, 0xe5, 0x7c, 0x51, 0x0a, 0xd8, 0x53, 0x66, 0x73, 0xbd, 0x5a, 0xb5, 0x42, 0xeb, - 0x70, 0x18, 0x6b, 0x47, 0x21, 0xad, 0xce, 0x02, 0xb6, 0x1a, 0xe9, 0x94, 0x00, 0x21, 0x38, 0x4e, 0xc3, 0x4e, 0x30, - 0xee, 0xd2, 0x69, 0x44, 0xd6, 0x2b, 0x25, 0xe9, 0xc2, 0x0c, 0x92, 0x7f, 0x92, 0xd7, 0x33, 0xa0, 0x25, 0x80, 0x43, - 0x11, 0x4b, 0x78, 0x38, 0x49, 0xae, 0x00, 0x3a, 0x1d, 0x0e, 0x2a, 0x0d, 0xcd, 0x59, 0xcd, 0x92, 0xf9, 0x24, 0x96, - 0xaa, 0xca, 0xc3, 0xc1, 0x53, 0x6e, 0x06, 0xfd, 0x7e, 0x36, 0x2d, 0x95, 0x0b, 0x40, 0x10, 0xeb, 0xc2, 0x00, 0xf1, - 0x48, 0x0b, 0x4f, 0x16, 0x7d, 0x4a, 0xe2, 0x97, 0xb3, 0x64, 0x6e, 0xb2, 0xe1, 0x1d, 0x18, 0xc1, 0x66, 0x5c, 0x97, - 0x94, 0x69, 0x8f, 0xca, 0xef, 0x19, 0x3d, 0xb5, 0x7d, 0xad, 0xd5, 0x16, 0xb1, 0xae, 0x83, 0xab, 0x12, 0xf5, 0x14, - 0x1f, 0x94, 0x24, 0x78, 0xbf, 0x76, 0x6e, 0x46, 0xca, 0xd7, 0x22, 0xf7, 0x83, 0x76, 0xa6, 0x56, 0x0e, 0x1c, 0x81, - 0x1c, 0xab, 0xa8, 0xe4, 0xf5, 0xae, 0x43, 0xf0, 0xe8, 0xae, 0x54, 0xa0, 0x1c, 0x7c, 0x0d, 0x62, 0x74, 0x7d, 0xd5, - 0x59, 0x43, 0xcd, 0x34, 0xaa, 0x3c, 0x82, 0x4e, 0x1d, 0xc0, 0x93, 0x82, 0x97, 0x5a, 0xfd, 0x78, 0x38, 0x78, 0xe6, - 0x07, 0x7f, 0x95, 0xe9, 0x5b, 0x88, 0x89, 0x72, 0xaa, 0x11, 0x12, 0x57, 0x4a, 0x12, 0xf1, 0xf1, 0xa2, 0x65, 0xc5, - 0xa8, 0x0c, 0x1f, 0x78, 0xa5, 0xca, 0x57, 0xa7, 0x2a, 0x2f, 0x46, 0xda, 0x96, 0xc0, 0x6b, 0xf2, 0x0f, 0x91, 0x6b, - 0xde, 0xfa, 0xba, 0xab, 0x0c, 0x7d, 0x27, 0x2b, 0xd0, 0x11, 0x6c, 0x65, 0x29, 0x39, 0xe0, 0x93, 0xea, 0xae, 0x5a, - 0xb5, 0x3e, 0xa7, 0x6c, 0x23, 0xdc, 0xe4, 0xd7, 0xb1, 0x83, 0x23, 0xe5, 0x37, 0x78, 0x2e, 0x80, 0xbd, 0x06, 0xec, - 0xcd, 0x39, 0x2b, 0x9a, 0x47, 0x87, 0xb4, 0x2d, 0xd0, 0xc8, 0xcc, 0xed, 0x5c, 0xdd, 0xb7, 0xe5, 0x51, 0x1a, 0x43, - 0x64, 0xda, 0x23, 0xd3, 0xc1, 0x66, 0x94, 0xff, 0x9e, 0xf2, 0x5b, 0x85, 0x63, 0xe0, 0xdb, 0xa9, 0x77, 0x00, 0x55, - 0x4f, 0x1b, 0x64, 0xac, 0x19, 0x86, 0x56, 0x76, 0xb9, 0x14, 0x5a, 0x82, 0x96, 0xba, 0x09, 0x82, 0xf3, 0x23, 0xa2, - 0x1c, 0x01, 0xe8, 0x22, 0x05, 0x4c, 0xf0, 0x53, 0xda, 0xee, 0x7e, 0x7f, 0x9d, 0x7a, 0xe4, 0xde, 0x15, 0x6a, 0x94, - 0x50, 0x82, 0xb1, 0x9f, 0x68, 0xcc, 0xa0, 0xa3, 0x2b, 0x72, 0xc2, 0xb3, 0x56, 0x87, 0x75, 0xdd, 0x94, 0x41, 0x59, - 0x1c, 0xf3, 0x6a, 0x3a, 0xfb, 0xfd, 0xc9, 0xbe, 0x6e, 0x90, 0x85, 0xfc, 0x77, 0xd6, 0x43, 0x32, 0xe8, 0x1e, 0x84, - 0x42, 0xf4, 0xe6, 0xc1, 0x0c, 0xff, 0x63, 0x1b, 0x9e, 0x7d, 0xc7, 0x8d, 0x3a, 0x01, 0xcc, 0x11, 0xd7, 0x4b, 0x4f, - 0xd1, 0xd6, 0xc3, 0x2d, 0x90, 0xad, 0xf1, 0xf2, 0xd6, 0x5e, 0x03, 0x39, 0xc5, 0xf1, 0xdf, 0xf1, 0x4c, 0xad, 0x6c, - 0xf0, 0xd3, 0x53, 0xb6, 0x03, 0x0f, 0x2f, 0x42, 0x40, 0x31, 0x2c, 0x1b, 0x7f, 0x67, 0x39, 0xce, 0xe8, 0xbf, 0x79, - 0xc4, 0x30, 0x58, 0x44, 0x7e, 0x7c, 0x59, 0x0a, 0xf1, 0x55, 0x78, 0x6f, 0x2b, 0xef, 0x8e, 0x9c, 0x32, 0xef, 0xf4, - 0x30, 0xba, 0x2e, 0x49, 0xdf, 0x25, 0x1f, 0x5b, 0xc3, 0xf6, 0xbb, 0x76, 0xbf, 0x19, 0x22, 0x08, 0xa1, 0x1c, 0x3f, - 0x67, 0x74, 0x42, 0xe3, 0xc3, 0x6a, 0x76, 0x7a, 0xfd, 0xde, 0x39, 0x5e, 0xb0, 0x35, 0x1a, 0xe0, 0xf1, 0xd0, 0xc5, - 0x3c, 0x51, 0x43, 0xa7, 0xeb, 0xda, 0x39, 0x78, 0x60, 0x90, 0xe5, 0xc9, 0x77, 0x0c, 0x4b, 0xec, 0x4f, 0x22, 0x9e, - 0xb4, 0x55, 0x1b, 0x9b, 0x23, 0xd5, 0x46, 0xcd, 0xc0, 0x0f, 0x5e, 0x41, 0x81, 0xd1, 0x05, 0x69, 0x05, 0xc6, 0xe1, - 0x08, 0x40, 0x56, 0x8c, 0xe3, 0x91, 0xc1, 0x04, 0x86, 0x74, 0x43, 0x51, 0x00, 0x1e, 0x1e, 0xc7, 0x83, 0x90, 0x01, - 0xa4, 0x0b, 0x1e, 0x1a, 0xb6, 0x49, 0x48, 0xf9, 0x79, 0x9e, 0xd7, 0x6a, 0x08, 0x7d, 0x67, 0xa1, 0x3a, 0xf6, 0x23, - 0xed, 0x15, 0xeb, 0x5a, 0x95, 0x8e, 0x6c, 0x75, 0x80, 0xbe, 0x21, 0x03, 0xdf, 0x3a, 0xb6, 0x00, 0x88, 0x96, 0xf8, - 0x2d, 0xf5, 0x6a, 0x5f, 0xc6, 0xac, 0x50, 0xaf, 0x2f, 0x4c, 0xbb, 0x5e, 0x4b, 0x8b, 0x02, 0x2a, 0x6e, 0x5b, 0xb5, - 0x3d, 0x92, 0xf3, 0x1f, 0xdf, 0x75, 0xb4, 0xe3, 0xb3, 0x53, 0x63, 0x4b, 0x28, 0x73, 0x8b, 0x27, 0xb2, 0x3a, 0xda, - 0x52, 0x9d, 0xea, 0x03, 0x2e, 0x35, 0xa9, 0xce, 0x0c, 0x0c, 0xaf, 0x11, 0xa0, 0xdc, 0x42, 0x24, 0x8d, 0xc3, 0xde, - 0xf9, 0x64, 0x50, 0x30, 0xb7, 0x48, 0x40, 0x02, 0xdb, 0xd8, 0xda, 0x45, 0x73, 0xfd, 0xfa, 0x2d, 0xf5, 0xaa, 0x36, - 0x55, 0x3d, 0x78, 0xe3, 0x05, 0xce, 0xde, 0x69, 0x2d, 0x20, 0x80, 0xc2, 0xd6, 0xb2, 0x1c, 0x9c, 0xbb, 0x5d, 0xd5, - 0x52, 0x51, 0x46, 0xfd, 0xfe, 0xf9, 0x6f, 0x29, 0x2a, 0x62, 0x4f, 0x15, 0xa7, 0xac, 0xdf, 0x6e, 0x99, 0x8b, 0xca, - 0x92, 0x37, 0xa8, 0xa2, 0xb5, 0x3a, 0x6a, 0x2a, 0xd7, 0xcd, 0x55, 0x4b, 0x26, 0x88, 0xd1, 0x7d, 0xba, 0xd6, 0xb9, - 0x53, 0xef, 0xbd, 0x8a, 0x23, 0x06, 0x82, 0x9b, 0xee, 0xf1, 0xc1, 0x41, 0x68, 0x54, 0x94, 0x0b, 0x6e, 0x94, 0x56, - 0x95, 0x94, 0x42, 0xde, 0xaa, 0x68, 0xce, 0xf4, 0x11, 0x00, 0x11, 0x60, 0x95, 0xa8, 0xff, 0xcd, 0x97, 0xc6, 0x78, - 0xf0, 0xc0, 0xd7, 0xe4, 0x3a, 0xb6, 0xde, 0x3f, 0xad, 0x91, 0x56, 0x1b, 0xc7, 0xa4, 0x56, 0xbd, 0x6c, 0x15, 0x2f, - 0xbb, 0xd7, 0xa9, 0x18, 0x3c, 0xff, 0x9f, 0xfb, 0x00, 0x35, 0xa2, 0xa5, 0x0c, 0x6e, 0x5d, 0x0d, 0xd0, 0xf8, 0x70, - 0x2c, 0x7c, 0xe3, 0x87, 0x8c, 0xf3, 0xc1, 0x0c, 0x1d, 0xd5, 0xe6, 0xe0, 0x80, 0xe0, 0xa8, 0xee, 0xd1, 0x98, 0x30, - 0x0b, 0xe7, 0x1e, 0x04, 0xaa, 0x4f, 0xdc, 0x67, 0x5c, 0x7b, 0x41, 0x9b, 0xc0, 0x27, 0xeb, 0xba, 0xa6, 0x08, 0x70, - 0x11, 0x1b, 0x13, 0x31, 0xc4, 0x65, 0x93, 0x48, 0x7d, 0x33, 0x06, 0x05, 0x40, 0x71, 0x5d, 0x91, 0x5c, 0xba, 0x48, - 0xf3, 0x4a, 0x94, 0xb5, 0x6e, 0x46, 0xc5, 0x8a, 0x21, 0x00, 0x3c, 0x04, 0xc5, 0x55, 0x65, 0x26, 0x34, 0x62, 0x03, - 0xa9, 0x2c, 0x05, 0xab, 0x86, 0x85, 0xdf, 0xb4, 0xdf, 0x24, 0x27, 0xbd, 0xf3, 0x71, 0xeb, 0xdc, 0xb1, 0xef, 0x1d, - 0x85, 0x94, 0xf6, 0x50, 0x4c, 0x10, 0x04, 0x3f, 0xad, 0xc3, 0xf9, 0x33, 0x7e, 0x4d, 0x60, 0x2a, 0xb2, 0x19, 0x03, - 0x0e, 0x42, 0x44, 0x66, 0xfc, 0x9e, 0xc3, 0x6b, 0x5e, 0x4e, 0xc2, 0xe1, 0xd0, 0x07, 0x7d, 0x28, 0xcf, 0x66, 0xe1, - 0x50, 0xcc, 0xa5, 0xf7, 0x3a, 0x58, 0xeb, 0x42, 0x5e, 0x4f, 0x42, 0x44, 0x0b, 0x0d, 0x7d, 0x70, 0x5e, 0x77, 0xcd, - 0x11, 0x96, 0x00, 0x34, 0x71, 0xf4, 0x65, 0xfd, 0x7e, 0xe4, 0x69, 0x43, 0x8b, 0x14, 0x17, 0x8d, 0x32, 0x9b, 0xe5, - 0xb2, 0x13, 0x36, 0xae, 0xdd, 0x02, 0xa1, 0x78, 0x98, 0xb6, 0x50, 0xb5, 0x9e, 0xea, 0xf5, 0xdc, 0xb4, 0xfb, 0xee, - 0x51, 0xb5, 0xca, 0x91, 0xce, 0xda, 0x74, 0xa5, 0x56, 0xb7, 0x8c, 0xaa, 0x75, 0x96, 0x46, 0x54, 0xb9, 0x49, 0xee, - 0x1a, 0xb5, 0xe0, 0x93, 0x0d, 0x5d, 0xa6, 0xec, 0x6c, 0x0d, 0x4e, 0x1c, 0x79, 0x2e, 0xb9, 0xe5, 0xbb, 0xf3, 0x8a, - 0xee, 0x4e, 0xb5, 0x6f, 0x01, 0xee, 0xcd, 0xb0, 0x21, 0x73, 0x5e, 0x63, 0xa7, 0x41, 0x98, 0x04, 0x7e, 0xc4, 0x3e, - 0x66, 0xc8, 0x06, 0x03, 0x3a, 0x0a, 0xe9, 0x7f, 0x6d, 0x99, 0x23, 0x01, 0x93, 0xbf, 0x9e, 0xfb, 0xcd, 0xa2, 0xc8, - 0x61, 0x31, 0x7e, 0xdc, 0x60, 0xa4, 0xb1, 0x5a, 0x83, 0x61, 0x79, 0x87, 0xc8, 0x9f, 0xda, 0x1d, 0xd3, 0x54, 0xc7, - 0x9b, 0xf5, 0x5a, 0xf3, 0xab, 0xa7, 0x4f, 0x75, 0x7d, 0xfe, 0xdb, 0xf7, 0x97, 0x61, 0xcd, 0xec, 0x0f, 0x41, 0x28, - 0xed, 0xde, 0x2d, 0xce, 0x1d, 0x89, 0xde, 0xb1, 0xd2, 0xcc, 0x2e, 0xed, 0x92, 0x5d, 0x9a, 0xd2, 0x6e, 0xc8, 0xf5, - 0xea, 0x1b, 0xe5, 0x8d, 0x9d, 0x57, 0x4c, 0xf7, 0xef, 0x85, 0xde, 0x51, 0x4e, 0xd5, 0x04, 0x22, 0x9a, 0xb4, 0x23, - 0x71, 0xbb, 0x57, 0x86, 0xcf, 0x27, 0x79, 0xbb, 0x84, 0xa3, 0xae, 0x61, 0xb9, 0xf9, 0xf6, 0x3f, 0xf2, 0xaa, 0xb3, - 0xc2, 0xed, 0x97, 0xc6, 0xac, 0xfd, 0x29, 0x88, 0xab, 0xfa, 0xc3, 0x7b, 0x52, 0x33, 0x25, 0xff, 0x57, 0x3d, 0x06, - 0xae, 0x7e, 0x32, 0xed, 0xe8, 0x9e, 0x42, 0xd8, 0x60, 0xf6, 0xf3, 0xe3, 0x87, 0x16, 0xac, 0xaa, 0x0b, 0x14, 0xc9, - 0x01, 0x74, 0xee, 0x92, 0x11, 0xde, 0xef, 0x18, 0xe7, 0xfe, 0xd5, 0x2f, 0x6a, 0x72, 0x84, 0x88, 0x76, 0x11, 0x0e, - 0x00, 0xe2, 0x4e, 0x53, 0x59, 0x87, 0x1a, 0xa0, 0x0f, 0x08, 0xac, 0x43, 0xdf, 0x66, 0x00, 0x07, 0x7d, 0xb4, 0x79, - 0x16, 0x81, 0xbc, 0xee, 0xdd, 0xb3, 0x77, 0x6c, 0xe7, 0xf3, 0xeb, 0x55, 0xea, 0xdd, 0xa3, 0x43, 0xf0, 0xf9, 0xd8, - 0x9f, 0x5e, 0x06, 0x06, 0x17, 0x9a, 0xbd, 0x7b, 0x26, 0xd8, 0x8e, 0xed, 0x9e, 0x21, 0x52, 0x51, 0x77, 0xfe, 0xe1, - 0xa5, 0x89, 0x9e, 0x77, 0x5e, 0xb8, 0xe3, 0x4b, 0x00, 0x0f, 0x64, 0x31, 0xa0, 0xf8, 0x2c, 0xbd, 0x7f, 0xb2, 0x04, - 0xd4, 0xe4, 0xb7, 0x7c, 0xed, 0xbd, 0xa7, 0xd4, 0x05, 0xfc, 0x39, 0xa0, 0xf4, 0x49, 0xce, 0xbd, 0xbb, 0xe1, 0xad, - 0x7f, 0xf1, 0x1c, 0x9c, 0x27, 0x56, 0xc3, 0x05, 0xfc, 0x55, 0xf0, 0xa1, 0x77, 0x37, 0xc0, 0xc4, 0x92, 0x0f, 0xbd, - 0xd5, 0x00, 0x52, 0x15, 0x2e, 0x24, 0xc6, 0x3e, 0xfc, 0x1a, 0xe4, 0x0c, 0xff, 0xf8, 0x4d, 0x63, 0xb0, 0xfe, 0x1a, - 0x14, 0x1a, 0x8d, 0xb5, 0x54, 0x21, 0x4b, 0xb1, 0x38, 0x13, 0x60, 0x13, 0x8e, 0xbb, 0x7d, 0xb1, 0xaa, 0xcd, 0x5a, - 0xd0, 0x9f, 0x8f, 0xf8, 0x1e, 0x8d, 0xd5, 0x55, 0x39, 0x17, 0xe5, 0x27, 0xa4, 0x4f, 0x75, 0x7c, 0x8c, 0x8a, 0x4d, - 0xdd, 0x9d, 0x4e, 0xb5, 0xea, 0x48, 0xfb, 0x4d, 0xb9, 0x06, 0x3b, 0x5e, 0x27, 0x47, 0x96, 0xc2, 0xb3, 0x0e, 0x3b, - 0x2f, 0x9d, 0x12, 0x1d, 0x86, 0xf1, 0x6e, 0xab, 0x9e, 0x31, 0x94, 0xe7, 0x06, 0x63, 0xba, 0xe0, 0x11, 0xbf, 0x1e, - 0xe4, 0x32, 0x34, 0xe6, 0x23, 0xb2, 0x61, 0x28, 0x1f, 0x5a, 0x64, 0x48, 0x88, 0x78, 0x0f, 0x95, 0x80, 0x6d, 0x0b, - 0xca, 0xa4, 0x80, 0xb3, 0x68, 0xf0, 0x5b, 0xed, 0xe5, 0xc0, 0x7b, 0x10, 0xf9, 0x8d, 0x74, 0x29, 0x97, 0xd8, 0xe8, - 0xc4, 0xb1, 0x2c, 0xb4, 0xf3, 0xb8, 0xfe, 0x3a, 0x06, 0xf5, 0x7b, 0xa5, 0xdf, 0xa0, 0x9c, 0xfd, 0x49, 0xb2, 0x4e, - 0x1b, 0x4f, 0x8c, 0x7f, 0xb9, 0xca, 0x3f, 0x45, 0x4b, 0x3d, 0xfc, 0x7f, 0xc6, 0x14, 0x4a, 0xff, 0x2a, 0x2d, 0xa3, - 0xcd, 0x6a, 0x29, 0x4a, 0x91, 0x47, 0xe2, 0xe4, 0x6b, 0x91, 0x9d, 0xcb, 0x77, 0x3e, 0x85, 0x7e, 0x01, 0x68, 0xd9, - 0x27, 0xc8, 0xe8, 0x5f, 0x98, 0xe0, 0xc3, 0x5f, 0xb4, 0x73, 0x6d, 0xce, 0xc7, 0x93, 0xfc, 0xca, 0xda, 0xbb, 0x1d, - 0x2f, 0x12, 0xa3, 0x18, 0xcb, 0x7d, 0xd5, 0xcd, 0xca, 0x89, 0x4a, 0x0e, 0x8c, 0x74, 0x4d, 0xf6, 0x72, 0x25, 0xeb, - 0x76, 0xba, 0x95, 0x40, 0x44, 0x15, 0x78, 0x8f, 0x71, 0x15, 0xfb, 0x08, 0xa6, 0xeb, 0x8e, 0xcb, 0x68, 0xc7, 0x7b, - 0xc6, 0xab, 0x13, 0x65, 0x05, 0xb7, 0x1b, 0xd1, 0x9e, 0xd0, 0xd1, 0x4f, 0x93, 0xda, 0xb2, 0x70, 0x00, 0x72, 0x97, - 0x30, 0x96, 0x0d, 0xc1, 0x8a, 0x41, 0xe9, 0xeb, 0x35, 0x25, 0xcb, 0x02, 0x2c, 0x3a, 0xbb, 0x8c, 0x40, 0x0c, 0xeb, - 0xa6, 0x39, 0xa1, 0xe3, 0xa5, 0x8b, 0xf3, 0x5e, 0xab, 0x48, 0xc1, 0x33, 0x5a, 0x74, 0xcc, 0x4d, 0x47, 0xba, 0x31, - 0xda, 0xdb, 0x97, 0x06, 0x21, 0xc5, 0xf3, 0x07, 0xb6, 0x5a, 0x17, 0x17, 0x89, 0x57, 0xc8, 0x44, 0x0b, 0x62, 0x29, - 0x02, 0x33, 0x5e, 0x68, 0x1a, 0x61, 0x82, 0x32, 0x25, 0x58, 0xb4, 0x46, 0x87, 0xf6, 0x87, 0x25, 0xec, 0x1e, 0x63, - 0x04, 0x08, 0x54, 0x99, 0xbe, 0x84, 0xad, 0x09, 0xb3, 0xa9, 0x8b, 0x0d, 0xd0, 0x56, 0x31, 0x34, 0x08, 0x6b, 0x43, - 0xcc, 0xa7, 0x34, 0xbf, 0xfb, 0x27, 0x16, 0x63, 0x7b, 0x02, 0xb1, 0xbd, 0xdb, 0x35, 0x09, 0xd3, 0xbd, 0x16, 0x37, - 0xd6, 0xcb, 0xed, 0x29, 0xc7, 0xd4, 0x8e, 0xb5, 0x51, 0x3b, 0xd6, 0x52, 0xef, 0x58, 0x6b, 0xbd, 0x63, 0xdd, 0x35, - 0xfc, 0x63, 0xe6, 0xc5, 0x2c, 0x01, 0xfd, 0xee, 0x8a, 0xab, 0x06, 0x41, 0x33, 0x36, 0xec, 0x16, 0x7e, 0x4b, 0xac, - 0xdd, 0xd2, 0xbf, 0x58, 0xb2, 0x85, 0xe9, 0x03, 0xdd, 0x3a, 0xc0, 0x32, 0xa2, 0x26, 0xdf, 0x23, 0xef, 0xa6, 0xb3, - 0xa2, 0x70, 0x7b, 0x62, 0x0b, 0x9f, 0xbd, 0x33, 0x6f, 0xde, 0x3f, 0x8b, 0x20, 0xf7, 0x8e, 0x7b, 0xf7, 0xc3, 0x77, - 0xfe, 0x85, 0x6e, 0x81, 0x9c, 0xcc, 0x72, 0x06, 0x52, 0x47, 0x7c, 0x86, 0x68, 0x65, 0x4f, 0xf9, 0x4e, 0xc8, 0x9d, - 0x6d, 0xfd, 0xec, 0xde, 0xdd, 0xd6, 0xee, 0x9e, 0xdd, 0xb3, 0x6a, 0x44, 0xb1, 0xe2, 0x34, 0x45, 0xc2, 0x2c, 0xda, - 0x00, 0x4f, 0xbd, 0x7c, 0xbf, 0x63, 0xc7, 0x1c, 0xee, 0x9e, 0x75, 0x74, 0xbc, 0x9c, 0x03, 0x76, 0xf7, 0x1f, 0x6d, - 0xc2, 0xc6, 0x4a, 0xd7, 0x2a, 0x74, 0xb8, 0x7b, 0x96, 0x69, 0x3c, 0x87, 0x23, 0xf9, 0x74, 0xac, 0xb1, 0x41, 0x50, - 0xd7, 0xe7, 0x0c, 0x6a, 0xc7, 0xee, 0x6b, 0xc2, 0x2e, 0x3b, 0xe6, 0xb5, 0xae, 0x79, 0x7b, 0xe5, 0xa9, 0xd8, 0x10, - 0xd0, 0xe1, 0x6b, 0x75, 0x83, 0xfc, 0x4b, 0xe0, 0x14, 0x01, 0x20, 0x87, 0xe3, 0x25, 0x8f, 0x7d, 0x9f, 0x66, 0x69, - 0xbd, 0x43, 0xad, 0x45, 0x65, 0x59, 0x86, 0xb5, 0xf7, 0x83, 0x56, 0x0c, 0x4b, 0x4d, 0xff, 0x74, 0x1c, 0xb8, 0x9d, - 0xed, 0x56, 0xc6, 0x2e, 0xe3, 0x59, 0x71, 0xf1, 0xcb, 0x69, 0xa1, 0x5c, 0xbb, 0x79, 0x1b, 0xbf, 0x69, 0xb5, 0x64, - 0x69, 0xad, 0x87, 0xbc, 0xb4, 0x2c, 0x22, 0x10, 0xc0, 0x70, 0xa4, 0xec, 0x62, 0x09, 0xf7, 0x08, 0xab, 0x7b, 0x10, - 0x4a, 0xe6, 0x85, 0x8b, 0xe7, 0x2c, 0x86, 0x44, 0x80, 0xed, 0x0e, 0x15, 0xdb, 0xc2, 0xc5, 0x73, 0xb6, 0xe1, 0x45, - 0xbf, 0x9f, 0xa9, 0x4e, 0x21, 0xeb, 0xce, 0x92, 0x6f, 0x54, 0x73, 0xac, 0xa1, 0x66, 0x6b, 0x93, 0x6c, 0x8d, 0x73, - 0x5b, 0xf1, 0x71, 0xd7, 0x56, 0x7c, 0xac, 0xac, 0x75, 0xe9, 0x5e, 0xef, 0x51, 0x5d, 0x00, 0x5b, 0xff, 0xed, 0xf1, - 0xca, 0xf5, 0x7c, 0x46, 0x00, 0x5f, 0x0b, 0x3e, 0x9e, 0x2c, 0xd0, 0xab, 0x64, 0xe1, 0xdf, 0x0e, 0xd4, 0xf8, 0x3b, - 0x9d, 0xbb, 0x00, 0xe8, 0x4a, 0xca, 0x2b, 0x20, 0xef, 0x20, 0xc7, 0xdc, 0xb2, 0x2b, 0xef, 0x4f, 0xbe, 0xc3, 0xde, - 0xf1, 0x7a, 0xb6, 0x98, 0xb3, 0x1d, 0x38, 0x15, 0x24, 0x03, 0x7b, 0x59, 0xb1, 0x5d, 0x10, 0xdb, 0x09, 0xbf, 0x11, - 0x30, 0xe5, 0x0b, 0x08, 0xe2, 0x0a, 0x6e, 0x21, 0x0e, 0x4f, 0xfe, 0x39, 0xb8, 0x6f, 0x6d, 0xd6, 0xf7, 0xcc, 0xea, - 0x9c, 0x60, 0xcd, 0xac, 0x1e, 0x0c, 0x96, 0xcd, 0x64, 0xd5, 0xef, 0x7b, 0x3b, 0xed, 0xf8, 0x74, 0x27, 0x75, 0x62, - 0xa7, 0xb5, 0x5a, 0x0b, 0xf6, 0x4e, 0x6a, 0x5d, 0x8c, 0xa1, 0x07, 0x88, 0x9f, 0x6e, 0x07, 0xfc, 0xbe, 0x63, 0x6d, - 0x79, 0xef, 0xd8, 0x82, 0xed, 0xe0, 0x12, 0xd4, 0xb4, 0x97, 0xfd, 0x49, 0xe5, 0x82, 0x76, 0xec, 0x92, 0x78, 0x38, - 0x63, 0x56, 0x29, 0x33, 0xeb, 0xa4, 0xba, 0x12, 0x9d, 0x31, 0x9d, 0xb5, 0x9e, 0xcf, 0xd5, 0x7c, 0x52, 0x68, 0x50, - 0xbf, 0x73, 0xe2, 0x23, 0x2a, 0x3a, 0x4f, 0x60, 0x6b, 0x59, 0x41, 0xac, 0xf6, 0x39, 0x58, 0x6b, 0xb5, 0x4b, 0xbf, - 0x97, 0x0f, 0xb8, 0x4d, 0x39, 0xac, 0x03, 0x83, 0x9a, 0x13, 0x2b, 0xea, 0x31, 0xdb, 0x31, 0x6e, 0x7e, 0x7a, 0xf9, - 0x83, 0x13, 0x96, 0xac, 0x58, 0xed, 0x4f, 0x7f, 0x79, 0xe6, 0xe9, 0xef, 0xd4, 0xfe, 0x85, 0xf0, 0x83, 0xf1, 0xbf, - 0x6b, 0xf7, 0xb5, 0x16, 0xa3, 0xb2, 0x55, 0x8e, 0xd0, 0xb8, 0x5b, 0x49, 0x93, 0xe5, 0x67, 0xe1, 0x09, 0x6b, 0xc1, - 0xb3, 0x5c, 0x2f, 0xd1, 0xac, 0x80, 0x15, 0xd6, 0x32, 0x09, 0x57, 0x18, 0xab, 0xa5, 0xad, 0xbe, 0x45, 0xd3, 0x1c, - 0x1f, 0xce, 0xb5, 0x41, 0x99, 0x72, 0x76, 0x46, 0xac, 0x86, 0xcb, 0xb0, 0x34, 0xa1, 0x08, 0xd9, 0xbd, 0x1d, 0xdc, - 0xd8, 0x29, 0x4b, 0x29, 0xc3, 0x39, 0x06, 0x13, 0x1e, 0x89, 0x51, 0x95, 0xef, 0xef, 0x4b, 0x8a, 0x9c, 0xb6, 0xe5, - 0xa0, 0x0a, 0x61, 0x1f, 0x49, 0x94, 0xc0, 0xad, 0x48, 0x0b, 0x45, 0xca, 0xe2, 0x6f, 0x07, 0xe8, 0x02, 0x2f, 0xa0, - 0xae, 0x46, 0xdd, 0xfe, 0x70, 0xc4, 0xc3, 0x47, 0xa6, 0x3e, 0x30, 0x62, 0x49, 0xa0, 0xb6, 0x17, 0x59, 0x7a, 0x07, - 0x2a, 0xfc, 0x1e, 0xae, 0x26, 0x62, 0x3f, 0xb7, 0xa4, 0xa8, 0xc8, 0x46, 0x7a, 0x43, 0x6b, 0xf0, 0x08, 0xad, 0x29, - 0x2f, 0x9d, 0x54, 0x9b, 0x74, 0xde, 0x11, 0x72, 0xac, 0xbe, 0xb5, 0x84, 0xd1, 0xae, 0xe8, 0xc5, 0xbd, 0xa3, 0xf7, - 0x3c, 0x5d, 0xf5, 0xdc, 0x9f, 0xb8, 0x62, 0x9e, 0xdc, 0x46, 0xa0, 0x6e, 0x05, 0xd5, 0xed, 0x83, 0x4a, 0xb0, 0x60, - 0x49, 0xbb, 0x8f, 0xdf, 0xce, 0xda, 0x81, 0xa8, 0x8c, 0x55, 0xfa, 0x96, 0x24, 0xec, 0x89, 0x41, 0xa7, 0x50, 0x95, - 0xdb, 0xdd, 0xd1, 0x16, 0xb8, 0x8e, 0x59, 0x8a, 0x5e, 0xd8, 0x22, 0x77, 0xcb, 0xbf, 0x7b, 0xae, 0xc8, 0xd9, 0x2f, - 0x01, 0xc1, 0xa9, 0xf9, 0x86, 0xf8, 0x72, 0x84, 0x47, 0xd5, 0x2d, 0x70, 0x9c, 0xbe, 0x03, 0xf8, 0x87, 0xc3, 0x25, - 0x68, 0x02, 0x62, 0xc1, 0x7a, 0x69, 0xdc, 0x63, 0xbd, 0xb8, 0xd8, 0xdc, 0x25, 0xf9, 0x06, 0x9c, 0x19, 0x28, 0xd5, - 0xd2, 0x0f, 0x1c, 0xab, 0x05, 0x54, 0x38, 0x98, 0x9d, 0xd4, 0x0b, 0xcb, 0xa8, 0xc7, 0xf4, 0xf9, 0x19, 0xec, 0x1d, - 0x21, 0x01, 0x70, 0xbf, 0xec, 0x03, 0x12, 0xf0, 0xd0, 0x99, 0x1d, 0x10, 0x4e, 0x98, 0x45, 0x55, 0x20, 0x91, 0x1c, - 0xe9, 0x67, 0x8f, 0x99, 0x48, 0xfe, 0x60, 0xd6, 0x73, 0x4e, 0x89, 0x1e, 0xeb, 0xa9, 0x23, 0xa4, 0xc7, 0x7a, 0xd6, - 0x11, 0xd1, 0x63, 0x3d, 0xeb, 0xf8, 0xe8, 0xb1, 0x9e, 0x39, 0x76, 0x7a, 0x10, 0x98, 0x00, 0x91, 0x07, 0xac, 0x47, - 0x93, 0xa9, 0xa7, 0xb8, 0x07, 0x88, 0x06, 0x81, 0xf5, 0xa4, 0x70, 0xde, 0x03, 0xe4, 0x31, 0x12, 0xab, 0x83, 0xde, - 0x7f, 0x8c, 0x9f, 0xf6, 0x8c, 0x8c, 0x3c, 0x6e, 0x1d, 0x56, 0xff, 0xeb, 0x3f, 0x21, 0x00, 0x0e, 0xcf, 0xa6, 0xde, - 0xe5, 0x18, 0xb2, 0xca, 0x32, 0x02, 0xc9, 0x4f, 0x0c, 0xbe, 0x7c, 0x01, 0x50, 0xf5, 0x99, 0xae, 0xd5, 0xe4, 0xa8, - 0x3d, 0xe6, 0xd0, 0x15, 0x03, 0xc0, 0x36, 0x2c, 0x51, 0x55, 0x0b, 0x9b, 0xb0, 0xb8, 0xfd, 0x0c, 0xa3, 0xb9, 0x6c, - 0x7a, 0x41, 0x03, 0xf5, 0x08, 0xc1, 0x2f, 0xad, 0x87, 0xd6, 0x5a, 0xa6, 0x1c, 0xba, 0x36, 0x8a, 0x2a, 0x1b, 0xea, - 0x12, 0x56, 0x6b, 0x11, 0xd5, 0x44, 0x91, 0x72, 0xc9, 0x28, 0x8a, 0xa5, 0x0a, 0xf6, 0x99, 0xb8, 0x83, 0xa8, 0x79, - 0xda, 0x6a, 0xab, 0x60, 0x7f, 0x07, 0x08, 0x6b, 0x61, 0x2d, 0xa4, 0x33, 0xa8, 0xbd, 0xd3, 0x8f, 0x94, 0xbf, 0xbc, - 0x90, 0xdb, 0xb9, 0x85, 0x22, 0xdc, 0x9e, 0x83, 0xf2, 0xa6, 0xae, 0x4a, 0x45, 0x34, 0x5a, 0x02, 0xa5, 0xcc, 0x09, - 0x22, 0x0b, 0x10, 0xc0, 0x71, 0x03, 0x81, 0xcf, 0x6b, 0x7c, 0x02, 0x8d, 0x42, 0x20, 0x3f, 0xb0, 0x0a, 0xd7, 0x1e, - 0xd2, 0x52, 0x6b, 0x44, 0x94, 0x88, 0x1f, 0x5d, 0x3d, 0xc7, 0xf6, 0xd5, 0xd3, 0x58, 0x5b, 0x4a, 0x13, 0xc4, 0x4f, - 0x2c, 0xb6, 0x10, 0x13, 0x44, 0x75, 0x88, 0x8e, 0x60, 0x39, 0x21, 0x44, 0xe1, 0x0f, 0xa1, 0x9f, 0x1a, 0xf8, 0x4b, - 0xb6, 0x2c, 0xf2, 0x9a, 0x60, 0x31, 0x2b, 0x06, 0x68, 0x55, 0x04, 0x9e, 0xe9, 0x6c, 0xa9, 0xcc, 0x69, 0x1e, 0x1d, - 0xd9, 0xc1, 0x79, 0xd7, 0xc1, 0x5e, 0xfa, 0x32, 0x76, 0xb2, 0x6c, 0x1a, 0xb5, 0xb1, 0x21, 0x12, 0x5e, 0x91, 0xbf, - 0xca, 0x52, 0xe3, 0x1c, 0x99, 0xcb, 0xf5, 0x5d, 0x17, 0x77, 0x77, 0xb4, 0x4d, 0x58, 0x85, 0x08, 0x75, 0xdb, 0x50, - 0xb9, 0x14, 0x66, 0x63, 0xd3, 0x34, 0xc0, 0x17, 0x8a, 0x4a, 0xa5, 0x2a, 0xb5, 0x95, 0x4a, 0x4e, 0x78, 0xd7, 0x37, - 0xb5, 0x48, 0x5d, 0x11, 0x6c, 0x63, 0x86, 0x7a, 0x28, 0x37, 0x6a, 0xec, 0xdb, 0x8e, 0x55, 0x7a, 0x87, 0x09, 0x72, - 0x46, 0x5e, 0xe4, 0xe0, 0xa2, 0xa4, 0x20, 0x73, 0x35, 0x84, 0xf9, 0xa3, 0x86, 0x4f, 0x0b, 0xcb, 0x3d, 0x94, 0x80, - 0xd9, 0x51, 0xc3, 0xcb, 0x08, 0x81, 0x88, 0x4b, 0x65, 0x5f, 0x31, 0xf1, 0x7b, 0x0a, 0x66, 0xc9, 0x84, 0xee, 0x45, - 0x2c, 0x8c, 0xd0, 0xc6, 0x27, 0x49, 0x32, 0xf5, 0x34, 0x05, 0x37, 0x72, 0x19, 0xe6, 0x68, 0x84, 0x96, 0x7c, 0xe4, - 0x40, 0xfa, 0x5a, 0x4e, 0x25, 0xf8, 0x88, 0x3a, 0x05, 0x1c, 0xcf, 0xcf, 0x0b, 0xeb, 0x27, 0xcb, 0x25, 0xe6, 0xb2, - 0x36, 0xff, 0x65, 0x47, 0xc7, 0x60, 0x97, 0xa7, 0x89, 0xe3, 0xea, 0x3f, 0xaa, 0x92, 0xe2, 0xe1, 0xe7, 0x34, 0x07, - 0x14, 0xc1, 0xcc, 0x9e, 0x62, 0x7c, 0xec, 0xb3, 0x4c, 0x01, 0x7f, 0xbb, 0xde, 0x5a, 0x32, 0xb1, 0x4b, 0xda, 0xcd, - 0x95, 0xf1, 0x4b, 0x6d, 0xd8, 0x71, 0x70, 0x6e, 0x00, 0x8a, 0xb3, 0x46, 0x87, 0xe5, 0xb5, 0x6e, 0x5b, 0x15, 0x2a, - 0x50, 0xeb, 0x7f, 0xef, 0x16, 0xa6, 0xbc, 0xcd, 0x4b, 0xe5, 0x6d, 0x1e, 0x9a, 0x00, 0x81, 0xc8, 0x0c, 0x79, 0xd6, - 0x74, 0x4c, 0x12, 0xf7, 0x8e, 0x94, 0xb4, 0xef, 0x48, 0xf1, 0xa3, 0x77, 0x24, 0xe4, 0x5b, 0x42, 0x47, 0xf6, 0x25, - 0x27, 0x27, 0x50, 0x66, 0xb0, 0x97, 0xd7, 0x4c, 0xf6, 0x0f, 0x68, 0x2f, 0x9c, 0xcb, 0xf2, 0x8a, 0xbf, 0x13, 0xde, - 0xda, 0x9f, 0xae, 0x4f, 0xbb, 0xaa, 0xde, 0x7e, 0x63, 0x66, 0x1e, 0x0e, 0xc5, 0xe1, 0x50, 0x99, 0xa0, 0xdd, 0x05, - 0x17, 0x83, 0x9c, 0xdd, 0xbb, 0xf1, 0xf1, 0xef, 0x38, 0x8a, 0xd8, 0x4a, 0x79, 0x24, 0x5d, 0xa8, 0xc4, 0xf0, 0xd2, - 0xc0, 0xc3, 0xec, 0xf8, 0x78, 0xb2, 0xbb, 0xba, 0x9f, 0x0c, 0x06, 0x3b, 0xd5, 0xb7, 0x5b, 0x5e, 0xcf, 0x76, 0x73, - 0xf6, 0xc0, 0x6f, 0xa7, 0xdb, 0x60, 0xdf, 0xc0, 0xb6, 0xbb, 0xbb, 0x12, 0x87, 0xc3, 0xee, 0x9a, 0x2f, 0xfc, 0xfd, - 0x03, 0x02, 0x3a, 0xf3, 0xf3, 0x71, 0x1b, 0xe3, 0xe7, 0xa6, 0xed, 0xaa, 0xb5, 0x03, 0x78, 0xfa, 0x1f, 0xbd, 0x9b, - 0xd9, 0x72, 0xee, 0xb3, 0x27, 0xfc, 0x01, 0xfc, 0xf3, 0x71, 0x93, 0x44, 0xea, 0x13, 0xed, 0x32, 0x79, 0x03, 0x0e, - 0xe4, 0x3b, 0x9f, 0xbd, 0xe5, 0x0f, 0xb3, 0xe5, 0x9c, 0x17, 0x87, 0xc3, 0x87, 0x69, 0x88, 0x64, 0x4d, 0x61, 0x45, - 0x2c, 0x29, 0x9e, 0x1f, 0x84, 0xc7, 0xef, 0x45, 0x64, 0x88, 0xb4, 0xdc, 0xbb, 0x43, 0x76, 0xc3, 0x22, 0x3f, 0x80, - 0x0f, 0xb2, 0x9d, 0x3f, 0x91, 0x35, 0xa5, 0xfb, 0xc5, 0x13, 0xff, 0x70, 0xa0, 0xbf, 0xde, 0xfa, 0x87, 0xc3, 0x07, - 0xf6, 0x80, 0xe0, 0xe8, 0x7c, 0x07, 0xfd, 0xa3, 0x6f, 0x1d, 0x50, 0x95, 0xe1, 0xbb, 0xd9, 0x66, 0xee, 0x5f, 0xaf, - 0xd8, 0x1d, 0x70, 0xa1, 0x28, 0x2f, 0xb4, 0x1b, 0xf6, 0x80, 0x5e, 0x67, 0xe4, 0x44, 0x34, 0xdb, 0xcd, 0x7d, 0x16, - 0xe3, 0x73, 0x75, 0x5f, 0x4c, 0xbe, 0x79, 0x5f, 0xdc, 0xb1, 0x6d, 0xf7, 0x7d, 0x51, 0xbe, 0xe9, 0xae, 0x9f, 0x2d, - 0xdb, 0xb1, 0x07, 0x98, 0x61, 0xef, 0xf8, 0x4d, 0x73, 0xec, 0x18, 0xfb, 0xcd, 0x1b, 0x23, 0x80, 0x32, 0x5b, 0xb0, - 0x58, 0x70, 0x50, 0xaa, 0x55, 0xdb, 0x92, 0xc8, 0x2b, 0x1d, 0xa8, 0x36, 0x23, 0xb8, 0xaf, 0x16, 0x72, 0xe6, 0x99, - 0x81, 0xbe, 0xad, 0x10, 0x2d, 0x1c, 0x36, 0xe0, 0x6f, 0xb4, 0x75, 0x8c, 0x61, 0x9a, 0xd5, 0x4c, 0xdb, 0xa2, 0x2e, - 0xbf, 0xef, 0x3d, 0x93, 0xdf, 0xc8, 0xc0, 0x16, 0x22, 0x29, 0x1c, 0xc7, 0x17, 0xcf, 0x4f, 0xf8, 0xaf, 0x5a, 0x1e, - 0xb5, 0xda, 0x2f, 0x94, 0xfa, 0xf4, 0x15, 0x1d, 0xd1, 0xc4, 0xbd, 0x68, 0xcb, 0xb0, 0x46, 0x59, 0x53, 0x4b, 0x87, - 0x61, 0x5c, 0xc3, 0xbe, 0x3c, 0x70, 0xe8, 0x3b, 0x20, 0xd0, 0x56, 0xa9, 0x14, 0x68, 0xe1, 0x18, 0x46, 0x61, 0x16, - 0x52, 0x1e, 0x17, 0x66, 0x29, 0xef, 0xb1, 0x40, 0x8b, 0x5b, 0x75, 0x8f, 0xa9, 0xed, 0x16, 0x44, 0x58, 0xbd, 0x65, - 0x9c, 0x5f, 0x36, 0xaa, 0x70, 0x5b, 0x80, 0xa2, 0x08, 0xca, 0x60, 0x4f, 0x72, 0xdb, 0x42, 0x49, 0xb3, 0x51, 0x58, - 0x8b, 0xbb, 0xa2, 0xdc, 0xf5, 0x1a, 0xb6, 0xc0, 0x0b, 0xaa, 0x7e, 0x42, 0xd8, 0x96, 0x3d, 0xeb, 0x50, 0x2e, 0xd2, - 0x7f, 0xcb, 0xd2, 0xf3, 0xfd, 0xd6, 0x9c, 0xff, 0xe9, 0x2b, 0xfa, 0xa8, 0xfc, 0xf7, 0x2f, 0xe9, 0x27, 0x83, 0x65, - 0xe4, 0x94, 0xfa, 0x25, 0x1a, 0xdd, 0xa6, 0x39, 0x61, 0x6c, 0xf9, 0xfa, 0xe9, 0x77, 0xc8, 0x14, 0x24, 0x87, 0x52, - 0xaa, 0x72, 0xb2, 0x87, 0xbe, 0xf0, 0xba, 0x0f, 0x33, 0xc1, 0x00, 0x84, 0xd7, 0x68, 0x53, 0x4d, 0x98, 0xc4, 0xa3, - 0x2b, 0xf8, 0xbf, 0x11, 0xc4, 0xa0, 0x7d, 0xa2, 0xa8, 0x63, 0xdb, 0x48, 0xd7, 0x6d, 0xe7, 0x20, 0xb9, 0x53, 0x57, - 0xfe, 0xa8, 0x9c, 0xfc, 0x3b, 0x1a, 0x22, 0xaf, 0xb8, 0x42, 0xac, 0x2c, 0xb8, 0xc4, 0x62, 0xa8, 0x48, 0x01, 0xae, - 0x21, 0x88, 0x94, 0x45, 0x49, 0xe1, 0x96, 0x83, 0xaa, 0x08, 0xc0, 0xb8, 0x5a, 0x1d, 0x75, 0x22, 0x7c, 0xdc, 0x5a, - 0x8b, 0x10, 0xac, 0x68, 0xd4, 0xca, 0x5a, 0x81, 0x2f, 0x48, 0x5f, 0x3a, 0x14, 0xc4, 0xf4, 0x28, 0xa4, 0xaa, 0x74, - 0x28, 0x90, 0xe6, 0x50, 0xf1, 0x8d, 0xc1, 0x46, 0x51, 0x91, 0x9e, 0xbf, 0x34, 0x29, 0xb9, 0x34, 0x66, 0x7c, 0x14, - 0x65, 0x24, 0xf2, 0x3a, 0xbc, 0x13, 0xd3, 0x02, 0xf9, 0x46, 0x8f, 0x1f, 0x04, 0x97, 0xf0, 0x6e, 0xc8, 0xbd, 0x02, - 0x6c, 0x09, 0xd8, 0x01, 0xee, 0x95, 0x19, 0xe5, 0x3a, 0xad, 0xeb, 0xb7, 0xd6, 0x43, 0x31, 0x0c, 0x9f, 0x59, 0x02, - 0xdb, 0xd1, 0x3a, 0x3a, 0xd2, 0xc3, 0x87, 0xff, 0x75, 0x55, 0x73, 0xd4, 0xa9, 0x5c, 0xce, 0x8e, 0x27, 0x2c, 0x45, - 0xcc, 0xa0, 0xfb, 0xeb, 0xf6, 0x95, 0x00, 0xba, 0x5d, 0x16, 0xf3, 0x6c, 0xb4, 0x93, 0x7f, 0x4b, 0x37, 0x56, 0x94, - 0x36, 0xf1, 0x2e, 0xeb, 0x8d, 0xfd, 0xe1, 0xe8, 0x3f, 0x9e, 0xbd, 0x9f, 0x10, 0xaa, 0xce, 0x86, 0xad, 0x75, 0x9c, - 0xcb, 0xff, 0xfa, 0xcf, 0x31, 0x59, 0x41, 0x50, 0x10, 0x96, 0x9d, 0x62, 0xa2, 0x82, 0x51, 0xa4, 0x58, 0xf3, 0xf1, - 0x64, 0x8d, 0x3a, 0xe1, 0xb5, 0xbf, 0xd4, 0x3a, 0x61, 0x62, 0x64, 0xa5, 0xf2, 0xd7, 0xac, 0x62, 0x77, 0x2a, 0xb3, - 0x80, 0xcc, 0x83, 0x7c, 0xb2, 0x36, 0x1a, 0xcc, 0x15, 0xaf, 0x67, 0xeb, 0xb9, 0x54, 0x3e, 0x83, 0x29, 0x67, 0x39, - 0x38, 0x59, 0x0a, 0xbb, 0x27, 0x81, 0xa2, 0x35, 0x43, 0xd7, 0xfe, 0x14, 0x5b, 0xf5, 0x3a, 0xad, 0x6a, 0x80, 0x07, - 0x84, 0x18, 0x18, 0x6a, 0xaf, 0x16, 0x1e, 0x5a, 0x0b, 0x60, 0xed, 0x8f, 0x4a, 0x3f, 0x18, 0x4f, 0x96, 0x7c, 0x81, - 0xfc, 0xcb, 0x91, 0xa3, 0x76, 0xef, 0xf7, 0xbd, 0x7b, 0x90, 0x82, 0x23, 0xd7, 0x42, 0x81, 0x44, 0x40, 0x0b, 0xbe, - 0xf1, 0x95, 0x0f, 0xc6, 0x3b, 0xd4, 0x56, 0x83, 0x82, 0xda, 0xd1, 0x2d, 0x8f, 0x1d, 0xbd, 0xf3, 0xfd, 0x09, 0x7d, - 0xf5, 0x42, 0x0b, 0xc7, 0xdf, 0x38, 0x23, 0xd7, 0x6c, 0xd5, 0x21, 0x47, 0x34, 0x93, 0x0e, 0x21, 0x62, 0xc5, 0xd6, - 0xec, 0x1d, 0xa9, 0x9c, 0x3b, 0x87, 0xec, 0xf4, 0x11, 0xaa, 0xf4, 0x5a, 0x8f, 0x6f, 0x27, 0x4a, 0x77, 0x7b, 0xbc, - 0x9b, 0x7c, 0xcf, 0x26, 0x22, 0x06, 0x03, 0xda, 0x20, 0x9c, 0x91, 0x75, 0x88, 0x54, 0x3a, 0x40, 0x08, 0x1c, 0x13, - 0xd0, 0xf4, 0x5f, 0xdf, 0x92, 0x28, 0xe0, 0x48, 0x1b, 0x21, 0x6b, 0xd9, 0xe1, 0x90, 0x83, 0x46, 0xb9, 0xf9, 0xc3, - 0x2b, 0xd4, 0x69, 0x0e, 0xcc, 0xd3, 0x25, 0xec, 0x39, 0x78, 0xa4, 0x17, 0xc7, 0x47, 0xfa, 0x7f, 0x47, 0x13, 0x35, - 0xfe, 0xf7, 0x35, 0x51, 0x4a, 0x8b, 0xe4, 0xa8, 0x96, 0xbe, 0x4b, 0x1d, 0x05, 0x17, 0x79, 0x47, 0x2d, 0x64, 0xcf, - 0xb2, 0x71, 0xa3, 0x9a, 0xf7, 0xff, 0x6b, 0x65, 0xfe, 0xbf, 0xa6, 0x95, 0x61, 0x4a, 0x76, 0x2c, 0xd5, 0xcc, 0x03, - 0xad, 0x62, 0x98, 0xfd, 0x4c, 0x12, 0x22, 0xc3, 0xa5, 0x01, 0x3f, 0xaa, 0x60, 0x1f, 0xa7, 0xd5, 0x3a, 0x0b, 0x77, - 0xa8, 0x44, 0xbd, 0x15, 0x77, 0x69, 0xfe, 0xa2, 0xfe, 0x97, 0x28, 0x0b, 0x98, 0xda, 0x77, 0x65, 0x1a, 0x07, 0x64, - 0xe1, 0xcf, 0xc2, 0x12, 0x27, 0x37, 0xb6, 0xf1, 0x67, 0x39, 0x9e, 0xf6, 0xab, 0xce, 0xcc, 0x03, 0x09, 0xd4, 0x40, - 0xfc, 0x91, 0x73, 0x59, 0x59, 0x3c, 0x20, 0x74, 0xf3, 0x8f, 0x65, 0x59, 0x94, 0x5e, 0xef, 0x73, 0x92, 0x56, 0x67, - 0x2b, 0x51, 0x27, 0x45, 0xac, 0xa0, 0x6c, 0x52, 0x80, 0xd1, 0x87, 0x95, 0x27, 0xe2, 0xe0, 0x0c, 0x81, 0x1a, 0xce, - 0xea, 0x24, 0x04, 0xa0, 0x61, 0x85, 0xb0, 0x7f, 0x06, 0x2d, 0x3c, 0x0b, 0xe3, 0x70, 0x0d, 0x30, 0x39, 0x69, 0x75, - 0xb6, 0x2e, 0x8b, 0xfb, 0x34, 0x16, 0xf1, 0xa8, 0xa7, 0x28, 0x59, 0xde, 0xe4, 0xae, 0x9c, 0xeb, 0xef, 0xff, 0xa0, - 0x00, 0x76, 0x03, 0x66, 0xdb, 0x02, 0x3b, 0x00, 0x48, 0x50, 0x20, 0x5b, 0xa8, 0xd3, 0xe8, 0x4c, 0x2d, 0x15, 0x78, - 0xcf, 0xf5, 0x00, 0x7f, 0x93, 0x03, 0x96, 0x71, 0x5d, 0xc8, 0x80, 0x11, 0x04, 0x30, 0x02, 0x07, 0x25, 0x60, 0xe8, - 0x0c, 0x71, 0x5b, 0x95, 0xb3, 0x16, 0x9a, 0x2b, 0xdd, 0x96, 0xdc, 0x34, 0xca, 0xd9, 0x4a, 0x04, 0xd0, 0x57, 0x37, - 0x25, 0x4e, 0x97, 0xcb, 0x56, 0x12, 0xf6, 0xed, 0x87, 0x76, 0xaa, 0xc8, 0xe3, 0xa3, 0x34, 0xe4, 0x15, 0x78, 0x92, - 0x71, 0x24, 0x89, 0x12, 0xc1, 0x9b, 0xbc, 0x31, 0xe3, 0xf0, 0xa2, 0x4d, 0x39, 0xb5, 0x37, 0xeb, 0x05, 0xe0, 0x3c, - 0x41, 0x5b, 0x06, 0x18, 0x0b, 0x18, 0x9c, 0x0b, 0xb1, 0xe4, 0x29, 0x82, 0x5f, 0x3a, 0x91, 0xc2, 0xb8, 0xcb, 0x61, - 0x98, 0x07, 0x45, 0xef, 0x92, 0xfa, 0xa3, 0xdf, 0x47, 0x6d, 0x32, 0x18, 0x82, 0x4a, 0x00, 0x95, 0x75, 0x83, 0xc4, - 0xc0, 0xaa, 0xb4, 0x90, 0xb8, 0x84, 0x78, 0x99, 0xaf, 0xa6, 0x75, 0x14, 0x7c, 0xa8, 0x27, 0x84, 0x70, 0x82, 0xf1, - 0x21, 0x6e, 0x80, 0x80, 0xc1, 0x2a, 0x2e, 0x30, 0x48, 0x9e, 0x4b, 0x74, 0x7f, 0x3c, 0xdf, 0x31, 0xc0, 0x95, 0xf3, - 0x9e, 0x6a, 0x57, 0x0f, 0xec, 0xe5, 0x2a, 0x5d, 0x32, 0x42, 0x58, 0xf1, 0x7f, 0x11, 0x79, 0xdf, 0x0e, 0x13, 0x50, - 0xdb, 0xc8, 0x1f, 0x83, 0xc4, 0x5c, 0x26, 0x8a, 0x20, 0x1e, 0x65, 0x05, 0x4b, 0xd2, 0x60, 0x33, 0x4a, 0x52, 0xd0, - 0x68, 0x62, 0x0c, 0x99, 0x0a, 0xed, 0x90, 0x34, 0x9a, 0x8d, 0xc9, 0x3e, 0x86, 0xbc, 0x86, 0x8b, 0xc5, 0x02, 0xef, - 0xfb, 0x59, 0xa8, 0x0e, 0xb6, 0xa5, 0x39, 0x04, 0x9c, 0x24, 0xd8, 0x53, 0x57, 0xa4, 0x24, 0xcc, 0x46, 0x9f, 0x42, - 0xce, 0x0d, 0xe8, 0x38, 0x69, 0x0c, 0xd5, 0x07, 0x26, 0xe1, 0x55, 0x84, 0x4e, 0xca, 0x0a, 0x61, 0x01, 0xf7, 0x8d, - 0x8c, 0x46, 0x2b, 0x69, 0x10, 0x78, 0x9b, 0x61, 0x2b, 0xb0, 0x09, 0x0d, 0x7f, 0x91, 0x79, 0x98, 0x56, 0xb3, 0x12, - 0xcc, 0xf9, 0x06, 0x2a, 0x31, 0x9e, 0x2c, 0xaf, 0xf8, 0xc6, 0xc5, 0x4a, 0x4c, 0x66, 0xcb, 0xf9, 0x64, 0x2d, 0xa9, - 0xe6, 0x72, 0x6f, 0xcd, 0x32, 0xb6, 0x84, 0xfd, 0xc3, 0xc0, 0x50, 0x3a, 0xb0, 0xa3, 0xa9, 0xa6, 0x4d, 0x02, 0x4c, - 0xa6, 0x73, 0xce, 0x87, 0x97, 0x88, 0x26, 0xab, 0x53, 0x77, 0x32, 0x55, 0xed, 0xe0, 0x9a, 0x9c, 0xc9, 0xe9, 0x91, - 0x7a, 0xaa, 0x75, 0x2f, 0xf9, 0x68, 0x3b, 0xac, 0x46, 0x5b, 0x3f, 0x00, 0xb7, 0x4e, 0x61, 0xa7, 0xef, 0x86, 0xd5, - 0x68, 0xe7, 0x6b, 0xd8, 0x5d, 0x52, 0x08, 0x54, 0x7f, 0x96, 0x35, 0x99, 0x8b, 0xd7, 0xc5, 0x83, 0x57, 0xb0, 0xe7, - 0xfe, 0x40, 0xff, 0x2a, 0xd9, 0x73, 0xdf, 0x66, 0x72, 0xfd, 0x33, 0xed, 0x1a, 0x8d, 0x99, 0x8e, 0xd7, 0xae, 0xc0, - 0x0a, 0x0d, 0x90, 0x5f, 0xb0, 0xa3, 0xbd, 0xcd, 0x41, 0x20, 0x40, 0xf7, 0x12, 0x1c, 0x45, 0x01, 0x51, 0xd3, 0xaa, - 0xf2, 0xe8, 0x74, 0xef, 0xef, 0xf1, 0x8d, 0x10, 0xb0, 0xc9, 0x53, 0xeb, 0xde, 0x32, 0xf6, 0x0f, 0x07, 0x08, 0xa1, - 0x97, 0xd3, 0x6f, 0xb4, 0x65, 0xf5, 0x68, 0xc7, 0x72, 0xdf, 0x30, 0xea, 0x29, 0x18, 0xc3, 0xd0, 0x85, 0x55, 0x8c, - 0xe4, 0x19, 0x90, 0x35, 0x7e, 0x83, 0xe8, 0x02, 0x16, 0xbd, 0xde, 0xeb, 0x23, 0x1a, 0x44, 0x40, 0xa5, 0xd7, 0xfc, - 0xa5, 0xc8, 0xe7, 0xaa, 0x10, 0xbd, 0xf7, 0xd6, 0xce, 0x9b, 0x19, 0xc9, 0x32, 0x69, 0xa4, 0xda, 0xad, 0x2c, 0xd6, - 0x95, 0x37, 0x3b, 0x21, 0x5d, 0xcc, 0x31, 0x54, 0x06, 0x8f, 0x03, 0x50, 0x7a, 0xfe, 0x25, 0xf4, 0x4a, 0x86, 0x4c, - 0xb3, 0x44, 0x33, 0xbb, 0x6b, 0xfc, 0xc9, 0x2a, 0xf5, 0x62, 0x44, 0xcc, 0x06, 0xb6, 0x10, 0xb7, 0x45, 0xa5, 0xdb, - 0xa2, 0x50, 0xb6, 0x28, 0xd2, 0x87, 0xda, 0x99, 0xee, 0xcc, 0xc2, 0x67, 0x95, 0x69, 0xdf, 0xdb, 0xcc, 0x8c, 0x0d, - 0xd0, 0x76, 0x11, 0xbe, 0x81, 0x0e, 0x54, 0x08, 0xf9, 0x8f, 0x88, 0x88, 0x44, 0xc0, 0x2e, 0xa7, 0xee, 0xc4, 0xa6, - 0x43, 0x32, 0x0f, 0x31, 0x2b, 0xd4, 0x28, 0x2f, 0x79, 0x72, 0x34, 0x20, 0x15, 0xa1, 0x6e, 0xf7, 0xfb, 0xe7, 0x4b, - 0x17, 0xd4, 0x7e, 0x4d, 0xb1, 0x63, 0x74, 0x53, 0xc0, 0xb9, 0xe0, 0x51, 0xde, 0x73, 0xef, 0x1c, 0xd0, 0x1c, 0xdb, - 0x53, 0x64, 0x0d, 0x38, 0xbd, 0xed, 0x42, 0x80, 0xed, 0xb3, 0x66, 0x6b, 0x7f, 0xb2, 0xba, 0x8a, 0xa6, 0x5e, 0xc9, - 0x67, 0xba, 0x8b, 0x12, 0xb7, 0x8b, 0x62, 0xd9, 0x45, 0x9b, 0x06, 0x82, 0x1d, 0x57, 0x7e, 0x00, 0xbc, 0xa1, 0x51, - 0xbf, 0x5f, 0xb6, 0x7a, 0xf6, 0xe4, 0x6b, 0xc7, 0x3d, 0x9b, 0xf9, 0xac, 0x34, 0x3d, 0xfb, 0x6b, 0xea, 0xf6, 0xac, - 0x9c, 0xec, 0x45, 0xe7, 0x64, 0x9f, 0xce, 0xe6, 0x81, 0xe0, 0x72, 0xe7, 0x3e, 0xcf, 0xa7, 0x7a, 0xda, 0x55, 0x7e, - 0xd0, 0x1a, 0x22, 0xf3, 0x85, 0xcf, 0x55, 0xf7, 0xba, 0x82, 0x05, 0x2c, 0xc1, 0xdd, 0x7a, 0x69, 0xfe, 0x2b, 0x76, - 0x7f, 0x2f, 0xe8, 0xa5, 0xf9, 0x6f, 0xf4, 0x27, 0x05, 0x70, 0x00, 0x1a, 0x53, 0xbb, 0x05, 0x1e, 0x62, 0xa8, 0xa0, - 0x70, 0x37, 0x2b, 0xe7, 0x5e, 0x0d, 0x70, 0x98, 0xa4, 0x6f, 0x68, 0xf5, 0x4a, 0x8b, 0x5d, 0x2f, 0x93, 0xbd, 0x02, - 0x3c, 0x54, 0x21, 0x0f, 0x0f, 0x87, 0xa8, 0x63, 0xd8, 0x41, 0x1d, 0x01, 0xc3, 0x1e, 0x42, 0x63, 0x0b, 0x3c, 0x1f, - 0x3f, 0x67, 0x7c, 0x2f, 0x40, 0x6d, 0x84, 0xf0, 0x78, 0xb5, 0x28, 0x43, 0x6c, 0xd9, 0x5b, 0xa4, 0x92, 0xfa, 0x59, - 0x20, 0xca, 0x68, 0x15, 0xd0, 0x56, 0x7b, 0xcc, 0xd2, 0x78, 0x03, 0xa1, 0x62, 0xa9, 0x8f, 0x21, 0x34, 0x70, 0xf8, - 0x1d, 0x0e, 0x20, 0xc1, 0x97, 0x5c, 0x93, 0xcd, 0xbd, 0xcd, 0xef, 0x69, 0x9f, 0x3f, 0x1c, 0xce, 0x2f, 0x11, 0x94, - 0x2e, 0x85, 0x8f, 0x54, 0x22, 0xaa, 0xa7, 0xb8, 0x29, 0x21, 0x9b, 0x25, 0x2b, 0xfd, 0xe0, 0x57, 0xf5, 0x0b, 0x00, - 0x64, 0x21, 0xd0, 0x26, 0x32, 0xfb, 0xd3, 0x99, 0x8a, 0x2e, 0x00, 0x0e, 0xf1, 0xc7, 0x4f, 0x10, 0x7d, 0x43, 0xcb, - 0xb4, 0x7c, 0x9c, 0xf0, 0x10, 0xb4, 0xb6, 0xa4, 0x93, 0x88, 0x95, 0x02, 0x1b, 0x22, 0xe1, 0xfb, 0xfd, 0xf3, 0x58, - 0xd2, 0x81, 0x46, 0xad, 0xee, 0x8d, 0x5b, 0xdd, 0x2b, 0x5f, 0xd7, 0x9d, 0xdc, 0xf8, 0xa0, 0x68, 0x9f, 0xcd, 0x1b, - 0x95, 0xef, 0xfb, 0x3a, 0x67, 0x77, 0xba, 0x77, 0xe4, 0x9c, 0xf8, 0xfe, 0x1e, 0x42, 0xd1, 0x43, 0x53, 0x64, 0x59, - 0x12, 0x06, 0xb4, 0xd6, 0xae, 0x3d, 0xcb, 0xe8, 0xe0, 0xb5, 0x6f, 0x08, 0x11, 0x79, 0x8a, 0x4f, 0x42, 0x6e, 0x71, - 0x7c, 0x50, 0xa0, 0x7f, 0x66, 0xfc, 0x99, 0x13, 0x3f, 0x6c, 0xf5, 0x0b, 0xe0, 0xdc, 0x74, 0xef, 0xdd, 0x89, 0x59, - 0x8f, 0xa1, 0x94, 0x8d, 0xff, 0xfb, 0x7d, 0x22, 0x0b, 0x74, 0x3a, 0xa2, 0x61, 0x20, 0xb8, 0x8b, 0xea, 0xff, 0x5e, - 0xf1, 0xba, 0x67, 0xad, 0xce, 0x97, 0x9f, 0x3a, 0x3d, 0xe9, 0xd5, 0xcb, 0xb8, 0x07, 0x54, 0xe8, 0x00, 0xe1, 0xbc, - 0xee, 0x37, 0x6c, 0xf7, 0xdd, 0x2f, 0xef, 0x8e, 0x5e, 0x06, 0x36, 0x29, 0x12, 0xdb, 0x4a, 0x3e, 0xeb, 0x81, 0xc2, - 0xaf, 0xc7, 0x7a, 0x75, 0xb1, 0xee, 0xb1, 0x1e, 0x6a, 0x01, 0xd1, 0xc3, 0x02, 0xd4, 0x7f, 0x3d, 0xfb, 0x34, 0x14, - 0x0e, 0xb2, 0x71, 0xaa, 0x40, 0x91, 0x05, 0xbf, 0x16, 0xa3, 0x75, 0x41, 0x80, 0xc8, 0x96, 0x90, 0x56, 0x9d, 0xcc, - 0x1e, 0x97, 0x5a, 0x92, 0xc1, 0x37, 0x01, 0x99, 0x1d, 0x58, 0x39, 0x41, 0xe9, 0xb8, 0x35, 0xe0, 0xca, 0x16, 0x8f, - 0x76, 0xfb, 0xd3, 0x20, 0x3b, 0x6b, 0x4e, 0x1a, 0xed, 0xc3, 0x3e, 0xcd, 0x03, 0x04, 0x22, 0x99, 0x8a, 0x20, 0xd7, - 0xdc, 0x5b, 0xd2, 0x47, 0x87, 0x73, 0x5e, 0xc8, 0x3f, 0xa7, 0x52, 0x87, 0x38, 0x94, 0x58, 0x03, 0x81, 0xca, 0x33, - 0x54, 0x39, 0x6c, 0x90, 0xe3, 0x9f, 0x1d, 0xc9, 0x4c, 0x62, 0xb2, 0xc8, 0xdd, 0x9a, 0xa9, 0xf0, 0x03, 0xc1, 0xc7, - 0x2c, 0xe7, 0xc0, 0x05, 0x36, 0x9b, 0xfb, 0x6a, 0x8a, 0x8b, 0x2b, 0xf0, 0xc7, 0x14, 0x7e, 0xc5, 0x53, 0xd8, 0x69, - 0xf7, 0xeb, 0xa2, 0x4a, 0x51, 0xb7, 0x51, 0x58, 0x54, 0xb2, 0x60, 0x5a, 0x43, 0x9a, 0xe8, 0x30, 0xfa, 0x83, 0x9c, - 0x81, 0x82, 0x90, 0x5f, 0x36, 0x0d, 0x30, 0x52, 0xc9, 0xe5, 0x41, 0x95, 0x04, 0x5e, 0x80, 0x6d, 0x50, 0xb1, 0x75, - 0x01, 0x41, 0xb6, 0x49, 0x51, 0xa6, 0x5f, 0x8b, 0xbc, 0x0e, 0xb3, 0xa0, 0x1a, 0xa5, 0xd5, 0x4f, 0xfa, 0x27, 0x30, - 0x6f, 0x53, 0x31, 0xaa, 0x55, 0x4c, 0x7e, 0xa3, 0xdf, 0x2f, 0x06, 0xad, 0x0f, 0x19, 0x7c, 0xf4, 0xda, 0x34, 0xf8, - 0x93, 0xd3, 0x60, 0x87, 0x89, 0x46, 0x00, 0x24, 0x73, 0x6a, 0xc9, 0x43, 0xd1, 0x1f, 0x41, 0x8e, 0x35, 0xaa, 0x9c, - 0x82, 0xc1, 0xfa, 0x8f, 0x47, 0x3b, 0x30, 0xf5, 0xe2, 0x68, 0x4b, 0x76, 0xd0, 0xca, 0x37, 0xc0, 0xfd, 0x1a, 0xd9, - 0x62, 0x96, 0x03, 0x34, 0x7b, 0x8d, 0xc8, 0xf8, 0xe4, 0x05, 0x30, 0x66, 0xeb, 0x2c, 0x8c, 0x44, 0x1c, 0x8c, 0x55, - 0x63, 0xc6, 0x0c, 0x0c, 0x5c, 0xa0, 0x6b, 0x99, 0x94, 0xa4, 0x21, 0x1d, 0x0c, 0x58, 0x29, 0x5b, 0x38, 0xe0, 0x45, - 0x73, 0xdc, 0x8e, 0x37, 0x2d, 0x1a, 0x0f, 0x6c, 0x17, 0xdb, 0xdf, 0xbf, 0x2c, 0xb6, 0xef, 0xc2, 0x2d, 0xe9, 0x15, - 0x72, 0x96, 0xd0, 0xcf, 0x9f, 0x64, 0x9f, 0x35, 0x9c, 0x9c, 0x0a, 0xcd, 0xd0, 0x52, 0x24, 0x94, 0xe2, 0x9d, 0x9e, - 0x14, 0x18, 0xcb, 0x58, 0xf8, 0x7b, 0xe0, 0x9c, 0x2e, 0x14, 0x91, 0x3b, 0x70, 0x1c, 0xdf, 0x40, 0x05, 0xa3, 0x86, - 0x83, 0x97, 0x31, 0x6c, 0x8b, 0x62, 0x16, 0x12, 0x4e, 0x21, 0x5c, 0xac, 0xb2, 0x7e, 0x5f, 0xfe, 0xa2, 0x2e, 0xba, - 0xc8, 0x64, 0xdd, 0x27, 0xe1, 0xc8, 0x8c, 0xe5, 0xd4, 0x0b, 0xc9, 0xf3, 0x9e, 0x27, 0xd3, 0xe4, 0x59, 0x1e, 0x44, - 0x00, 0xf9, 0x1c, 0xde, 0x87, 0x69, 0x06, 0x56, 0x69, 0x52, 0x7e, 0x84, 0xd2, 0x17, 0x9f, 0x57, 0x7e, 0xa0, 0xb3, - 0xe7, 0x26, 0x19, 0xde, 0xac, 0x5a, 0x6f, 0x52, 0xeb, 0xba, 0x78, 0xc0, 0xdf, 0x3b, 0x83, 0x8d, 0x73, 0x9d, 0x09, - 0x0e, 0xbc, 0x48, 0x6a, 0xbd, 0x66, 0xfc, 0x3a, 0xc3, 0x75, 0xa9, 0xda, 0xe8, 0xa3, 0x10, 0x9d, 0x43, 0xa6, 0x02, - 0x14, 0x8a, 0xb4, 0x7f, 0x50, 0x6a, 0x65, 0x52, 0x69, 0x23, 0x01, 0x74, 0x0f, 0x93, 0x06, 0x5b, 0x0c, 0x65, 0x2c, - 0x4d, 0xa2, 0xdc, 0x69, 0x10, 0x57, 0xf6, 0xe7, 0x4a, 0xe2, 0xd0, 0xb2, 0x48, 0xfe, 0xbd, 0xeb, 0xe9, 0x2b, 0xa4, - 0xee, 0x64, 0x81, 0xcc, 0x18, 0x2f, 0xf2, 0xf8, 0x33, 0x10, 0x66, 0x83, 0x36, 0x2a, 0x0a, 0x21, 0x64, 0x83, 0x18, - 0x34, 0x5e, 0xe4, 0xf1, 0x4b, 0x45, 0xe3, 0x21, 0x1f, 0x45, 0xbe, 0xfa, 0xab, 0xd4, 0x7f, 0x85, 0x3e, 0x33, 0xc1, - 0x23, 0x54, 0x13, 0xfd, 0xbb, 0xe7, 0xb3, 0x7b, 0x50, 0x1b, 0x46, 0x61, 0x66, 0xca, 0xaf, 0x7c, 0x53, 0x9c, 0xbd, - 0xfe, 0x8a, 0xae, 0xb2, 0xad, 0xfb, 0xd1, 0xa7, 0x23, 0x02, 0x6b, 0x63, 0x74, 0xc5, 0x8d, 0x01, 0xe4, 0x30, 0x79, - 0xbf, 0xa2, 0xb4, 0x1c, 0xd2, 0x20, 0x74, 0xd0, 0x10, 0xf4, 0x4a, 0xa2, 0x0f, 0x24, 0x16, 0x31, 0x86, 0x17, 0xe2, - 0x19, 0xa9, 0xc9, 0x44, 0x43, 0xbc, 0x22, 0xf6, 0x43, 0xb4, 0xe4, 0xd4, 0x44, 0x37, 0xc2, 0x14, 0x03, 0x89, 0x9d, - 0x41, 0x72, 0x92, 0xd4, 0xca, 0x2f, 0x9e, 0x49, 0xc2, 0x12, 0x3b, 0x0f, 0x31, 0x98, 0xd4, 0xd2, 0x9d, 0xde, 0x54, - 0xe9, 0xeb, 0x91, 0x96, 0x83, 0xf6, 0x01, 0xd8, 0xa5, 0xa4, 0xf7, 0x4f, 0x0a, 0x45, 0x7c, 0x0c, 0xe3, 0x18, 0xc2, - 0xb7, 0x88, 0xea, 0x0a, 0x9c, 0x6b, 0x05, 0x1a, 0xab, 0x81, 0x87, 0x66, 0x56, 0xcd, 0x87, 0x9c, 0x7e, 0x2a, 0x2d, - 0x7f, 0x8c, 0x68, 0x6c, 0xb4, 0x6e, 0x0e, 0x87, 0x3d, 0xad, 0x7a, 0xe9, 0x1c, 0x74, 0xd9, 0x4c, 0x62, 0xe2, 0x06, - 0xd2, 0xf5, 0xa3, 0xdf, 0x4c, 0xd8, 0x8b, 0xa8, 0x90, 0x4b, 0x21, 0x28, 0x68, 0x75, 0x20, 0x70, 0x28, 0xbc, 0x45, - 0x99, 0x2f, 0x62, 0xda, 0x40, 0x18, 0x7c, 0x7e, 0x20, 0x3f, 0xdf, 0x14, 0xa4, 0x62, 0xc7, 0xba, 0xf6, 0xfb, 0x9b, - 0xd2, 0x03, 0x3c, 0x39, 0x93, 0xe4, 0x69, 0x33, 0x84, 0x15, 0x01, 0x34, 0x66, 0x35, 0x59, 0x9c, 0x70, 0x65, 0x0e, - 0x3f, 0x55, 0x5e, 0xc9, 0x52, 0xa6, 0xce, 0x53, 0xbd, 0x00, 0xa2, 0x8e, 0x37, 0x68, 0x45, 0xea, 0x57, 0xe8, 0xec, - 0x35, 0x2b, 0x21, 0xe3, 0xe1, 0x39, 0xe7, 0xe9, 0xe8, 0x81, 0x25, 0x3c, 0xc2, 0xbf, 0x92, 0x89, 0x3e, 0xfc, 0x1e, - 0x38, 0xdc, 0x8c, 0x13, 0x1e, 0xb9, 0xcd, 0xde, 0x57, 0xe1, 0x0a, 0x6e, 0xa6, 0x05, 0x20, 0xb9, 0x05, 0x49, 0x13, - 0x50, 0x42, 0x22, 0x13, 0x32, 0x6b, 0x4a, 0x7e, 0x69, 0x69, 0x1b, 0xac, 0x61, 0xd2, 0x79, 0xc0, 0x8b, 0x56, 0x1f, - 0xad, 0x26, 0xda, 0x65, 0x96, 0xcf, 0x87, 0x38, 0x43, 0x35, 0xc7, 0xdd, 0x19, 0xfc, 0x1c, 0xf0, 0x8a, 0x55, 0x4d, - 0x3a, 0xda, 0x0d, 0xb8, 0xf0, 0xe4, 0x3a, 0x4f, 0x47, 0x5b, 0xfc, 0x25, 0xf7, 0x07, 0x80, 0x0e, 0xa6, 0x2e, 0x81, - 0x3f, 0x55, 0x5b, 0x4d, 0xa5, 0x5e, 0xb6, 0xf6, 0xeb, 0xba, 0xb3, 0x5a, 0xb9, 0x67, 0x5d, 0x86, 0xf6, 0xc8, 0x90, - 0x33, 0x66, 0xc0, 0x9f, 0x33, 0x96, 0xfc, 0x39, 0x63, 0xc5, 0x9f, 0x33, 0x6e, 0x8c, 0x0c, 0xa0, 0x04, 0xf7, 0x92, - 0x5f, 0xef, 0x11, 0x33, 0xc4, 0x6a, 0x50, 0x09, 0xac, 0x2c, 0xe5, 0xdc, 0x47, 0x4e, 0x31, 0xe5, 0x94, 0xe1, 0xa5, - 0xd3, 0x99, 0x3b, 0x90, 0xf3, 0x60, 0xe6, 0x0e, 0x93, 0xb3, 0x3e, 0xc5, 0xb1, 0x34, 0x26, 0x45, 0x05, 0xe9, 0x9c, - 0x0e, 0x37, 0xaf, 0x8e, 0xf3, 0x84, 0x65, 0x7c, 0xdc, 0x3e, 0x53, 0x20, 0xc4, 0x16, 0xcf, 0x90, 0x48, 0xa9, 0x9a, - 0xe5, 0x36, 0x7f, 0x38, 0xd4, 0xa3, 0x07, 0xbd, 0xd3, 0xc3, 0xaf, 0x84, 0xbd, 0xcc, 0x3c, 0xfb, 0x04, 0x01, 0x4c, - 0x12, 0x79, 0x26, 0xe1, 0xe8, 0xc7, 0x72, 0xf4, 0x37, 0x0d, 0xff, 0x9a, 0xa1, 0xba, 0x3b, 0x04, 0x26, 0xb6, 0xec, - 0xc0, 0x21, 0x38, 0x5d, 0x55, 0x22, 0x01, 0x07, 0x9b, 0x0d, 0x8b, 0xf4, 0x1e, 0x0f, 0x71, 0x3e, 0x28, 0x7c, 0x84, - 0x86, 0x19, 0xbd, 0xdf, 0xdf, 0x08, 0xaf, 0x92, 0xad, 0x3c, 0x1c, 0x12, 0xeb, 0x2e, 0xec, 0xe8, 0xe3, 0x68, 0x8f, - 0x12, 0x6a, 0x3f, 0xaa, 0xf5, 0xa6, 0x52, 0x0f, 0x72, 0xb3, 0x0b, 0x89, 0x41, 0xc5, 0x52, 0x7d, 0x7a, 0xa5, 0xfa, - 0x50, 0xb3, 0xce, 0xef, 0xea, 0xb8, 0x4f, 0xc5, 0x68, 0x2d, 0x27, 0x04, 0xb8, 0x0e, 0x12, 0x8d, 0x0e, 0x80, 0x71, - 0xb6, 0xd9, 0xf2, 0x52, 0x5b, 0x27, 0x4a, 0xc7, 0x71, 0xae, 0x8f, 0xe3, 0xc3, 0x41, 0x8a, 0x19, 0x97, 0x47, 0x62, - 0xc6, 0x65, 0x03, 0xf0, 0x66, 0x9d, 0x07, 0xf5, 0xe1, 0x70, 0x49, 0x97, 0x22, 0xd3, 0xd9, 0x46, 0xf9, 0x59, 0x8f, - 0x1e, 0x9e, 0x25, 0x68, 0xee, 0xad, 0xb0, 0xf7, 0x22, 0xd9, 0x9e, 0xc9, 0x3a, 0xf5, 0x32, 0xf2, 0xe9, 0x85, 0x7b, - 0x76, 0xc9, 0xd5, 0x0f, 0xab, 0xaf, 0xa7, 0xbf, 0x0a, 0x2f, 0x62, 0x15, 0xed, 0xd6, 0x25, 0x13, 0xf6, 0x96, 0x52, - 0x49, 0xab, 0xbc, 0x7c, 0xba, 0xf1, 0x03, 0xcc, 0x4c, 0x7b, 0xfa, 0x20, 0x1b, 0x51, 0xfd, 0x59, 0x89, 0x5a, 0x19, - 0x26, 0x0b, 0xe7, 0x25, 0x53, 0x4f, 0x06, 0x3c, 0x66, 0x25, 0x8f, 0x64, 0xa7, 0x37, 0x06, 0x41, 0x00, 0xeb, 0x9c, - 0xb4, 0xea, 0x8c, 0xa3, 0xd1, 0xaa, 0x72, 0x71, 0xba, 0xca, 0x05, 0x86, 0xdb, 0xad, 0xd9, 0x46, 0xd5, 0x59, 0x6e, - 0x6a, 0x95, 0xf2, 0x1d, 0xc0, 0xc7, 0xb2, 0xca, 0x05, 0x1d, 0x53, 0xa6, 0xce, 0x1b, 0x08, 0xc6, 0x56, 0x35, 0x2e, - 0x9c, 0x1a, 0x17, 0x3c, 0xa2, 0x76, 0x37, 0x4d, 0x3d, 0xda, 0x02, 0x4b, 0xe9, 0x68, 0xc7, 0x4b, 0x54, 0x29, 0xfc, - 0x4d, 0xf0, 0x7d, 0x18, 0xc7, 0x2f, 0x8b, 0xad, 0x3a, 0x10, 0x6f, 0x8b, 0x2d, 0xd2, 0xbe, 0xc8, 0xbf, 0x10, 0x07, - 0xbc, 0xd6, 0x35, 0xe5, 0xb5, 0x35, 0xa7, 0x81, 0xad, 0x61, 0xa4, 0xa4, 0x70, 0x6e, 0xfe, 0x3c, 0x1c, 0x68, 0x65, - 0xd7, 0xea, 0xae, 0x50, 0xeb, 0x31, 0x87, 0x0d, 0x7b, 0x91, 0x85, 0x3b, 0x51, 0x82, 0x23, 0x97, 0xfc, 0xeb, 0x70, - 0xd0, 0x2a, 0x4b, 0x75, 0xa4, 0xcf, 0xf6, 0x5f, 0x83, 0x31, 0x43, 0x97, 0x26, 0x60, 0xd9, 0x18, 0xc9, 0xbf, 0x9a, - 0x66, 0xde, 0x30, 0x59, 0x33, 0x85, 0xe3, 0xd0, 0x30, 0x42, 0x1a, 0xd0, 0x6d, 0x50, 0x1b, 0x9e, 0xcc, 0x37, 0x55, - 0xf9, 0xd5, 0x1d, 0xa9, 0xf6, 0x83, 0xe1, 0xe5, 0x44, 0x9c, 0xd3, 0x25, 0x49, 0x3d, 0x95, 0x50, 0x12, 0x82, 0x5d, - 0xfa, 0x40, 0x4e, 0xac, 0x80, 0xac, 0x65, 0x2c, 0xbf, 0xd5, 0x03, 0x42, 0xff, 0x69, 0xb7, 0x5e, 0xe8, 0x3f, 0x4d, - 0xb3, 0x85, 0xba, 0xfe, 0x30, 0xb9, 0xef, 0xe8, 0xf5, 0x07, 0x87, 0x77, 0xea, 0xaa, 0xe2, 0x2a, 0x1e, 0xd5, 0x86, - 0x49, 0x6e, 0x94, 0x85, 0xbb, 0x62, 0x53, 0xab, 0xe5, 0xe9, 0x38, 0x8c, 0xc0, 0x8c, 0xa0, 0x00, 0x59, 0xd7, 0x6d, - 0x44, 0x0c, 0x2b, 0xb9, 0x4c, 0xc8, 0x27, 0x04, 0x64, 0x51, 0x6a, 0x9c, 0x8f, 0x5b, 0xa0, 0x12, 0xc1, 0xe0, 0x34, - 0xb4, 0x56, 0xdd, 0xe4, 0x27, 0x95, 0x8d, 0xdd, 0x01, 0x39, 0x24, 0x99, 0x2c, 0xee, 0x46, 0xb7, 0x62, 0x59, 0x94, - 0xe2, 0x67, 0xac, 0x87, 0x6b, 0xb6, 0x70, 0x9f, 0x01, 0xa1, 0xfd, 0x44, 0x69, 0x6f, 0x22, 0x4d, 0xd0, 0x7d, 0xc7, - 0x56, 0x00, 0x32, 0x80, 0xa2, 0xae, 0x76, 0xeb, 0x73, 0x7e, 0x8e, 0xa4, 0x19, 0x0e, 0xa3, 0xdb, 0xa7, 0x77, 0xc1, - 0xdd, 0xe0, 0x12, 0xb5, 0xd2, 0x97, 0x2c, 0x6e, 0x61, 0x50, 0xed, 0xcd, 0x12, 0x0e, 0x6a, 0x66, 0xad, 0x8d, 0x40, - 0x30, 0xd9, 0x43, 0x41, 0xc5, 0x5c, 0xc1, 0x3e, 0x28, 0x58, 0x4b, 0x5e, 0x07, 0x87, 0x5b, 0xfb, 0xb2, 0x52, 0x5c, - 0x3c, 0xbf, 0x48, 0x5a, 0x17, 0x96, 0xf2, 0xe2, 0x79, 0x03, 0x06, 0x97, 0x23, 0x6c, 0xaa, 0xca, 0x9f, 0x6c, 0x00, - 0x74, 0x2b, 0xa2, 0x88, 0x17, 0xa5, 0xb0, 0x6d, 0xe5, 0x33, 0x27, 0x6c, 0xb0, 0x61, 0x0f, 0x70, 0xaf, 0x0c, 0x4a, - 0x06, 0x17, 0x62, 0xdc, 0x6e, 0x76, 0x01, 0xae, 0x60, 0x28, 0x8c, 0xad, 0xf9, 0x9b, 0xcc, 0x8b, 0x94, 0x80, 0x9b, - 0x21, 0xca, 0xd7, 0x06, 0x4e, 0x26, 0x3d, 0xb9, 0x96, 0x2c, 0x06, 0x2c, 0x68, 0xf0, 0x1d, 0xb5, 0xfe, 0xce, 0xe4, - 0xdf, 0x78, 0x7a, 0xe8, 0x07, 0x5f, 0x32, 0x6f, 0xe9, 0xb3, 0x37, 0x95, 0x8c, 0xd6, 0x24, 0x51, 0x5e, 0x3d, 0x5c, - 0x82, 0xdc, 0xb0, 0x1c, 0x3d, 0xb0, 0x25, 0x88, 0x13, 0xcb, 0x51, 0x42, 0x19, 0x5d, 0xe1, 0x5e, 0x65, 0xb6, 0x4c, - 0x04, 0x52, 0x1c, 0x58, 0x4a, 0xb9, 0xb7, 0x58, 0x07, 0x4b, 0xdc, 0x9f, 0x48, 0x2e, 0xa0, 0xe4, 0x01, 0x94, 0x2b, - 0x05, 0x04, 0x7c, 0x3a, 0x80, 0xf2, 0xa5, 0xbc, 0x08, 0x7f, 0xe2, 0x44, 0x0d, 0x96, 0xa3, 0x87, 0x86, 0xfd, 0xe4, - 0x85, 0x96, 0xfd, 0xe1, 0x4e, 0x6b, 0x1a, 0x56, 0xfc, 0x0e, 0xa6, 0xc5, 0xc4, 0xed, 0xcb, 0x95, 0x5d, 0x15, 0x9f, - 0xad, 0xd4, 0xd9, 0x4d, 0x0d, 0x49, 0xd8, 0x37, 0x64, 0x15, 0xe0, 0x60, 0x55, 0xc4, 0x3d, 0xcb, 0x72, 0x1f, 0x46, - 0x7f, 0x6e, 0xd2, 0x52, 0x58, 0xa8, 0x92, 0xfe, 0xbe, 0x29, 0x05, 0x52, 0x99, 0xe8, 0x44, 0x0b, 0xc1, 0x15, 0x18, - 0x04, 0xee, 0x45, 0x5e, 0x03, 0x60, 0x0c, 0xb8, 0x14, 0x28, 0xcb, 0xb6, 0x84, 0x90, 0xea, 0x7e, 0x06, 0x6a, 0x3b, - 0x71, 0x9f, 0x46, 0x64, 0x2d, 0x44, 0x5f, 0x05, 0x63, 0xe6, 0xbc, 0x94, 0x6e, 0xb1, 0xe9, 0x6a, 0xb3, 0xba, 0x41, - 0xe7, 0xd2, 0x96, 0x9b, 0x9f, 0xb0, 0xc5, 0x5a, 0x81, 0xb2, 0x09, 0x49, 0xdb, 0x39, 0xcf, 0x51, 0x36, 0xa1, 0xa5, - 0xbd, 0xa7, 0x1e, 0x15, 0xaa, 0x93, 0xad, 0x97, 0xaa, 0xa9, 0x45, 0x58, 0x2d, 0x2e, 0x2a, 0x3f, 0x00, 0xdd, 0x54, - 0x5a, 0xbd, 0xa8, 0x6b, 0x34, 0x85, 0x5a, 0x2d, 0x1c, 0x37, 0xda, 0xd9, 0x74, 0x99, 0xde, 0x21, 0xce, 0xaa, 0xb4, - 0x43, 0xff, 0x92, 0x69, 0xd7, 0xcb, 0x8e, 0x7e, 0x33, 0xae, 0x2e, 0x70, 0x21, 0x36, 0xe0, 0x73, 0xee, 0x2f, 0xaf, - 0xf7, 0x3c, 0xee, 0xf9, 0x87, 0x03, 0xb2, 0x27, 0xb5, 0x3f, 0x54, 0x1f, 0xbb, 0x82, 0x21, 0x0b, 0xa3, 0xd4, 0x5f, - 0xa4, 0xbc, 0xf7, 0x04, 0xc7, 0xfd, 0x4b, 0xd5, 0x63, 0x3f, 0x65, 0x7c, 0x5f, 0x17, 0x9b, 0x28, 0xa1, 0xa8, 0x86, - 0xde, 0xaa, 0xd8, 0x54, 0x22, 0x2e, 0x1e, 0xf2, 0x1e, 0xc3, 0x64, 0x18, 0x0b, 0x99, 0x0a, 0x7f, 0xca, 0x54, 0xf0, - 0x08, 0xa1, 0xc4, 0xcd, 0xba, 0x47, 0xda, 0x4d, 0x88, 0x53, 0xaa, 0x45, 0x29, 0x93, 0xf1, 0x6f, 0xfd, 0x04, 0xca, - 0x73, 0x8a, 0x96, 0xe9, 0x47, 0x85, 0xcb, 0xf4, 0xcd, 0xfa, 0xb8, 0xf4, 0x4c, 0x84, 0x3a, 0x73, 0xb1, 0xa9, 0x75, - 0x3a, 0xc6, 0x4e, 0xe9, 0xd4, 0x86, 0xbd, 0xaf, 0x14, 0x97, 0x15, 0x85, 0x7f, 0x23, 0x91, 0x55, 0xcf, 0x88, 0xe3, - 0xff, 0xcc, 0xda, 0x67, 0x58, 0x05, 0x7e, 0x19, 0xc8, 0xfb, 0x05, 0xc0, 0xc7, 0x75, 0x5d, 0xa6, 0xb7, 0x1b, 0xa0, - 0x0d, 0xa1, 0xe1, 0xef, 0xf9, 0xc8, 0x80, 0xe9, 0x3e, 0xc2, 0x19, 0xd2, 0x43, 0x9d, 0x73, 0x3a, 0x2b, 0xd3, 0x39, - 0x57, 0x61, 0x2d, 0xc1, 0x5e, 0x4e, 0x9a, 0x5c, 0xae, 0x4b, 0x50, 0x33, 0x81, 0xdb, 0x87, 0xf6, 0x88, 0x10, 0x6a, - 0x53, 0x56, 0xd3, 0x4b, 0xa8, 0x79, 0x27, 0xa7, 0x1d, 0x4d, 0x4a, 0x70, 0xd5, 0xd0, 0x59, 0xb9, 0xfe, 0xeb, 0x70, - 0xe8, 0xdd, 0x66, 0x45, 0xf4, 0x47, 0x0f, 0xfd, 0x1d, 0xb7, 0x37, 0xe9, 0x57, 0x88, 0x96, 0xb1, 0xfe, 0x86, 0x0c, - 0xe8, 0x78, 0x32, 0xbc, 0x2d, 0xb6, 0x3d, 0xf6, 0x1e, 0x35, 0x58, 0xfa, 0xfa, 0xf1, 0x07, 0x48, 0xa8, 0xba, 0xf6, - 0x85, 0xc5, 0x13, 0xe6, 0x29, 0xd1, 0xb6, 0xf0, 0x21, 0x2c, 0xf4, 0x3d, 0x44, 0x46, 0x42, 0xb8, 0xa9, 0xec, 0x1e, - 0x25, 0xed, 0x42, 0x5f, 0xfa, 0x5a, 0xf6, 0x95, 0xef, 0x5c, 0x00, 0xac, 0xec, 0x73, 0x1b, 0xee, 0x49, 0x7f, 0x4a, - 0xf5, 0x61, 0xfb, 0x5b, 0xb2, 0x80, 0x42, 0x0b, 0xeb, 0xa9, 0x9c, 0x9d, 0xeb, 0x92, 0xa7, 0xd9, 0x74, 0xbf, 0x86, - 0x3d, 0xea, 0x1e, 0xbd, 0xa6, 0x82, 0xf3, 0x4b, 0x33, 0x7a, 0xff, 0x30, 0x14, 0xaa, 0xa3, 0xce, 0x1d, 0x64, 0x5d, - 0x5a, 0x97, 0x9c, 0xdf, 0xac, 0xdc, 0x51, 0x98, 0xdf, 0x87, 0xe0, 0x19, 0xd6, 0xbd, 0xbb, 0x38, 0xef, 0xfd, 0xd9, - 0x9a, 0x23, 0x3f, 0x65, 0xb3, 0x14, 0xb1, 0x48, 0xe6, 0x60, 0xf5, 0x43, 0x3f, 0x8f, 0xfd, 0x36, 0xc8, 0xe1, 0xb8, - 0x69, 0x40, 0x87, 0x0d, 0x99, 0xb5, 0x2f, 0x11, 0x38, 0xd5, 0x08, 0xd2, 0xd4, 0x04, 0x35, 0xcb, 0x43, 0x24, 0xb6, - 0x4b, 0xd9, 0x36, 0xc8, 0x75, 0x17, 0x4c, 0x73, 0xa4, 0x3d, 0x83, 0xf7, 0x4d, 0x9a, 0xa4, 0x42, 0xb3, 0x48, 0x5b, - 0x25, 0xe3, 0xdf, 0x91, 0x36, 0x53, 0xb2, 0xc7, 0xd6, 0xc0, 0x7b, 0x09, 0xca, 0xc9, 0x30, 0xc5, 0xf0, 0x1d, 0x5f, - 0xef, 0x3c, 0xe6, 0x9e, 0x73, 0xcc, 0x36, 0x29, 0x3b, 0x82, 0x49, 0xb2, 0xf1, 0x0d, 0xc5, 0x1b, 0x7e, 0xb8, 0xad, - 0x44, 0x09, 0xa0, 0x97, 0x05, 0xbf, 0x96, 0x36, 0x57, 0xe8, 0x76, 0xf7, 0x8e, 0x52, 0xf8, 0x25, 0x2f, 0x0f, 0x87, - 0x6d, 0xea, 0x85, 0xd0, 0xf9, 0x22, 0x7e, 0x0f, 0xe6, 0x30, 0x86, 0xd8, 0x8c, 0x00, 0x61, 0x8e, 0x0f, 0xa8, 0x83, - 0xf5, 0x23, 0x00, 0x8d, 0x13, 0x28, 0xc0, 0xe8, 0xab, 0x6d, 0x41, 0xdf, 0xf2, 0xe2, 0x22, 0x42, 0xd4, 0x28, 0xc0, - 0x44, 0x49, 0xb3, 0x18, 0x86, 0x03, 0x9d, 0xdf, 0x37, 0xb7, 0x75, 0x29, 0x70, 0xe8, 0x1d, 0xcb, 0xf0, 0xdf, 0xfe, - 0xc7, 0xda, 0xd2, 0xaa, 0xb2, 0xdd, 0x1a, 0xa7, 0x99, 0xff, 0xed, 0xb6, 0xd0, 0xf7, 0x5f, 0x09, 0xc5, 0xf3, 0x8e, - 0xd7, 0xed, 0xaf, 0x10, 0xbd, 0xaf, 0x5b, 0x79, 0x57, 0x6a, 0x37, 0xcc, 0x94, 0x3f, 0xa4, 0x79, 0x5c, 0x3c, 0x8c, - 0xe2, 0xd6, 0x91, 0x37, 0x49, 0xcf, 0x39, 0xff, 0x5a, 0xf5, 0xfb, 0xde, 0x57, 0x20, 0xe3, 0x7d, 0x25, 0x8c, 0x23, - 0x26, 0x71, 0xf0, 0xed, 0xc5, 0x28, 0xda, 0x94, 0xb0, 0x21, 0xb7, 0x4f, 0x4b, 0xd0, 0xcc, 0xf4, 0xfb, 0x28, 0x51, - 0x5a, 0xf3, 0xfd, 0x2f, 0x72, 0xbe, 0xbf, 0x12, 0xf2, 0x66, 0x25, 0x3f, 0x7c, 0xb4, 0xc2, 0xc0, 0xf7, 0x38, 0xfd, - 0x2a, 0x7a, 0xec, 0xae, 0xf4, 0xe1, 0xbb, 0xd2, 0xd2, 0x67, 0x15, 0xf5, 0x77, 0x54, 0xd4, 0xbc, 0x12, 0x23, 0x22, - 0x1e, 0x04, 0xed, 0x6c, 0xbb, 0xd4, 0xae, 0x25, 0x68, 0x17, 0x6c, 0x0a, 0xfb, 0xd7, 0xa3, 0x43, 0xde, 0xef, 0x7f, - 0xca, 0xbd, 0x16, 0xaf, 0xbb, 0x0e, 0x4d, 0xf9, 0x6b, 0xe1, 0x21, 0x04, 0xb0, 0x96, 0x81, 0x32, 0x8e, 0x30, 0xe9, - 0x22, 0xaf, 0x51, 0x36, 0x9d, 0x08, 0x7c, 0xcc, 0xb2, 0x2b, 0x27, 0x99, 0x06, 0x98, 0x51, 0x4d, 0x61, 0x26, 0xc0, - 0x48, 0x7d, 0xc2, 0xba, 0xe9, 0x69, 0x15, 0x5a, 0xbe, 0x86, 0x60, 0x5d, 0x64, 0x19, 0x47, 0x31, 0x13, 0x00, 0x6c, - 0x3e, 0x81, 0x7c, 0x45, 0x57, 0x87, 0xa4, 0x95, 0x2a, 0xef, 0xd7, 0x19, 0x91, 0xd1, 0x24, 0x44, 0xf3, 0x5b, 0x78, - 0x60, 0xdf, 0x36, 0x33, 0xaa, 0xd4, 0x33, 0xaa, 0xf2, 0x19, 0x0e, 0x4b, 0xe1, 0x18, 0xf1, 0xff, 0x96, 0xaa, 0x1e, - 0x11, 0xe8, 0x55, 0x99, 0x56, 0x51, 0x91, 0xe7, 0x22, 0x42, 0x84, 0x6a, 0xe9, 0x1c, 0x0e, 0xfd, 0xd8, 0xef, 0xe3, - 0x40, 0x98, 0x17, 0xff, 0xfa, 0x58, 0x57, 0xfe, 0xb5, 0xc0, 0xb5, 0x92, 0x02, 0xa7, 0xa2, 0x46, 0x88, 0x10, 0xde, - 0x9f, 0xc0, 0xb3, 0x9a, 0xfa, 0x7e, 0x63, 0x99, 0xe8, 0xfe, 0x91, 0x01, 0xe5, 0x0f, 0xc8, 0xd7, 0x95, 0x14, 0x67, - 0xea, 0xe4, 0x31, 0x71, 0xc6, 0x01, 0x88, 0xf9, 0xb6, 0x44, 0xa3, 0xb1, 0xff, 0x01, 0x09, 0x86, 0xea, 0x07, 0x3b, - 0xdd, 0xd4, 0xfb, 0x67, 0x26, 0x71, 0x14, 0x7d, 0xda, 0x26, 0x8f, 0x25, 0x4b, 0xa3, 0x85, 0xa3, 0xf7, 0x88, 0x61, - 0x1c, 0x4e, 0xe7, 0x63, 0x92, 0x6d, 0x4c, 0x56, 0x01, 0xa4, 0x93, 0x99, 0x3a, 0xa6, 0xd4, 0xd1, 0x38, 0xd7, 0x0b, - 0xaa, 0xd0, 0x63, 0x5d, 0xf2, 0x1c, 0xac, 0x27, 0x3f, 0x7a, 0xa5, 0x3f, 0x15, 0x72, 0x0e, 0x1b, 0x89, 0xa0, 0xf0, - 0x03, 0x5c, 0x0d, 0x56, 0x0a, 0x18, 0x4c, 0x7d, 0x0b, 0x5f, 0x13, 0xcf, 0x51, 0xf0, 0x28, 0xec, 0x62, 0x6c, 0xad, - 0x7c, 0xe7, 0x93, 0x82, 0x72, 0xcf, 0x8a, 0x39, 0xaf, 0x80, 0x73, 0x19, 0x14, 0xc2, 0x74, 0x3c, 0xcb, 0xff, 0x99, - 0xe4, 0xf5, 0xc4, 0x86, 0x00, 0x19, 0xfc, 0x29, 0x71, 0x5a, 0xba, 0x43, 0x77, 0x1e, 0x7a, 0x16, 0x71, 0xd8, 0xe8, - 0xc9, 0xba, 0x2c, 0xb6, 0x29, 0xea, 0x25, 0xcc, 0x0f, 0xe4, 0xe7, 0x2d, 0xf9, 0x3e, 0x44, 0xf1, 0x36, 0xf8, 0x35, - 0x63, 0xb1, 0xc0, 0xbf, 0xfe, 0x96, 0x31, 0x9a, 0x68, 0xc1, 0xbf, 0xb2, 0x06, 0x89, 0x8a, 0xff, 0x9a, 0x4d, 0x00, - 0xd6, 0x91, 0xab, 0x0f, 0x9f, 0x12, 0xe3, 0xad, 0xd9, 0xf0, 0xc8, 0x37, 0x2b, 0xd0, 0xa9, 0xcf, 0xdd, 0x95, 0xed, - 0xa9, 0x6a, 0xfc, 0x2d, 0xd5, 0xd5, 0x48, 0x55, 0x35, 0xfe, 0x96, 0x52, 0x35, 0x7e, 0xcb, 0x28, 0x7e, 0xa7, 0xf2, - 0x19, 0x32, 0x27, 0x9b, 0x98, 0xa4, 0xd3, 0xf7, 0x86, 0x13, 0xbb, 0xec, 0x37, 0x6f, 0x13, 0x99, 0x89, 0x14, 0x72, - 0x6f, 0x00, 0xda, 0x7e, 0x97, 0x1b, 0x4e, 0x89, 0xf3, 0x73, 0x0f, 0x57, 0x6c, 0x5a, 0xbd, 0xa2, 0x05, 0x0b, 0x6c, - 0x5e, 0x66, 0x79, 0x8a, 0x04, 0xb6, 0x4d, 0x99, 0xf5, 0xe7, 0xdc, 0x03, 0x08, 0x66, 0x52, 0x13, 0x00, 0xd2, 0x42, - 0x54, 0x0a, 0x91, 0xbf, 0xc2, 0x59, 0x7d, 0xce, 0x7b, 0x9b, 0x3c, 0x26, 0xd2, 0xea, 0x5e, 0xbf, 0x9f, 0x9e, 0xa5, - 0x39, 0x05, 0x35, 0x1c, 0x67, 0x9d, 0xfe, 0x92, 0x05, 0x75, 0x22, 0x57, 0xe9, 0xdf, 0xdd, 0x20, 0x2f, 0xe3, 0xfb, - 0xba, 0xed, 0xf9, 0x13, 0xf5, 0xf7, 0xce, 0xfa, 0xdb, 0x02, 0xc1, 0x9d, 0x1c, 0xfb, 0xc9, 0xaa, 0x94, 0x27, 0xc6, - 0xa5, 0xbd, 0xe7, 0x37, 0x75, 0x51, 0x64, 0x75, 0xba, 0xfe, 0x28, 0xf5, 0x34, 0xba, 0x2f, 0xf6, 0x60, 0x0c, 0xde, - 0x01, 0xe0, 0x99, 0x0e, 0x0d, 0x90, 0xbe, 0x67, 0xe4, 0xe1, 0x3e, 0xb7, 0xe4, 0x27, 0x95, 0xb5, 0x49, 0xc2, 0x8a, - 0x62, 0x33, 0x8c, 0x11, 0x4a, 0xc6, 0x69, 0x6c, 0xfd, 0x7e, 0x5f, 0xfd, 0xbd, 0xc3, 0x28, 0x2a, 0x2a, 0xee, 0x18, - 0x8d, 0xca, 0xaa, 0x1e, 0x6d, 0x07, 0x87, 0xc3, 0x79, 0x6e, 0xe3, 0x68, 0xeb, 0x15, 0xb0, 0xb7, 0x42, 0xa5, 0xec, - 0x95, 0x08, 0xcb, 0x0f, 0x57, 0x7e, 0xbf, 0x0f, 0xff, 0xca, 0x48, 0x0b, 0xcf, 0x9f, 0xe2, 0xaf, 0x45, 0x5d, 0x60, - 0x78, 0x06, 0xad, 0xd1, 0x0a, 0x82, 0x09, 0xfe, 0xde, 0x81, 0x7a, 0x69, 0xa5, 0x7d, 0x02, 0xdd, 0x0a, 0xf4, 0xa0, - 0x1e, 0xfa, 0x34, 0x69, 0x5f, 0x48, 0xd4, 0xed, 0xad, 0x4e, 0xa3, 0x3f, 0x2a, 0xb8, 0x9c, 0xc2, 0xe4, 0x70, 0x43, - 0x9f, 0x56, 0xe1, 0xf6, 0x33, 0x3c, 0xfd, 0x19, 0x28, 0xb7, 0x0e, 0x87, 0x1c, 0xc4, 0x16, 0x70, 0xf3, 0x58, 0x85, - 0x5f, 0x8a, 0x52, 0x46, 0xd4, 0xc7, 0xd3, 0x02, 0xb4, 0x77, 0x01, 0x3a, 0x60, 0x69, 0x10, 0xaf, 0x90, 0x3c, 0x67, - 0x23, 0x80, 0x65, 0x07, 0x96, 0xb3, 0x8c, 0x53, 0x98, 0x67, 0x79, 0xad, 0x56, 0xda, 0x59, 0x99, 0x78, 0x35, 0xcb, - 0xc0, 0x59, 0xe0, 0xa2, 0xf2, 0x59, 0xa6, 0x55, 0x4f, 0x55, 0x82, 0x3e, 0xaf, 0xe4, 0x04, 0x57, 0x82, 0x93, 0x0d, - 0xc8, 0x2f, 0x40, 0x92, 0xa6, 0x94, 0x35, 0xe5, 0xf5, 0x25, 0xdd, 0x90, 0xd1, 0x73, 0xde, 0xf3, 0xa2, 0x61, 0xe8, - 0x5f, 0x78, 0x25, 0x84, 0x6f, 0xe2, 0xb6, 0x8d, 0x52, 0xd8, 0x5f, 0x04, 0x16, 0x9f, 0xb0, 0x1f, 0xbd, 0xa5, 0x3f, - 0x1d, 0x07, 0xe1, 0x10, 0xb9, 0xa1, 0x62, 0x0e, 0xec, 0x69, 0xc0, 0x62, 0x13, 0x5f, 0x6d, 0x26, 0xf1, 0x60, 0xe0, - 0xeb, 0x8c, 0xc5, 0x2c, 0x06, 0x1a, 0xe4, 0x78, 0x70, 0x39, 0xd7, 0x27, 0x84, 0x7e, 0x18, 0x51, 0x39, 0x2a, 0xd0, - 0x39, 0x88, 0x06, 0x4b, 0xc0, 0x53, 0x6f, 0x65, 0x83, 0x24, 0x63, 0x92, 0x49, 0x5c, 0x6b, 0x92, 0xea, 0x70, 0x42, - 0xeb, 0x40, 0xc7, 0xd5, 0x05, 0x74, 0x3e, 0xae, 0x7b, 0x1f, 0xaf, 0x86, 0x0b, 0x2a, 0xfd, 0x42, 0x0c, 0xbc, 0x7a, - 0x3a, 0x0e, 0x2e, 0xe9, 0x56, 0xb8, 0x58, 0x85, 0xdb, 0x9f, 0xe5, 0x03, 0xc7, 0x1d, 0x95, 0x34, 0x04, 0x06, 0x6f, - 0x0f, 0xdd, 0xcd, 0x0c, 0x0d, 0x75, 0xd2, 0x3e, 0x8c, 0x43, 0x39, 0xc4, 0xaa, 0x15, 0x17, 0xd2, 0x1b, 0xc1, 0xb7, - 0x0b, 0xc5, 0x58, 0x36, 0x76, 0x69, 0x28, 0x0a, 0x7f, 0x05, 0xb0, 0x43, 0xed, 0xaf, 0x54, 0xf2, 0x31, 0x32, 0xaa, - 0x69, 0xa0, 0x63, 0x00, 0x96, 0x2c, 0x4d, 0x24, 0x55, 0xa4, 0x91, 0xf8, 0x23, 0x33, 0xd6, 0x51, 0xd3, 0xf5, 0x05, - 0x53, 0xd5, 0x22, 0xe9, 0x76, 0x26, 0xb1, 0x9c, 0x48, 0x52, 0xdb, 0x7d, 0x44, 0x0c, 0x06, 0x3e, 0xd8, 0x88, 0x69, - 0x26, 0xc2, 0x11, 0x8f, 0x4a, 0x64, 0xd1, 0xe5, 0xb7, 0x51, 0x26, 0x6d, 0x5f, 0x56, 0x64, 0x0b, 0x82, 0xe9, 0x49, - 0xf4, 0x41, 0x92, 0x72, 0x2a, 0x12, 0x69, 0x46, 0x08, 0xf0, 0xe3, 0x49, 0x79, 0xa5, 0x3f, 0x07, 0x4d, 0x2b, 0xc1, - 0x4b, 0x06, 0xc9, 0x23, 0xf1, 0x33, 0x29, 0x98, 0xc5, 0x58, 0x35, 0x18, 0x60, 0x39, 0xd5, 0x33, 0xc7, 0x24, 0xfd, - 0x97, 0x4e, 0x27, 0xec, 0x17, 0x5e, 0x6e, 0x6b, 0x79, 0xd3, 0xdc, 0x7b, 0xe1, 0x55, 0x2c, 0xd5, 0xb0, 0x0c, 0xfa, - 0xaf, 0x89, 0x76, 0xc1, 0xd6, 0x96, 0x31, 0x61, 0xd5, 0x0f, 0x20, 0xed, 0x91, 0x2e, 0xaf, 0x1a, 0xe6, 0x4c, 0xf0, - 0xe8, 0xc2, 0x9a, 0x07, 0xd1, 0x85, 0xf0, 0x91, 0xcb, 0x6e, 0x92, 0x5c, 0x8d, 0x27, 0x7e, 0x38, 0x18, 0x28, 0x00, - 0x5a, 0x5a, 0x27, 0xc5, 0x20, 0x7c, 0x26, 0xe4, 0x40, 0x1a, 0x1d, 0x55, 0x01, 0x16, 0xcb, 0xec, 0xaa, 0x9c, 0x64, - 0x83, 0x81, 0x0f, 0x62, 0x63, 0x62, 0x37, 0x34, 0x9b, 0xfb, 0xec, 0x44, 0x41, 0x56, 0x9b, 0xc3, 0xd6, 0x4c, 0xb7, - 0xc0, 0x00, 0x60, 0x10, 0x11, 0x2c, 0xf7, 0xb9, 0x91, 0x8f, 0xa8, 0xd3, 0x53, 0x18, 0x01, 0xc1, 0x2f, 0x27, 0x02, - 0x91, 0x8b, 0x04, 0xea, 0x01, 0x66, 0x02, 0xcc, 0xa8, 0x62, 0x78, 0x09, 0xec, 0xe2, 0xb9, 0x79, 0xc5, 0xa0, 0x7f, - 0xd1, 0x24, 0x4b, 0x34, 0x95, 0x38, 0x1a, 0x23, 0xa7, 0xd2, 0x18, 0x19, 0x10, 0xbb, 0x38, 0xfe, 0x3d, 0xa5, 0x47, - 0x41, 0xca, 0xbe, 0x54, 0x86, 0x38, 0x1c, 0xc5, 0x57, 0xb0, 0x6a, 0x1c, 0x0e, 0xb5, 0x79, 0x3d, 0x9d, 0xd5, 0xf3, - 0x81, 0x08, 0xe0, 0xbf, 0xa1, 0x60, 0x2f, 0x35, 0x15, 0xb9, 0x41, 0xea, 0x3c, 0x1c, 0x52, 0x90, 0x4f, 0x75, 0x93, - 0x7f, 0xa9, 0xdc, 0xfd, 0x74, 0x36, 0xb7, 0xe6, 0xe8, 0x45, 0x8d, 0xeb, 0xd6, 0xea, 0x86, 0x42, 0xa2, 0x35, 0x4d, - 0x8a, 0xab, 0x6a, 0x52, 0x0c, 0x78, 0xee, 0x0b, 0xd5, 0xc5, 0xd6, 0x08, 0x16, 0xfe, 0xdc, 0x02, 0x61, 0x32, 0xee, - 0xc5, 0x47, 0x0b, 0x39, 0xa5, 0x5d, 0x5b, 0xed, 0xb6, 0x95, 0x0d, 0x29, 0x9a, 0x0f, 0x2f, 0x61, 0x97, 0x4e, 0x11, - 0x6d, 0xbb, 0x24, 0xf8, 0x02, 0xb4, 0xac, 0x2e, 0x44, 0x1e, 0xd3, 0xaf, 0x90, 0x5f, 0x8a, 0xe1, 0x7f, 0x4a, 0xf7, - 0xe6, 0xd4, 0x06, 0x39, 0x80, 0xed, 0xde, 0xc3, 0xed, 0x18, 0x3d, 0x90, 0xc1, 0x1b, 0x21, 0xe7, 0x9c, 0x5f, 0x4e, - 0xad, 0x19, 0x13, 0x0d, 0x0b, 0x56, 0x0e, 0x23, 0x3f, 0x40, 0xc6, 0xcb, 0x29, 0xb0, 0xb2, 0x1f, 0x15, 0x71, 0xe9, - 0x0f, 0x23, 0xff, 0xe2, 0x79, 0x90, 0x71, 0x2f, 0x1a, 0x76, 0x7c, 0x01, 0xf6, 0xea, 0x8b, 0xe7, 0x2c, 0x1a, 0xf0, - 0xea, 0xaa, 0x9e, 0x66, 0xc1, 0x30, 0x63, 0xd1, 0x55, 0x31, 0x04, 0x1f, 0xda, 0xeb, 0x72, 0x10, 0xfa, 0xbe, 0xd9, - 0x39, 0x74, 0x37, 0x24, 0xf2, 0x08, 0xfb, 0x09, 0xdc, 0x76, 0xb5, 0xc4, 0x0c, 0x26, 0x9b, 0xbb, 0x88, 0x19, 0x6c, - 0xf9, 0x8b, 0xe7, 0x86, 0x4b, 0xa8, 0xba, 0x96, 0x9a, 0x8d, 0x02, 0xcd, 0xc9, 0x15, 0x9a, 0x93, 0x95, 0x50, 0x4b, - 0x3e, 0xa9, 0x70, 0xc2, 0xce, 0x27, 0xb9, 0xb2, 0x1b, 0x8d, 0x31, 0x70, 0xd1, 0x9e, 0xdb, 0xc2, 0xc8, 0x4c, 0x67, - 0x29, 0x1a, 0xb0, 0xf0, 0x4c, 0x9c, 0xd2, 0x18, 0xd0, 0xbe, 0x1c, 0x58, 0xda, 0x90, 0x9f, 0xe4, 0xcc, 0x40, 0xdb, - 0x90, 0xd2, 0xa8, 0x19, 0xf8, 0x33, 0x35, 0x61, 0x7e, 0x05, 0x2b, 0x11, 0x44, 0x75, 0x01, 0x26, 0x49, 0x4e, 0x46, - 0x23, 0x65, 0x25, 0x92, 0x73, 0xc0, 0xfb, 0x04, 0x9e, 0x2c, 0x62, 0x5b, 0xfb, 0x53, 0xfa, 0x5f, 0x1d, 0x3e, 0x97, - 0xfe, 0x33, 0x01, 0x2c, 0xe4, 0xd2, 0x20, 0x32, 0x50, 0x38, 0xa4, 0xa6, 0x12, 0x71, 0xe2, 0x78, 0x06, 0xbe, 0x81, - 0x0b, 0x34, 0x05, 0xf4, 0x07, 0x35, 0xa3, 0x88, 0x2c, 0xfc, 0xd5, 0xb3, 0x9b, 0xba, 0xd1, 0xf3, 0xcc, 0x79, 0x0d, - 0x9a, 0x19, 0x08, 0xe9, 0x71, 0xaa, 0xde, 0x86, 0x44, 0xe7, 0xe5, 0xa5, 0x7e, 0x99, 0x10, 0xc9, 0x8a, 0xc8, 0xd3, - 0xf7, 0x39, 0x98, 0x47, 0x14, 0xa1, 0x83, 0x2b, 0xf3, 0x70, 0x38, 0x17, 0x14, 0xbe, 0xa3, 0x3c, 0x1f, 0x70, 0x9a, - 0x45, 0x09, 0x68, 0x03, 0x59, 0x6e, 0xca, 0x5c, 0x27, 0x2d, 0x53, 0xf7, 0x1e, 0xac, 0x04, 0x15, 0xba, 0x39, 0x05, - 0x85, 0x32, 0x12, 0x94, 0xd2, 0x6a, 0x10, 0x4a, 0x75, 0x58, 0x04, 0x91, 0x43, 0x16, 0x02, 0x6e, 0xa6, 0xa2, 0xd1, - 0x92, 0x86, 0x47, 0x38, 0x37, 0x50, 0x08, 0x40, 0x62, 0x4f, 0x15, 0x65, 0x5c, 0x0e, 0x01, 0x1f, 0x25, 0x1c, 0xe2, - 0xac, 0x49, 0x5b, 0x9e, 0x83, 0x38, 0x96, 0x4b, 0xbe, 0xae, 0x10, 0x0c, 0x22, 0xf4, 0x19, 0xf2, 0x27, 0xcb, 0xf9, - 0x77, 0xeb, 0x30, 0xed, 0x08, 0x1f, 0x76, 0xb5, 0x05, 0x17, 0xb3, 0xdb, 0xf9, 0x04, 0xe2, 0x5b, 0x6e, 0xe7, 0xc7, - 0x18, 0x22, 0x0b, 0x7f, 0x70, 0x37, 0x94, 0x5c, 0x51, 0xe8, 0xb2, 0x1e, 0x91, 0x22, 0x7b, 0xba, 0xe6, 0x08, 0x82, - 0x03, 0xad, 0x1a, 0x64, 0x68, 0x24, 0xbe, 0x78, 0x0e, 0x59, 0x83, 0x35, 0xff, 0x52, 0x91, 0xb3, 0xba, 0x3f, 0xd9, - 0x40, 0x35, 0xc9, 0x64, 0xad, 0xa8, 0x9c, 0xbf, 0x5d, 0x95, 0xe5, 0xc9, 0xaa, 0x0c, 0x57, 0x83, 0xae, 0xaa, 0x2c, - 0x39, 0x52, 0x1b, 0xa0, 0x35, 0x5d, 0x21, 0x86, 0x42, 0xd6, 0x60, 0x69, 0x55, 0x65, 0x4d, 0x7d, 0x02, 0x81, 0x3e, - 0xc0, 0x32, 0x6a, 0xf6, 0xd3, 0xe1, 0x3f, 0x83, 0x7f, 0xaa, 0x90, 0xa5, 0x3a, 0xad, 0x33, 0xf1, 0x6b, 0xb0, 0x64, - 0xf8, 0xc7, 0x6f, 0xc1, 0x1a, 0xb0, 0x04, 0xc8, 0x72, 0xb7, 0xb1, 0xd1, 0x7a, 0xe5, 0x15, 0xe2, 0x7d, 0xad, 0x2f, - 0xfa, 0xad, 0xdb, 0x44, 0xad, 0x00, 0x23, 0x14, 0x5a, 0x04, 0xd8, 0xea, 0x81, 0x7b, 0x0a, 0x7e, 0x20, 0x86, 0x73, - 0x4d, 0x5a, 0x53, 0x27, 0xbc, 0xce, 0xc6, 0x91, 0x88, 0xea, 0x2d, 0x5c, 0xdc, 0xeb, 0xad, 0xc5, 0xdf, 0xa8, 0x40, - 0x00, 0x64, 0x31, 0xc5, 0xda, 0x79, 0x43, 0x7a, 0x65, 0xd8, 0x49, 0xe8, 0xbd, 0x61, 0x27, 0x90, 0x17, 0x87, 0x9d, - 0x42, 0x97, 0x68, 0x3b, 0x45, 0x6a, 0xa2, 0xed, 0xa4, 0xc5, 0x2a, 0x2c, 0x21, 0xf8, 0x55, 0x7b, 0xeb, 0x28, 0xdb, - 0x17, 0x59, 0xc2, 0xb4, 0x05, 0x8c, 0x72, 0xab, 0x3e, 0x73, 0x8a, 0x58, 0x29, 0x7b, 0xa7, 0x93, 0x2a, 0x77, 0x91, - 0xcf, 0xad, 0xa6, 0xc8, 0xe4, 0x97, 0xc7, 0x2d, 0x92, 0x4f, 0x7e, 0x6e, 0x37, 0x4c, 0xa6, 0x7f, 0x3a, 0xfa, 0x02, - 0xba, 0x22, 0x3b, 0x7d, 0x02, 0x01, 0x99, 0x0a, 0xaa, 0xd5, 0xad, 0x62, 0x9a, 0xb7, 0xab, 0xec, 0xf6, 0x42, 0x89, - 0xe1, 0x74, 0x76, 0x12, 0x1e, 0x6d, 0x86, 0x0c, 0x1c, 0x82, 0x40, 0x21, 0x54, 0x14, 0xc3, 0x23, 0x50, 0x6b, 0x24, - 0x1f, 0xe0, 0x47, 0xbb, 0x53, 0x41, 0xa4, 0x76, 0x53, 0x71, 0xe3, 0xe4, 0xa6, 0xeb, 0xa5, 0x40, 0xad, 0x53, 0xb2, - 0x02, 0x28, 0x21, 0xea, 0xcf, 0x62, 0x5b, 0xbf, 0x82, 0x2b, 0x36, 0xdf, 0x37, 0x8a, 0x9e, 0x5c, 0x9f, 0xa2, 0x6e, - 0xc5, 0xd5, 0x69, 0xda, 0x6a, 0x8e, 0x1d, 0x67, 0xc8, 0xc1, 0xb3, 0x82, 0x60, 0x3b, 0x2a, 0x51, 0xbe, 0x6b, 0x37, - 0x1d, 0x13, 0x5b, 0xfd, 0xb3, 0xa8, 0x36, 0x77, 0x50, 0x11, 0x11, 0x1f, 0x65, 0x37, 0x4f, 0xda, 0xef, 0x60, 0x8f, - 0xb5, 0x1a, 0x44, 0xf6, 0x19, 0x5c, 0xe5, 0x3a, 0x2d, 0x72, 0x5b, 0x06, 0xe7, 0x1f, 0x5e, 0xed, 0x2a, 0x6c, 0x72, - 0xac, 0xab, 0xab, 0x99, 0xea, 0xa4, 0x62, 0x03, 0x63, 0x4d, 0x6b, 0xa9, 0xe6, 0x31, 0x24, 0xdd, 0x95, 0xc5, 0x59, - 0x95, 0x74, 0xd3, 0x73, 0xe3, 0x4c, 0x21, 0x06, 0xce, 0x56, 0xa3, 0xe5, 0x0c, 0x43, 0x74, 0x7d, 0x98, 0x25, 0x7e, - 0xab, 0xa7, 0xdc, 0xe7, 0xe1, 0xd6, 0xef, 0xea, 0x05, 0x27, 0x93, 0xfd, 0xe4, 0x38, 0x77, 0xbb, 0x48, 0xfb, 0x89, - 0x6f, 0xc3, 0xfc, 0xeb, 0x1b, 0xc4, 0x9d, 0xa8, 0xff, 0x51, 0x01, 0xd0, 0xe0, 0x26, 0x8f, 0x25, 0x4a, 0xfd, 0x5e, - 0x55, 0x3f, 0xa8, 0x99, 0xaa, 0x69, 0x20, 0x98, 0x53, 0x29, 0xe0, 0x0f, 0xb7, 0x0b, 0x57, 0x3c, 0xe2, 0x86, 0x85, - 0xf1, 0x4f, 0xaf, 0x66, 0xa7, 0x82, 0xca, 0xc0, 0xcd, 0xf8, 0x4f, 0x4f, 0xb0, 0x53, 0x58, 0x2b, 0x20, 0x2b, 0xfc, - 0xe9, 0xe5, 0x8f, 0xbc, 0x5f, 0xf1, 0x3f, 0xbd, 0xea, 0x91, 0xf7, 0x11, 0xe7, 0xe5, 0x4f, 0x24, 0x75, 0x42, 0x54, - 0x97, 0x3f, 0x09, 0x53, 0x6c, 0x95, 0xe6, 0xaf, 0x49, 0xe1, 0x13, 0x7c, 0x01, 0xbe, 0xc3, 0x55, 0xb8, 0x35, 0xbf, - 0xc1, 0x63, 0xc7, 0x62, 0xdb, 0xa5, 0xbe, 0x80, 0x72, 0x04, 0x16, 0x91, 0xdb, 0x6f, 0x57, 0xf6, 0xab, 0x85, 0x51, - 0xc6, 0xd8, 0x7d, 0xc9, 0x4a, 0x94, 0xce, 0xfa, 0xfd, 0x42, 0x0a, 0x46, 0x76, 0x61, 0x8d, 0xf6, 0x28, 0x55, 0xaf, - 0xbe, 0x0b, 0xeb, 0x28, 0x49, 0xf3, 0x3b, 0x19, 0x7d, 0x24, 0xc3, 0x8e, 0xf4, 0x95, 0x94, 0x68, 0xaf, 0x55, 0x58, - 0x8e, 0x66, 0xbf, 0x2e, 0x39, 0x50, 0x5e, 0xb7, 0x82, 0xf2, 0x55, 0x13, 0x40, 0xaf, 0x54, 0xfb, 0x0c, 0xb4, 0x82, - 0xc2, 0x52, 0x79, 0xb0, 0x12, 0xe7, 0xa2, 0xcf, 0x8a, 0xc3, 0x41, 0x5d, 0x0c, 0x09, 0x05, 0xaa, 0xc4, 0x49, 0x68, - 0xc4, 0x73, 0xb8, 0x10, 0x8a, 0xeb, 0x1c, 0x63, 0x2b, 0x72, 0xe0, 0x40, 0x86, 0x1f, 0x10, 0x78, 0x2f, 0xfb, 0x57, - 0x30, 0x18, 0x26, 0xb8, 0x91, 0x51, 0x27, 0xe7, 0xec, 0x4f, 0x0c, 0xcc, 0xa0, 0x9e, 0xd4, 0xee, 0xb3, 0x7b, 0x15, - 0xd8, 0x0b, 0x67, 0x40, 0x7b, 0x37, 0x46, 0x3f, 0xab, 0x62, 0xed, 0xa4, 0x7f, 0x2e, 0xd6, 0x90, 0x4c, 0x87, 0xc5, - 0xd1, 0x36, 0x0d, 0x8f, 0xe4, 0xc9, 0x71, 0xbc, 0xe9, 0x1f, 0x0e, 0x63, 0xfc, 0x38, 0xca, 0xaf, 0x2d, 0xe0, 0x55, - 0xdc, 0x42, 0x1a, 0x8b, 0x14, 0xbd, 0x03, 0x31, 0x87, 0xa2, 0x97, 0xec, 0xb7, 0x8c, 0x97, 0x13, 0x41, 0x29, 0x49, - 0x6c, 0x78, 0x47, 0x7a, 0x9a, 0xd6, 0xa3, 0xad, 0x0c, 0xd8, 0xaf, 0x47, 0x3b, 0xfa, 0x0b, 0x14, 0x8f, 0x16, 0xfe, - 0x92, 0xfe, 0x2e, 0xee, 0xe6, 0x9e, 0xf3, 0x4d, 0xe3, 0x3b, 0xe2, 0x02, 0xc5, 0x9a, 0xdd, 0x5f, 0xd3, 0xd2, 0x59, - 0x07, 0x82, 0x03, 0xde, 0x62, 0x17, 0xed, 0xfb, 0x8d, 0xeb, 0xf4, 0xb4, 0xff, 0xde, 0xad, 0x51, 0xbe, 0xf7, 0x0f, - 0x89, 0x72, 0xb0, 0x7f, 0xed, 0xa2, 0xf9, 0xdb, 0x4f, 0x19, 0x92, 0x0a, 0xcd, 0x0d, 0xb6, 0x93, 0x2d, 0xc2, 0xda, - 0x18, 0x07, 0x15, 0xbb, 0x2b, 0xc3, 0x08, 0x18, 0xd4, 0xb1, 0xff, 0xd1, 0x67, 0xd3, 0x86, 0xec, 0x03, 0x40, 0xe5, - 0x2a, 0x04, 0xec, 0x01, 0x38, 0xd1, 0x08, 0x37, 0xc0, 0xad, 0x46, 0x4b, 0x3a, 0xa8, 0xdb, 0x82, 0x81, 0x68, 0x09, - 0x1b, 0x79, 0xdb, 0xd5, 0xe9, 0x1b, 0xc2, 0x87, 0xda, 0x49, 0xe9, 0x50, 0xfe, 0xe6, 0x39, 0xfb, 0xef, 0x1d, 0xd6, - 0xd4, 0x94, 0x1b, 0xc0, 0xcc, 0x59, 0x89, 0xbc, 0x42, 0xe8, 0x14, 0xf9, 0xbd, 0xaa, 0x2b, 0x31, 0x5c, 0xd6, 0xa2, - 0xec, 0xcc, 0x6e, 0x9d, 0xe8, 0x9d, 0x53, 0x50, 0x4b, 0x65, 0x83, 0x9c, 0xa4, 0xda, 0x7c, 0x64, 0xad, 0xa0, 0x44, - 0x5d, 0xa3, 0xc0, 0xf1, 0x29, 0xd7, 0xee, 0xff, 0x9d, 0x33, 0x41, 0xcd, 0x36, 0xaa, 0xfb, 0x6b, 0xfd, 0x54, 0xd5, - 0x24, 0x16, 0xe0, 0x72, 0x92, 0xe6, 0x1d, 0x8f, 0xb0, 0xfa, 0xc7, 0xc9, 0x52, 0x04, 0x7a, 0x1d, 0xd1, 0xae, 0x04, - 0x24, 0x68, 0x27, 0x67, 0xa1, 0x22, 0x50, 0xa0, 0xaf, 0xbf, 0xdc, 0xa4, 0x59, 0x2c, 0x57, 0xb3, 0x3d, 0x4c, 0x94, - 0xc5, 0x7a, 0x88, 0x20, 0x67, 0xa6, 0x0e, 0xf6, 0x7b, 0x9a, 0xd1, 0x2c, 0xbc, 0x32, 0x25, 0xb8, 0x14, 0x57, 0x51, - 0x91, 0x83, 0xcf, 0x21, 0xbe, 0xf0, 0xb9, 0x90, 0x1b, 0x44, 0x34, 0xfd, 0x45, 0xa2, 0xda, 0x91, 0x02, 0x39, 0x94, - 0xfc, 0x84, 0xf8, 0x4b, 0xd6, 0xc6, 0xb8, 0x5f, 0x3a, 0xd5, 0x7e, 0xa5, 0x10, 0xdc, 0x7f, 0xb6, 0xc5, 0x46, 0x95, - 0x27, 0x7a, 0xf4, 0x29, 0xd6, 0xff, 0x64, 0x01, 0xa5, 0xba, 0x6f, 0x83, 0x53, 0xf1, 0x28, 0xdc, 0xd4, 0xc5, 0x0d, - 0x42, 0x0b, 0x94, 0xa3, 0xaa, 0xd8, 0x94, 0x11, 0x71, 0xc2, 0x6e, 0xea, 0xa2, 0xa7, 0x39, 0xd0, 0xa9, 0xc3, 0xd2, - 0x44, 0x9e, 0x08, 0xed, 0x16, 0x74, 0x4f, 0x73, 0xac, 0xc4, 0x0b, 0x59, 0x3a, 0xc8, 0x3a, 0x91, 0x26, 0x54, 0xee, - 0xea, 0xaa, 0xa3, 0x52, 0xa9, 0x1b, 0xde, 0xa4, 0x9a, 0xf1, 0x77, 0x69, 0xfe, 0xc4, 0xb2, 0xdf, 0xb4, 0x7e, 0xab, - 0xd5, 0xde, 0x58, 0x3d, 0x2a, 0x59, 0x73, 0x9c, 0x4d, 0x48, 0x4a, 0x9f, 0xb0, 0xdd, 0x4c, 0xba, 0xd6, 0x81, 0x27, - 0xc1, 0xe5, 0xd0, 0x13, 0x50, 0x31, 0x68, 0xe2, 0xed, 0x2e, 0x50, 0x8f, 0xc0, 0x33, 0x50, 0x3e, 0x51, 0xeb, 0x80, - 0x9f, 0xd7, 0x5a, 0x9e, 0x32, 0xc2, 0xb0, 0xda, 0x59, 0xb4, 0x1c, 0x9c, 0x77, 0x8a, 0xc0, 0xb5, 0x2b, 0x81, 0xe7, - 0x43, 0xf5, 0x5e, 0x08, 0x18, 0xee, 0x9f, 0x0b, 0x95, 0xcd, 0x6e, 0x86, 0xf3, 0xa8, 0x71, 0x7a, 0xa0, 0xbd, 0xed, - 0x5a, 0x0f, 0xf5, 0xae, 0xdb, 0xb9, 0xad, 0x74, 0xef, 0xd7, 0x4e, 0x26, 0x5d, 0x40, 0x6b, 0xf3, 0xd9, 0x77, 0x76, - 0xa5, 0x75, 0xd3, 0x73, 0xf6, 0x60, 0xeb, 0x96, 0xe8, 0x5c, 0x10, 0x4d, 0x7e, 0x3f, 0xf0, 0xac, 0x6d, 0x47, 0xbf, - 0x4d, 0x3b, 0xb6, 0xb9, 0x87, 0xba, 0x57, 0x50, 0xeb, 0x0d, 0xcd, 0xfb, 0x67, 0xae, 0x6d, 0xc7, 0x57, 0xbf, 0xae, - 0x3b, 0x5c, 0xe7, 0x4d, 0x70, 0xdc, 0x74, 0x6d, 0xab, 0x9d, 0xfd, 0xdc, 0xdd, 0x5b, 0x8b, 0x28, 0xcc, 0xb2, 0x9f, - 0x8a, 0xe2, 0x8f, 0x4a, 0xdf, 0x11, 0xe8, 0xe8, 0xce, 0x8b, 0x3a, 0x5d, 0xee, 0x3e, 0x12, 0xc6, 0x93, 0x57, 0x1f, - 0x11, 0xdd, 0xfa, 0x3e, 0x73, 0xbf, 0x02, 0xdc, 0x08, 0xee, 0x20, 0xda, 0xbb, 0xa5, 0x3e, 0xa9, 0xd5, 0xd7, 0x7a, - 0xed, 0x3c, 0x3d, 0xbf, 0xe9, 0xdc, 0x7e, 0xf7, 0xcd, 0xd1, 0xd6, 0x7b, 0x5c, 0x58, 0x2b, 0x4b, 0x4f, 0x55, 0xc1, - 0xde, 0x2c, 0x4f, 0x55, 0xc1, 0xe4, 0x81, 0xd7, 0xec, 0x17, 0x34, 0xb8, 0xd2, 0xd1, 0xc6, 0x7b, 0xa2, 0x06, 0x6e, - 0x51, 0x58, 0x3a, 0xfc, 0x92, 0x9b, 0xc9, 0x2b, 0xdc, 0x5f, 0x2a, 0x72, 0xb1, 0xef, 0x9c, 0xd1, 0x9d, 0x99, 0x75, - 0xaf, 0x2a, 0x5c, 0x2d, 0xc8, 0xd5, 0x81, 0xad, 0x65, 0x17, 0x87, 0x1b, 0x16, 0x51, 0x80, 0x40, 0x4c, 0xaf, 0xd4, - 0xda, 0x1f, 0xd1, 0x20, 0xe4, 0x83, 0x81, 0x5f, 0x60, 0xb0, 0x2a, 0x50, 0xf8, 0x40, 0x91, 0xfc, 0xb5, 0x27, 0x60, - 0x17, 0xcf, 0x00, 0xdd, 0x8a, 0xcd, 0x8a, 0x11, 0x22, 0x64, 0xb2, 0x9c, 0xd5, 0x74, 0x06, 0xf9, 0xd4, 0x17, 0xdf, - 0xd9, 0xaa, 0xd3, 0x79, 0x5b, 0x53, 0xe5, 0xd4, 0xa1, 0xd0, 0xdd, 0x4d, 0xdd, 0xb9, 0x75, 0x91, 0xa7, 0x0e, 0x21, - 0x57, 0x2a, 0x56, 0x62, 0x1a, 0x6a, 0x9e, 0xa4, 0x19, 0xf5, 0xa5, 0xbd, 0xdf, 0x6b, 0x14, 0x4e, 0xf9, 0xd3, 0x31, - 0xa8, 0xc2, 0x55, 0x0d, 0x71, 0x2c, 0x55, 0xf1, 0xc8, 0x06, 0x81, 0xe6, 0xd5, 0xad, 0x4a, 0x9a, 0x90, 0xc9, 0x8d, - 0xf0, 0xa9, 0x49, 0x29, 0x4f, 0xd3, 0x26, 0xad, 0x14, 0xa9, 0x83, 0x0f, 0xea, 0x54, 0xe3, 0xb9, 0x59, 0x5d, 0x03, - 0x98, 0x71, 0x7e, 0xc5, 0x2f, 0x15, 0x97, 0x51, 0x5b, 0x99, 0x49, 0xfb, 0x93, 0xa3, 0xb1, 0x51, 0x97, 0xd3, 0x46, - 0x19, 0x61, 0xa5, 0x34, 0x27, 0xc5, 0x72, 0x3c, 0xff, 0x80, 0xc1, 0x9a, 0x27, 0xb0, 0x83, 0x89, 0x4a, 0x79, 0x1f, - 0x01, 0xf1, 0x75, 0x92, 0xde, 0x25, 0x90, 0x22, 0xfd, 0x4b, 0x97, 0x3c, 0x75, 0x18, 0x1b, 0x88, 0x31, 0x2b, 0x66, - 0x46, 0xff, 0x83, 0xbb, 0xa4, 0x3f, 0x09, 0x01, 0x70, 0x13, 0x4d, 0xa1, 0x53, 0xe7, 0xc9, 0x45, 0x1e, 0x2c, 0x2f, - 0x3c, 0xb4, 0x62, 0xc4, 0x83, 0xff, 0xbc, 0x0e, 0x11, 0xc4, 0x1c, 0x53, 0x3c, 0xfd, 0xc2, 0xe8, 0x3f, 0x82, 0x4b, - 0x8c, 0x20, 0x74, 0xf7, 0xce, 0x61, 0x08, 0x37, 0x7b, 0x90, 0x41, 0xfd, 0xa1, 0x0e, 0x89, 0x1a, 0xfe, 0x54, 0x79, - 0xd0, 0xff, 0x75, 0x26, 0x2c, 0xb5, 0x9f, 0x9e, 0x0e, 0xa0, 0x82, 0xf7, 0x15, 0x6f, 0x23, 0xe2, 0xfb, 0xc4, 0xcf, - 0xe2, 0xc1, 0xe6, 0xd9, 0x06, 0xac, 0x75, 0x4f, 0x72, 0x63, 0x5d, 0x25, 0x6c, 0x20, 0xe0, 0x6b, 0x4c, 0x6b, 0xcf, - 0x6b, 0xb7, 0x7b, 0xf0, 0x9f, 0xfe, 0x45, 0xc8, 0x80, 0x89, 0xd3, 0xf7, 0x99, 0x93, 0x35, 0xba, 0xc8, 0x64, 0xfa, - 0xd0, 0x49, 0xdf, 0xe8, 0x74, 0xdf, 0x09, 0xff, 0xa8, 0x98, 0xc5, 0x87, 0x5b, 0xfa, 0x4a, 0x93, 0xe2, 0x0e, 0x58, - 0xd9, 0x3c, 0x2a, 0x08, 0x75, 0x2e, 0xa2, 0x6f, 0x4c, 0xf9, 0x96, 0x50, 0xb3, 0x6f, 0x2c, 0x29, 0xa5, 0x7b, 0x0d, - 0xbd, 0x49, 0x6b, 0xfd, 0x36, 0x4a, 0x30, 0x26, 0x3a, 0x9e, 0xbc, 0x8c, 0xc7, 0xca, 0xfb, 0x78, 0xdc, 0x48, 0x85, - 0x3c, 0x00, 0x11, 0xa8, 0x18, 0x7f, 0xba, 0xf2, 0xe4, 0xa4, 0x17, 0xc6, 0xab, 0x50, 0x0a, 0x0a, 0x03, 0xba, 0x02, - 0x29, 0xe0, 0x51, 0x7b, 0xa2, 0xb3, 0xb0, 0x4b, 0xb8, 0x47, 0x37, 0x01, 0x63, 0x7d, 0xfe, 0x09, 0xd0, 0xdc, 0x85, - 0x3b, 0xbc, 0x18, 0xa0, 0x36, 0xf5, 0xea, 0xee, 0xe3, 0x5a, 0x9d, 0xc3, 0x21, 0x38, 0x58, 0x0d, 0x22, 0x38, 0x9d, - 0x4f, 0x1d, 0xcd, 0xb2, 0x00, 0x95, 0x93, 0xe5, 0x46, 0xde, 0x3c, 0x5a, 0xf4, 0xea, 0xbe, 0xb7, 0x4c, 0xcb, 0xaa, - 0x0e, 0x32, 0x96, 0x85, 0x15, 0xe0, 0xea, 0xd0, 0xfa, 0x41, 0xb8, 0x2c, 0x9c, 0x3f, 0x10, 0x82, 0xd8, 0xbd, 0xda, - 0x96, 0x3c, 0x57, 0x73, 0xf8, 0xd9, 0x73, 0xb6, 0xe6, 0x12, 0x75, 0xd2, 0x99, 0x08, 0x40, 0xec, 0xa9, 0x59, 0x45, - 0xd7, 0x40, 0x52, 0xa7, 0x59, 0x45, 0xd7, 0xd4, 0x6c, 0x63, 0x1c, 0xc8, 0x47, 0xab, 0x14, 0xb0, 0xef, 0xa6, 0xe3, - 0x60, 0xf5, 0x2c, 0x96, 0xd7, 0xa1, 0xbb, 0x67, 0x1b, 0xe5, 0x33, 0xa8, 0x5b, 0x6d, 0x8c, 0x89, 0xed, 0xe6, 0xcb, - 0xb9, 0x7e, 0x3b, 0x58, 0xfa, 0x76, 0xd0, 0x9c, 0x53, 0xf6, 0x9d, 0x2e, 0x7b, 0x65, 0x97, 0x4d, 0x3d, 0x77, 0x54, - 0xb4, 0x1a, 0x03, 0x7a, 0x03, 0x0b, 0xd6, 0xe7, 0x22, 0xcd, 0x56, 0xa5, 0x2a, 0x01, 0x2f, 0x8c, 0x15, 0xbb, 0xf3, - 0x1b, 0x99, 0x21, 0x09, 0xf3, 0x38, 0x13, 0xef, 0xe8, 0x5e, 0x0b, 0x93, 0xe3, 0x58, 0x24, 0x53, 0x42, 0xa7, 0x74, - 0x67, 0x1b, 0x3a, 0x57, 0x61, 0x14, 0xd1, 0x5a, 0x49, 0xa5, 0x91, 0xc0, 0xd4, 0x0c, 0x50, 0x32, 0x57, 0xe0, 0x94, - 0x2e, 0xf7, 0xbf, 0x23, 0x31, 0xce, 0x7c, 0x51, 0x32, 0x03, 0xba, 0xe5, 0xd7, 0xc5, 0xba, 0x95, 0x22, 0x23, 0xcc, - 0x9b, 0xe3, 0xf6, 0xba, 0x3e, 0x04, 0x72, 0xb5, 0xec, 0x51, 0x34, 0x0e, 0x0a, 0x1d, 0x2e, 0x55, 0x02, 0xec, 0x8b, - 0xc4, 0xcf, 0x08, 0x5b, 0xda, 0x03, 0xb9, 0x3d, 0x3a, 0x13, 0xe6, 0x9c, 0x93, 0xb2, 0xec, 0x5c, 0x9a, 0xc1, 0xe5, - 0xc4, 0x95, 0xe0, 0x22, 0xbd, 0x6d, 0x4f, 0x93, 0x96, 0xb6, 0x8f, 0x0d, 0xe7, 0x68, 0x68, 0x1b, 0x74, 0xc7, 0xfe, - 0xd0, 0x5c, 0x2c, 0x62, 0xeb, 0x62, 0x31, 0xec, 0xcc, 0x7e, 0xb4, 0x58, 0x80, 0x1c, 0x00, 0x8e, 0xba, 0x0d, 0x1f, - 0xb3, 0x25, 0x70, 0x5a, 0x4d, 0xb3, 0xa9, 0xb7, 0xe1, 0xd5, 0x33, 0xd5, 0xd3, 0x4b, 0x9e, 0x3f, 0x13, 0x66, 0x2c, - 0x36, 0x3c, 0x7f, 0x66, 0x1d, 0x39, 0xd5, 0x33, 0xa1, 0x44, 0xeb, 0x02, 0x9a, 0x81, 0xd7, 0x14, 0x30, 0x62, 0xc9, - 0x64, 0x4a, 0x15, 0x79, 0xdc, 0x9b, 0x6e, 0xd4, 0xe0, 0x05, 0x85, 0x43, 0x20, 0xa5, 0xd3, 0x2f, 0x9e, 0x33, 0xfd, - 0xde, 0xc5, 0xf3, 0x0e, 0x59, 0xdb, 0x30, 0x5d, 0x6e, 0x86, 0xc9, 0xa0, 0xf4, 0x9f, 0x99, 0x89, 0x71, 0x61, 0x4d, - 0x12, 0x40, 0xfc, 0x1b, 0xfb, 0x1d, 0x52, 0xb8, 0x79, 0x7f, 0x39, 0x8c, 0x1f, 0x79, 0x3f, 0x46, 0xf6, 0x24, 0xcd, - 0x10, 0x6b, 0x26, 0x15, 0x72, 0xf7, 0xd5, 0xfa, 0xc7, 0xc4, 0x6e, 0xb2, 0x07, 0x16, 0x80, 0xd8, 0x9a, 0xb6, 0xba, - 0xe5, 0xfd, 0xbe, 0x67, 0x8a, 0x00, 0x3f, 0x28, 0xff, 0xe8, 0xce, 0x90, 0x0c, 0xca, 0xae, 0x1b, 0x42, 0x3c, 0x28, - 0x9b, 0xa6, 0xbd, 0xde, 0xf6, 0xce, 0x3c, 0x56, 0xd7, 0x69, 0x67, 0x71, 0xb5, 0xc8, 0x20, 0xad, 0x3e, 0x64, 0xc7, - 0x99, 0x7d, 0x76, 0xb4, 0x54, 0xba, 0xdf, 0x87, 0x88, 0xb8, 0xa3, 0xac, 0xed, 0xb7, 0x5b, 0x70, 0x0d, 0x47, 0x83, - 0xd0, 0x95, 0xbd, 0x5d, 0x46, 0x1b, 0x17, 0xe2, 0xb8, 0x67, 0x3a, 0x5f, 0xf0, 0xe5, 0x51, 0xda, 0x79, 0x70, 0xaa, - 0x27, 0xfa, 0xdc, 0x74, 0x57, 0x99, 0x5c, 0xeb, 0xb0, 0x1a, 0x83, 0xda, 0x2c, 0x6c, 0xe1, 0x2e, 0x6c, 0xa3, 0x83, - 0xd6, 0xbe, 0x2c, 0xf8, 0xa7, 0x0c, 0xc0, 0x97, 0x9e, 0x2d, 0xdb, 0x5e, 0x93, 0x56, 0x6f, 0x64, 0x14, 0x62, 0x4b, - 0xdb, 0xab, 0x4f, 0x47, 0xf9, 0xb8, 0x39, 0xa1, 0xb8, 0x90, 0xa3, 0xfc, 0xe8, 0x35, 0x44, 0x5d, 0xeb, 0x3a, 0x2e, - 0x16, 0x1d, 0x6e, 0x5c, 0x75, 0xdb, 0x8d, 0xeb, 0x47, 0xc4, 0x5b, 0xa3, 0x4d, 0x0a, 0xb5, 0x32, 0x76, 0x04, 0x2f, - 0xcb, 0x87, 0x43, 0x26, 0x86, 0x43, 0x09, 0x99, 0xfa, 0xd8, 0xbd, 0xa1, 0x69, 0x9f, 0x9f, 0xb6, 0x7e, 0xc4, 0x52, - 0xe3, 0x28, 0x36, 0xbc, 0xd3, 0x77, 0x1e, 0x5b, 0xe3, 0x4a, 0xbe, 0x0c, 0x66, 0xbb, 0x82, 0x6a, 0x6b, 0xbc, 0x61, - 0x2f, 0xe7, 0xbf, 0x54, 0x52, 0xc9, 0xdf, 0xfe, 0x0c, 0xd7, 0xf0, 0xd6, 0x96, 0x0e, 0x9a, 0x6a, 0x96, 0xb3, 0x5c, - 0xdf, 0x0b, 0x8e, 0x3f, 0xee, 0x5e, 0x11, 0x0c, 0x7e, 0x4f, 0x47, 0x41, 0x2e, 0x96, 0x6a, 0x0d, 0x28, 0x48, 0x47, - 0x76, 0x4c, 0x65, 0x81, 0x61, 0x00, 0x6f, 0xc8, 0x00, 0x79, 0x4c, 0xe1, 0x6e, 0xa8, 0xf0, 0xc2, 0x97, 0x15, 0xd9, - 0x25, 0xb0, 0xad, 0x19, 0x1f, 0x33, 0xdc, 0x41, 0xc8, 0x3f, 0x82, 0xdd, 0xb1, 0x15, 0xbb, 0x65, 0x0b, 0x86, 0x64, - 0xe3, 0x38, 0x8c, 0x31, 0x1f, 0x4f, 0xe2, 0x2b, 0x31, 0x89, 0x07, 0x3c, 0x42, 0xc7, 0x88, 0x35, 0xaf, 0x67, 0xb1, - 0x1c, 0x40, 0x76, 0xc7, 0x95, 0x0e, 0x08, 0xa1, 0xb1, 0xa1, 0x25, 0x6f, 0x0a, 0x83, 0x8b, 0x1d, 0xfb, 0x8c, 0x44, - 0x32, 0x0e, 0xc1, 0xa2, 0x55, 0x0d, 0x2c, 0x4c, 0xec, 0x96, 0x17, 0xb3, 0xd5, 0x1c, 0xff, 0x39, 0x1c, 0x10, 0x00, - 0x3b, 0xd8, 0x37, 0xec, 0x2e, 0x42, 0xa4, 0xb7, 0x05, 0xbf, 0xb3, 0x3c, 0x5d, 0xd8, 0x3d, 0x7f, 0xc7, 0xc7, 0xec, - 0xfc, 0x47, 0x0f, 0x22, 0x67, 0xcf, 0x3f, 0x01, 0x1a, 0xe2, 0x3d, 0xbf, 0x4d, 0xbd, 0x8a, 0xdd, 0x12, 0x05, 0xe1, - 0x2d, 0x38, 0x03, 0xdd, 0x43, 0x04, 0xec, 0x3b, 0xbe, 0xc0, 0x58, 0xb1, 0xb3, 0x74, 0xe9, 0x61, 0x46, 0xa8, 0x3d, - 0x9d, 0x2f, 0x6b, 0x35, 0x09, 0x37, 0x57, 0xcb, 0xc9, 0x60, 0xb0, 0xf1, 0x77, 0x7c, 0x0d, 0x7c, 0x30, 0xe7, 0x3f, - 0x7a, 0x3b, 0x2a, 0x17, 0xfe, 0xf3, 0x3a, 0x4b, 0xde, 0xf9, 0xec, 0xdd, 0x80, 0x2f, 0x00, 0x6f, 0x09, 0x1d, 0xb8, - 0xee, 0x7d, 0x26, 0xf1, 0xda, 0xde, 0xe9, 0x6b, 0x04, 0x12, 0xf9, 0x02, 0x30, 0x62, 0x62, 0x7e, 0xbf, 0x83, 0x08, - 0x8c, 0x04, 0x7c, 0x5b, 0xb5, 0x47, 0xfc, 0x96, 0x1b, 0xc0, 0xaf, 0xcc, 0x67, 0x0f, 0x3c, 0xd4, 0x3f, 0x13, 0x9f, - 0xdd, 0xf0, 0x0f, 0xfc, 0xda, 0x93, 0x92, 0x74, 0x39, 0xfb, 0x30, 0x87, 0xeb, 0xa1, 0x94, 0xa7, 0x43, 0xfa, 0xd9, - 0x18, 0x0c, 0x20, 0x14, 0x32, 0x6f, 0x3c, 0x60, 0x4d, 0x0a, 0xf1, 0x2f, 0xe0, 0xdb, 0x51, 0xc2, 0xe6, 0x8d, 0xb7, - 0xf5, 0xb5, 0xbc, 0x79, 0xe3, 0x3d, 0xf8, 0x14, 0x05, 0x58, 0x05, 0xa5, 0x2c, 0xb0, 0x0a, 0xc2, 0x46, 0x1b, 0x61, - 0x0c, 0x5c, 0xbd, 0x6b, 0x0c, 0x75, 0x3d, 0x47, 0x6c, 0x5b, 0xe9, 0xfb, 0xf0, 0x3d, 0x64, 0xc0, 0x07, 0x6f, 0x8a, - 0x92, 0xe8, 0x73, 0x6a, 0x8a, 0xa4, 0x75, 0xcf, 0xfd, 0xd6, 0xba, 0xa3, 0x35, 0xa5, 0x3e, 0x72, 0x35, 0x3e, 0x1c, - 0xea, 0x6b, 0xa1, 0x45, 0x82, 0x29, 0x68, 0x5c, 0x83, 0xb6, 0x00, 0x41, 0x9f, 0x07, 0xc8, 0x5a, 0x52, 0x2c, 0xf8, - 0xf6, 0x57, 0x88, 0xc1, 0x2b, 0xd3, 0x3b, 0x97, 0xab, 0x8c, 0x84, 0xed, 0x85, 0x5f, 0x0e, 0x6b, 0x7f, 0xe2, 0xd4, - 0xc2, 0xd2, 0x6a, 0x0e, 0xea, 0x67, 0xb6, 0x1c, 0xa7, 0xaa, 0xf6, 0x2f, 0x49, 0x52, 0xed, 0x2a, 0x2d, 0xa7, 0xf7, - 0xf6, 0x4d, 0x97, 0x09, 0x36, 0xf6, 0x03, 0xaa, 0x8e, 0xac, 0x86, 0xdd, 0x17, 0xea, 0x8b, 0x9e, 0x92, 0x09, 0xcd, - 0x47, 0x15, 0xcd, 0xb3, 0xfb, 0xcd, 0x8e, 0xfa, 0x4f, 0x2f, 0x87, 0x22, 0x40, 0xb2, 0x4a, 0x8b, 0xa5, 0xc8, 0xd9, - 0xd8, 0x8f, 0x87, 0x49, 0xa6, 0xc2, 0x0b, 0xd2, 0xd1, 0xdd, 0x6f, 0xdc, 0xdf, 0x72, 0x03, 0x59, 0xa1, 0x55, 0x1b, - 0x8c, 0x95, 0xa2, 0x65, 0xb0, 0xbe, 0x1a, 0xf7, 0xfb, 0xe2, 0x6a, 0x3c, 0x15, 0x41, 0x0d, 0xc4, 0x45, 0xe2, 0x7a, - 0x3c, 0xad, 0x89, 0x25, 0xb5, 0x2b, 0x30, 0x46, 0x8f, 0xab, 0xa2, 0xf6, 0xa9, 0xaf, 0x21, 0x14, 0xa9, 0xd6, 0xcc, - 0xb1, 0xc6, 0x8d, 0x11, 0x71, 0x87, 0x95, 0x6b, 0xa7, 0xf6, 0x3a, 0x00, 0xcb, 0xab, 0x71, 0x41, 0xd8, 0x24, 0xc7, - 0xce, 0x05, 0xac, 0x46, 0x43, 0xaa, 0xdd, 0x70, 0xeb, 0x65, 0xe7, 0x37, 0x8f, 0x13, 0x5b, 0x1b, 0xe1, 0x96, 0x02, - 0xca, 0x28, 0xbf, 0xb1, 0x9c, 0xb0, 0x3b, 0xd5, 0x3b, 0x52, 0xb5, 0x23, 0x4e, 0x5c, 0xc0, 0x72, 0xc3, 0x53, 0xab, - 0x6f, 0x62, 0x70, 0x22, 0x54, 0xad, 0x74, 0xb8, 0x93, 0x09, 0xc4, 0xfd, 0xea, 0xbe, 0xee, 0x95, 0xe0, 0x27, 0x21, - 0xaf, 0xdf, 0xf2, 0x0e, 0x00, 0x2b, 0x3e, 0xe4, 0xc5, 0xb4, 0x70, 0xb4, 0x2e, 0x83, 0x32, 0x40, 0x84, 0x66, 0x00, - 0x74, 0x72, 0x75, 0x10, 0xa5, 0x81, 0x2b, 0xee, 0x10, 0xe1, 0xa7, 0xd1, 0xb3, 0xfc, 0x3a, 0x7c, 0x56, 0x4d, 0xc3, - 0x8b, 0x3c, 0x88, 0x2e, 0xaa, 0x20, 0x7a, 0x56, 0x5d, 0x85, 0xcf, 0xf2, 0x69, 0x74, 0x91, 0x07, 0xe1, 0x45, 0xd5, - 0xd8, 0x77, 0xed, 0xee, 0x9e, 0x90, 0xb7, 0x5d, 0xfd, 0x91, 0x73, 0x65, 0x4f, 0x99, 0x9e, 0x9f, 0xd7, 0x7a, 0xa5, - 0x76, 0x9b, 0xeb, 0x35, 0x6a, 0xa6, 0x3e, 0xca, 0xfe, 0x62, 0x1b, 0x0b, 0x8f, 0xe6, 0x10, 0xfa, 0x8c, 0xb4, 0x98, - 0x7b, 0x9c, 0xeb, 0xcd, 0x9e, 0x14, 0x06, 0x46, 0x4c, 0x2a, 0x19, 0x39, 0xbd, 0xc0, 0x45, 0xa8, 0x42, 0x0c, 0x6b, - 0xe9, 0x6a, 0x9f, 0x75, 0xe9, 0x0d, 0xd4, 0x35, 0xc5, 0xbe, 0x86, 0x0c, 0xbc, 0x68, 0x7a, 0x19, 0x8c, 0x01, 0x39, - 0x02, 0xef, 0xf8, 0x6c, 0x09, 0x07, 0xe6, 0x1a, 0xa0, 0x6f, 0x1e, 0xf5, 0x75, 0xb9, 0xe3, 0x6b, 0xd5, 0x37, 0xd3, - 0xf5, 0x48, 0x29, 0x3f, 0x56, 0xfc, 0xee, 0xe2, 0x39, 0xbb, 0xe5, 0x1a, 0x15, 0xe5, 0xa5, 0x5e, 0xac, 0xf7, 0xc0, - 0x55, 0xf7, 0x12, 0x6e, 0xb3, 0x78, 0xec, 0xca, 0x03, 0x96, 0x6d, 0xd9, 0x03, 0xbb, 0x61, 0x1f, 0xd8, 0x13, 0xf6, - 0x96, 0x7d, 0x65, 0x35, 0x42, 0x94, 0x97, 0x4a, 0xca, 0xf3, 0x17, 0xfc, 0x56, 0xda, 0x1e, 0x25, 0x2c, 0xd9, 0x83, - 0x6d, 0xa7, 0x19, 0x6e, 0xd8, 0x07, 0xbe, 0x18, 0xae, 0xd8, 0x5b, 0xc8, 0x86, 0x42, 0xf1, 0x60, 0xc5, 0x6a, 0xb8, - 0xc2, 0x52, 0x06, 0x7d, 0x1a, 0x96, 0x96, 0xb0, 0x68, 0x0a, 0x45, 0x29, 0xfa, 0x2d, 0xaf, 0x09, 0x3b, 0xad, 0xc6, - 0x42, 0xe4, 0x87, 0x86, 0x2b, 0xf6, 0xc0, 0x17, 0x83, 0x15, 0xfb, 0xa0, 0x6d, 0x44, 0x83, 0x8d, 0x5b, 0x1c, 0x81, - 0x59, 0xe9, 0xc2, 0xa4, 0x40, 0xbd, 0xb5, 0x6f, 0x82, 0x1b, 0x76, 0x83, 0xf5, 0x7b, 0x82, 0x45, 0xa3, 0xcc, 0x3f, - 0x58, 0xb1, 0xaf, 0x5c, 0x62, 0xa8, 0xb9, 0xe5, 0x49, 0xc7, 0x50, 0x5d, 0x20, 0x5d, 0x11, 0x9e, 0x70, 0x7a, 0x91, - 0x7d, 0xc5, 0x32, 0xe8, 0x2b, 0xc3, 0x15, 0xdb, 0x62, 0xed, 0x6e, 0x8c, 0x71, 0xcb, 0xaa, 0x9e, 0x04, 0x05, 0x46, - 0x59, 0xa5, 0xb4, 0x5c, 0x1c, 0xb1, 0x6c, 0xea, 0xa8, 0x41, 0x6d, 0x18, 0xd0, 0x07, 0xa3, 0xff, 0xf0, 0xf5, 0xbb, - 0x1f, 0xbd, 0x52, 0xdf, 0x7c, 0x5f, 0x3a, 0xde, 0x95, 0x25, 0x7a, 0x57, 0xfe, 0xca, 0xcb, 0xd9, 0xcb, 0xf9, 0x44, - 0xd7, 0x92, 0x36, 0x19, 0x72, 0x37, 0x9d, 0xbd, 0xec, 0xf0, 0xb7, 0xfc, 0xd5, 0xf7, 0x1b, 0xab, 0x8f, 0xd5, 0x77, - 0x75, 0xf7, 0x3e, 0x0c, 0x36, 0x8d, 0x53, 0xf1, 0xdd, 0xe9, 0x8a, 0x63, 0x3b, 0x6b, 0xed, 0x9d, 0xf9, 0x3f, 0x5c, - 0xeb, 0x2d, 0x8e, 0xdd, 0x0d, 0xdf, 0x0e, 0x37, 0xf6, 0x30, 0xc8, 0xef, 0x4b, 0xc5, 0x71, 0x56, 0xf3, 0x17, 0x5e, - 0xa7, 0x24, 0x0b, 0xa8, 0x46, 0x9f, 0x8d, 0x34, 0x74, 0xc9, 0x4c, 0x4c, 0x43, 0x7c, 0x91, 0x01, 0x3a, 0x17, 0x88, - 0x67, 0xf7, 0x7c, 0x3c, 0xb9, 0xbf, 0x8a, 0x27, 0xf7, 0x03, 0xfe, 0xd9, 0xb4, 0xa0, 0xbd, 0xe0, 0xee, 0x7d, 0xf6, - 0x2b, 0x2f, 0xec, 0x25, 0xf9, 0xd2, 0x67, 0xef, 0x85, 0xbb, 0x4a, 0x5f, 0xfa, 0xec, 0xab, 0xe0, 0xbf, 0x8e, 0x34, - 0x59, 0x06, 0xfb, 0x5a, 0xf3, 0x5f, 0x47, 0xc8, 0xfa, 0xc1, 0xbe, 0x08, 0xfe, 0x1e, 0xfc, 0xbf, 0xab, 0x04, 0x2d, - 0xe3, 0x5f, 0x6a, 0xf5, 0xf3, 0x83, 0x8c, 0xcd, 0x81, 0x37, 0xa1, 0x15, 0xf4, 0xe6, 0x6d, 0x2d, 0x7f, 0x12, 0x17, - 0x47, 0xaa, 0x9e, 0x1a, 0x0e, 0x5a, 0x2c, 0x66, 0x51, 0x1f, 0xa5, 0x53, 0x79, 0x93, 0x77, 0x3c, 0x93, 0x16, 0xe6, - 0x7b, 0x08, 0x07, 0x7e, 0x67, 0xc3, 0x14, 0xec, 0x38, 0x6e, 0x06, 0xef, 0x18, 0x40, 0x48, 0x66, 0xd3, 0x2d, 0xbf, - 0xe1, 0x4f, 0xf8, 0x57, 0xbe, 0x0b, 0x1e, 0xf8, 0x07, 0xfe, 0x96, 0xd7, 0x35, 0xdf, 0xb1, 0xa5, 0x84, 0x3c, 0xad, - 0xb7, 0x97, 0xc1, 0x96, 0xd5, 0xbb, 0xcb, 0xe0, 0x81, 0xd5, 0xdb, 0xe7, 0xc1, 0x0d, 0xab, 0x77, 0xcf, 0x83, 0x0f, - 0x6c, 0x7b, 0x19, 0x3c, 0x61, 0xbb, 0xcb, 0xe0, 0x2d, 0xdb, 0x3e, 0x0f, 0xbe, 0xb2, 0xdd, 0xf3, 0xa0, 0x56, 0x48, - 0x0f, 0x5f, 0x85, 0x64, 0x3a, 0xf9, 0x5a, 0x33, 0xc3, 0xaa, 0x1b, 0x7c, 0x11, 0xd6, 0x2f, 0xaa, 0x65, 0xf0, 0xa5, - 0x66, 0xba, 0xcd, 0x81, 0x10, 0x4c, 0xb7, 0x38, 0xb8, 0xa5, 0x27, 0xa6, 0x5d, 0x41, 0x2a, 0x58, 0x57, 0x4b, 0x83, - 0x45, 0xdd, 0xb4, 0x4e, 0x66, 0xc7, 0x3b, 0x31, 0xee, 0xf0, 0x4e, 0x5c, 0xb0, 0x65, 0xd3, 0xe9, 0xaa, 0x73, 0xfa, - 0x3c, 0xd0, 0x47, 0x80, 0xde, 0xfb, 0x2b, 0xe9, 0x41, 0x53, 0x34, 0x3c, 0x57, 0xba, 0xe3, 0xd6, 0x7e, 0x1f, 0x5a, - 0xfb, 0x3d, 0x93, 0x8a, 0xb4, 0x88, 0x45, 0x65, 0x51, 0x55, 0xc8, 0x27, 0x1e, 0x64, 0x5a, 0xab, 0x96, 0x30, 0x52, - 0x67, 0x02, 0x26, 0x7d, 0x41, 0x87, 0x41, 0x4e, 0x76, 0x05, 0xb6, 0xe4, 0x9b, 0x41, 0xc2, 0xd6, 0x3c, 0x9e, 0x0e, - 0x93, 0x60, 0xc9, 0xee, 0xf8, 0xb0, 0x5b, 0x2c, 0x58, 0xa9, 0x30, 0x26, 0x7d, 0x7d, 0x3a, 0xda, 0xdd, 0x79, 0x6f, - 0x95, 0xc6, 0x71, 0x26, 0x50, 0xe7, 0x56, 0xe9, 0x6d, 0x7e, 0xeb, 0xec, 0xea, 0x6b, 0xb5, 0xcb, 0x83, 0xc0, 0xf0, - 0x2b, 0x10, 0xed, 0x10, 0xef, 0x1d, 0xd4, 0x18, 0xe9, 0x96, 0xcc, 0xba, 0xaf, 0xec, 0x7d, 0x7d, 0x6b, 0xb6, 0xea, - 0x7f, 0xb7, 0x08, 0xda, 0xcb, 0x65, 0xef, 0x7f, 0x36, 0xaf, 0xfe, 0xd6, 0xf1, 0xea, 0xc6, 0x9f, 0x3c, 0xf0, 0xcf, - 0x18, 0x9d, 0x80, 0x89, 0x6c, 0xc7, 0x3f, 0x8f, 0xb6, 0x8d, 0x53, 0x9e, 0xdc, 0xcb, 0xff, 0xaf, 0x14, 0x68, 0xef, - 0xe6, 0x95, 0xbd, 0x29, 0x6e, 0x79, 0xc7, 0x5e, 0xbe, 0xb4, 0xf6, 0x44, 0x83, 0x50, 0xf2, 0x99, 0xbb, 0x41, 0xd1, - 0xb0, 0x27, 0xbe, 0xe4, 0xd5, 0xec, 0xf3, 0x7c, 0xb2, 0xe5, 0xc7, 0x3b, 0xe2, 0xe7, 0x8e, 0x1d, 0xf1, 0xa5, 0x3f, - 0x58, 0x36, 0xdf, 0xea, 0xd5, 0xce, 0x9d, 0xdc, 0xa9, 0xf4, 0x8e, 0x1f, 0xef, 0xe3, 0xc3, 0x7f, 0xbb, 0xd2, 0xbb, - 0xef, 0xae, 0xb4, 0x5d, 0xe5, 0xee, 0xce, 0x37, 0x1d, 0xdf, 0xc8, 0x5a, 0x63, 0xb8, 0x99, 0x51, 0x30, 0xc2, 0xb4, - 0x85, 0x69, 0x1a, 0x44, 0x96, 0x62, 0x11, 0x12, 0x35, 0x4a, 0xe7, 0x44, 0x9f, 0x05, 0x9d, 0x82, 0x2e, 0x6e, 0xf4, - 0xb7, 0x7c, 0xcc, 0x16, 0xc6, 0x65, 0xf3, 0xf6, 0x6a, 0x31, 0x19, 0x0c, 0x6e, 0xfd, 0xfd, 0x3d, 0x0f, 0x67, 0xb7, - 0x73, 0xf6, 0x8e, 0xdf, 0xd3, 0x7a, 0x9a, 0xa8, 0xc6, 0x17, 0x8f, 0x49, 0x60, 0xb7, 0xbe, 0x3f, 0xb1, 0x88, 0x60, - 0xed, 0x1b, 0xe7, 0xad, 0x3f, 0x90, 0x66, 0x69, 0xb9, 0xb5, 0x7f, 0x78, 0x5c, 0x43, 0x71, 0x0b, 0x42, 0xc6, 0x07, - 0x5b, 0xe5, 0xf0, 0x96, 0x7f, 0xf2, 0xde, 0xf9, 0xd3, 0x77, 0x3a, 0xf8, 0x66, 0xa2, 0xce, 0xa5, 0xb7, 0x17, 0xcf, - 0xd9, 0xaf, 0xfc, 0xb3, 0x3c, 0x53, 0xde, 0x0b, 0x39, 0x6d, 0x6f, 0x90, 0xc4, 0x89, 0x8e, 0x8a, 0xaf, 0x6e, 0x22, - 0x81, 0x42, 0x20, 0x1e, 0x47, 0xcd, 0x1f, 0x26, 0xe5, 0xd4, 0xdb, 0x01, 0xc9, 0x2b, 0xb7, 0x15, 0xd1, 0xb7, 0x9c, - 0xf3, 0xc5, 0xf0, 0x72, 0xfa, 0xb5, 0xdb, 0xb7, 0x47, 0x85, 0xb5, 0xa9, 0x88, 0xb7, 0x5b, 0x0c, 0xc2, 0x3a, 0x99, - 0x59, 0xe6, 0x92, 0x2f, 0x7d, 0xad, 0xcd, 0xdc, 0x63, 0x7a, 0xc7, 0x99, 0x66, 0xc8, 0xe8, 0x0b, 0xcc, 0x4c, 0x87, - 0xc3, 0xdd, 0x39, 0x96, 0xc7, 0x87, 0x6f, 0x9f, 0x3d, 0x19, 0x3c, 0xc1, 0x10, 0x2e, 0x2b, 0x2c, 0xe4, 0x2b, 0x1f, - 0x66, 0x75, 0xeb, 0xda, 0x71, 0xf1, 0x7c, 0xf8, 0x12, 0xf2, 0x06, 0x5d, 0x0f, 0x4d, 0x11, 0xad, 0xf2, 0x3b, 0x8a, - 0x3e, 0x51, 0x72, 0xd0, 0xf1, 0x04, 0x6a, 0x87, 0x5c, 0xb8, 0x5f, 0x9f, 0x71, 0x50, 0x74, 0x60, 0xa9, 0xfd, 0xfe, - 0xf9, 0x67, 0x22, 0x94, 0x86, 0xf1, 0x7e, 0x19, 0x46, 0x7f, 0xc4, 0x65, 0xb1, 0x86, 0x23, 0x76, 0x00, 0x9f, 0x7b, - 0xa6, 0xaf, 0x61, 0x77, 0xbe, 0xef, 0x07, 0xde, 0x96, 0xdf, 0xb0, 0xaf, 0xdc, 0xbb, 0x1c, 0xbe, 0xf5, 0x9f, 0x3d, - 0x01, 0xf9, 0x09, 0xc6, 0xe5, 0x0b, 0x86, 0xc4, 0x76, 0x14, 0xa3, 0xd6, 0xe1, 0x97, 0x1a, 0x62, 0xb5, 0x3e, 0x23, - 0x75, 0x17, 0xa4, 0x7f, 0x54, 0xc8, 0x7e, 0x42, 0x60, 0x35, 0x49, 0x9f, 0x02, 0x93, 0xf8, 0xb6, 0x86, 0x04, 0xd2, - 0xb4, 0x40, 0x0c, 0x0e, 0x14, 0x9f, 0x0a, 0xfe, 0x75, 0xf8, 0x85, 0xe4, 0xbf, 0x45, 0xcd, 0xc7, 0xf0, 0x37, 0x0c, - 0xcd, 0xa4, 0x7a, 0x48, 0xeb, 0x28, 0xf1, 0x6a, 0x38, 0xf5, 0xc2, 0x4a, 0xa8, 0x93, 0x21, 0x48, 0xc5, 0x90, 0x0b, - 0x71, 0xf1, 0x7c, 0x72, 0x5b, 0x8a, 0xf0, 0x8f, 0x09, 0x3e, 0x93, 0x2b, 0x4d, 0x3e, 0xa3, 0x27, 0x8d, 0x2c, 0xe0, - 0x41, 0xbe, 0x2f, 0x7b, 0x35, 0x58, 0xd4, 0x43, 0x7e, 0x5b, 0xbb, 0xef, 0xcb, 0x39, 0x41, 0x8f, 0xec, 0x07, 0x34, - 0x07, 0x03, 0x35, 0x03, 0x29, 0x43, 0x70, 0x0b, 0x97, 0x7e, 0x4f, 0x15, 0xe4, 0xcb, 0xef, 0x7d, 0x11, 0x32, 0x70, - 0x65, 0x41, 0x98, 0x72, 0xa9, 0x90, 0x02, 0xc7, 0x6d, 0x3d, 0xf8, 0xa2, 0xd1, 0x49, 0x24, 0xf8, 0x94, 0x80, 0x24, - 0x69, 0x79, 0x20, 0x69, 0xc4, 0x74, 0x20, 0x2e, 0x94, 0xa6, 0x59, 0x49, 0x11, 0x87, 0xd8, 0x55, 0xdf, 0x21, 0xe1, - 0x59, 0xf0, 0x81, 0xc1, 0xda, 0x91, 0xa2, 0xc5, 0x57, 0x63, 0x3a, 0xd6, 0x61, 0x43, 0x77, 0xb2, 0xb8, 0x5f, 0x25, - 0x75, 0x1a, 0x89, 0x2b, 0xef, 0x85, 0xfc, 0xf9, 0x4f, 0x25, 0x02, 0xe9, 0x5d, 0x0d, 0xc4, 0x20, 0xf8, 0x01, 0xfa, - 0x0f, 0x58, 0xe4, 0x20, 0x28, 0xd5, 0x65, 0x98, 0x57, 0x19, 0x15, 0x38, 0xdb, 0xb1, 0xed, 0x9c, 0xa9, 0xba, 0x05, - 0x5f, 0x84, 0x61, 0x48, 0x3b, 0x5b, 0x35, 0x27, 0xb7, 0x7a, 0x03, 0xf5, 0x4c, 0xe2, 0x48, 0x2d, 0xc5, 0x91, 0xb6, - 0xe6, 0x3e, 0x5d, 0x7a, 0xdd, 0xf2, 0x82, 0x86, 0x0b, 0xd0, 0x8b, 0xd2, 0x5d, 0xe7, 0x13, 0x0a, 0x5d, 0x56, 0xe3, - 0x6a, 0x28, 0xea, 0x50, 0x8e, 0xb1, 0xf6, 0xe7, 0x4a, 0x9e, 0xdf, 0x81, 0xf5, 0x08, 0x0d, 0x5f, 0x95, 0x3a, 0x88, - 0xed, 0x27, 0x7a, 0xd7, 0xa9, 0xd4, 0xdf, 0x00, 0x30, 0x70, 0xea, 0x78, 0xa8, 0x8f, 0xda, 0x29, 0x64, 0x3b, 0xf7, - 0x96, 0x18, 0x95, 0x2b, 0xe1, 0xa9, 0xd2, 0xf2, 0x94, 0xb2, 0xea, 0x6b, 0xc1, 0xad, 0xec, 0x3e, 0x1b, 0x40, 0x46, - 0x1b, 0x14, 0xc8, 0x33, 0x6a, 0x6b, 0x3c, 0x48, 0x35, 0xcd, 0x12, 0xc7, 0xf0, 0x41, 0x91, 0x66, 0x15, 0x58, 0xbc, - 0xcc, 0x25, 0x73, 0x50, 0xb0, 0x5c, 0x6f, 0x36, 0xd3, 0x4c, 0xf5, 0x45, 0x6e, 0x6f, 0x34, 0x5e, 0xa6, 0xff, 0x66, - 0xc9, 0x80, 0x47, 0x17, 0xcf, 0xfd, 0x00, 0xd2, 0x24, 0xc5, 0x03, 0x24, 0xc1, 0xf6, 0x60, 0x17, 0x3b, 0x0c, 0x5b, - 0xc5, 0xca, 0x9e, 0x3c, 0x5d, 0xee, 0xd0, 0x94, 0x4b, 0x70, 0xc9, 0x89, 0xb9, 0x9c, 0xfa, 0xbe, 0x64, 0xbd, 0xa1, - 0x38, 0x65, 0xd3, 0x04, 0x94, 0x04, 0xda, 0x2d, 0xf8, 0x2f, 0x7c, 0x6a, 0xe8, 0xb4, 0x00, 0x4b, 0x6d, 0x37, 0xe0, - 0xbf, 0xd0, 0x2f, 0xb6, 0xbb, 0xa8, 0x1f, 0x98, 0x07, 0x7b, 0xb3, 0xb8, 0x32, 0x06, 0x9c, 0x24, 0xae, 0x34, 0x8f, - 0x5c, 0x3f, 0x28, 0xfa, 0x74, 0x59, 0x3b, 0x70, 0xa6, 0xb8, 0xb0, 0x4a, 0x6d, 0x92, 0x5e, 0xfb, 0x2d, 0x35, 0xf1, - 0x26, 0x4a, 0xaa, 0xc2, 0x76, 0x48, 0xfb, 0x97, 0x94, 0x33, 0x55, 0xdc, 0x21, 0x7a, 0xb2, 0x9b, 0xb8, 0x0a, 0xbc, - 0xb0, 0xaa, 0xd8, 0x08, 0xb5, 0x19, 0x59, 0x4e, 0xe0, 0x74, 0x8f, 0xd5, 0x05, 0x1f, 0xdb, 0xd5, 0xec, 0x82, 0x95, - 0x6c, 0xcd, 0xa4, 0xfb, 0xbc, 0x1d, 0x73, 0x21, 0xaf, 0xf4, 0xb2, 0x68, 0x05, 0xb4, 0x07, 0x81, 0xc3, 0x2f, 0x35, - 0xdd, 0xa3, 0x67, 0x9b, 0x6d, 0x6a, 0xb3, 0xb1, 0xb5, 0x08, 0x21, 0x03, 0xd1, 0xd0, 0x17, 0x72, 0x46, 0x91, 0xaf, - 0xd2, 0x72, 0xad, 0x36, 0x56, 0x19, 0x2f, 0x30, 0x11, 0x64, 0x38, 0x0b, 0xef, 0xd1, 0xd3, 0x7a, 0xa4, 0x29, 0x26, - 0xc1, 0x49, 0x17, 0x7f, 0x01, 0x36, 0x94, 0x27, 0xb9, 0x39, 0x20, 0x07, 0x50, 0xb9, 0x14, 0xa5, 0x52, 0x06, 0xff, - 0xac, 0xee, 0xc8, 0xb6, 0xea, 0xbf, 0xd3, 0x40, 0x06, 0x77, 0xa0, 0x6f, 0x7b, 0xa1, 0xb5, 0xa3, 0x9d, 0x2b, 0x5b, - 0xd3, 0xb6, 0x4c, 0xf3, 0x18, 0x59, 0x6c, 0x00, 0xf9, 0x44, 0x3a, 0x07, 0x22, 0xaf, 0x89, 0xc6, 0x3b, 0xbb, 0xe6, - 0xe3, 0xa9, 0x78, 0x4c, 0xde, 0xab, 0x7c, 0xdf, 0xdc, 0xeb, 0x83, 0x31, 0xf6, 0x2d, 0x28, 0x13, 0x1f, 0xad, 0xb6, - 0xd6, 0x25, 0xd6, 0x5b, 0xa5, 0x49, 0x74, 0xc3, 0x15, 0x74, 0x1c, 0x89, 0x1b, 0xc4, 0xe0, 0x98, 0xf1, 0xda, 0x2a, - 0x4b, 0x5f, 0x61, 0x99, 0xeb, 0x98, 0x25, 0x43, 0x26, 0x75, 0x9e, 0x28, 0x78, 0xf2, 0xf3, 0x84, 0x64, 0x44, 0xd4, - 0x6c, 0xcb, 0x51, 0xca, 0x4d, 0x0b, 0xb8, 0xcc, 0xc8, 0x00, 0xbe, 0x49, 0x13, 0x80, 0x72, 0xf9, 0x12, 0xa4, 0xd2, - 0x10, 0xc1, 0x35, 0xdb, 0x4b, 0x46, 0xb7, 0x8e, 0xd6, 0x41, 0x95, 0x64, 0xee, 0xe0, 0xdc, 0xce, 0x22, 0xa5, 0xde, - 0x7c, 0x84, 0x61, 0x27, 0x1f, 0xc3, 0x3a, 0xc1, 0x6f, 0x03, 0x6a, 0xd2, 0xe7, 0xc2, 0x8b, 0x46, 0x80, 0xa6, 0xbe, - 0x53, 0x65, 0x7c, 0x2e, 0xbc, 0x6c, 0xb4, 0x65, 0x19, 0xa5, 0x50, 0x5d, 0x30, 0xbb, 0x35, 0x5d, 0x88, 0x79, 0x55, - 0x0d, 0xb4, 0x41, 0x6e, 0xd7, 0x31, 0x03, 0x1a, 0xb5, 0x5d, 0x79, 0x64, 0x01, 0x6e, 0xcd, 0x44, 0x60, 0xe4, 0xfc, - 0x87, 0xfc, 0x95, 0x0a, 0xe7, 0xe9, 0xf7, 0x43, 0x6f, 0xbf, 0x0d, 0xa2, 0xd1, 0xf6, 0x92, 0xed, 0x82, 0x68, 0xb4, - 0xbb, 0x6c, 0x18, 0xfd, 0x7e, 0x4e, 0xbf, 0x9f, 0x37, 0xa0, 0x2a, 0x11, 0x26, 0xe2, 0x5e, 0xbf, 0x51, 0xcb, 0x57, - 0x6a, 0xfd, 0x4e, 0x2d, 0x5f, 0xaa, 0xe1, 0xad, 0x3d, 0x89, 0x04, 0x91, 0xa5, 0xb1, 0x79, 0x90, 0x6c, 0xa9, 0x96, - 0x4a, 0xc7, 0xa8, 0x32, 0xa2, 0x96, 0xce, 0xe6, 0x58, 0x31, 0xd2, 0xce, 0x41, 0xc9, 0x80, 0x4c, 0x8b, 0xab, 0x1a, - 0xd3, 0xcd, 0x8a, 0x96, 0x98, 0x8c, 0xb0, 0xb2, 0x2d, 0x6f, 0x37, 0xa9, 0x9a, 0xce, 0xc9, 0xcd, 0xad, 0x52, 0x6e, - 0x6e, 0x05, 0xcf, 0xbf, 0xa1, 0x5b, 0x2e, 0xb9, 0xf6, 0x32, 0x9b, 0x16, 0x4a, 0xb7, 0x8c, 0x6b, 0xb0, 0xb5, 0x6f, - 0x02, 0x59, 0xe6, 0x23, 0x45, 0x8d, 0xed, 0x45, 0xa3, 0x7c, 0x83, 0x6c, 0x45, 0x8c, 0x3a, 0x65, 0xc1, 0xf8, 0xdb, - 0x1d, 0x3d, 0x90, 0x81, 0xaa, 0xaa, 0x36, 0x0e, 0xee, 0xac, 0xf4, 0x87, 0xe5, 0xc5, 0x73, 0x96, 0x58, 0xe9, 0xe4, - 0x42, 0x15, 0xfa, 0x83, 0x10, 0xdd, 0x54, 0x36, 0x1c, 0x1c, 0xea, 0x62, 0x2b, 0x03, 0x42, 0x0f, 0xd3, 0x7b, 0x1b, - 0x2b, 0x59, 0xee, 0x9a, 0xf2, 0xc5, 0x8c, 0x27, 0x1c, 0x47, 0x5f, 0xae, 0x16, 0x61, 0xad, 0x16, 0xd9, 0x09, 0xf0, - 0xd0, 0x5a, 0x2d, 0x85, 0x5c, 0x2d, 0xc2, 0x99, 0xe9, 0x42, 0xcd, 0xf4, 0x0c, 0x14, 0x90, 0x42, 0xcd, 0xf2, 0x04, - 0x60, 0xe1, 0x85, 0x99, 0xe1, 0xc2, 0xcc, 0x70, 0x1c, 0x52, 0xe3, 0xff, 0xa0, 0xf7, 0x3a, 0xf7, 0xdc, 0x72, 0x37, - 0x3a, 0x8d, 0xf8, 0x76, 0xb4, 0xc1, 0x1c, 0x1f, 0x84, 0x13, 0x08, 0x15, 0x2c, 0x11, 0xab, 0x47, 0x23, 0xec, 0xa8, - 0xa1, 0x72, 0xb4, 0x5f, 0x16, 0x96, 0x64, 0x49, 0x58, 0x92, 0x7b, 0x35, 0xce, 0xa5, 0xe5, 0xe2, 0x55, 0x12, 0x88, - 0x44, 0xc6, 0x4b, 0x69, 0x82, 0x4f, 0x78, 0x39, 0x32, 0x52, 0xf3, 0x64, 0x91, 0x7a, 0x39, 0xcb, 0xd8, 0x18, 0x31, - 0x8c, 0x42, 0xbf, 0xa9, 0xfa, 0xfd, 0xb4, 0xf4, 0x72, 0x6a, 0xe7, 0x67, 0x70, 0xbd, 0x3c, 0x75, 0x16, 0x39, 0x42, - 0x5e, 0x8d, 0xa4, 0xc2, 0xf2, 0x5a, 0xa9, 0xa7, 0x2f, 0xc1, 0x07, 0x75, 0xf7, 0x46, 0x01, 0x10, 0x17, 0xb9, 0xf4, - 0xaf, 0x2d, 0xe1, 0xd2, 0x94, 0x1b, 0x18, 0xf4, 0x90, 0xe7, 0x24, 0x84, 0x4a, 0x10, 0x92, 0xc2, 0xba, 0x71, 0x5f, - 0x3c, 0x9f, 0xb8, 0xee, 0x2c, 0x36, 0x30, 0xc1, 0xe1, 0x00, 0x88, 0x07, 0x53, 0x2f, 0x1a, 0xf0, 0x52, 0xcd, 0x99, - 0x4f, 0x5e, 0x4e, 0x30, 0x19, 0xa0, 0xaa, 0x18, 0x38, 0x65, 0x3d, 0x93, 0x8f, 0x8c, 0x9b, 0x99, 0xef, 0x07, 0xf8, - 0x6e, 0x5d, 0x48, 0xf4, 0x07, 0x05, 0x50, 0x90, 0x29, 0x80, 0x82, 0xc4, 0x00, 0x14, 0xc4, 0x06, 0xa0, 0x60, 0xd3, - 0xf0, 0xb5, 0xd4, 0xe1, 0x46, 0x40, 0x17, 0xe1, 0x43, 0xcf, 0xc2, 0xc6, 0x0a, 0xc5, 0xb3, 0x31, 0x1b, 0xb3, 0x42, - 0xed, 0x3c, 0xb9, 0x9c, 0x8a, 0x9d, 0xc5, 0x58, 0x57, 0x91, 0x65, 0xe2, 0x85, 0x84, 0x22, 0xe7, 0xdc, 0x48, 0xd4, - 0xdd, 0xcf, 0xbd, 0x97, 0x64, 0x2c, 0x99, 0x37, 0x34, 0x6a, 0x30, 0x2f, 0xbb, 0x0e, 0x60, 0x5a, 0xf2, 0x6d, 0x41, - 0x83, 0xe9, 0x54, 0x79, 0x44, 0x9a, 0x04, 0xb5, 0x73, 0x99, 0x14, 0x39, 0x21, 0x4c, 0x82, 0x5e, 0x09, 0x7e, 0x23, - 0x51, 0xfe, 0xbf, 0xe9, 0x04, 0x0f, 0x70, 0x4c, 0xb4, 0x4a, 0xbe, 0x82, 0x01, 0x33, 0xe7, 0x2f, 0xa4, 0x53, 0x36, - 0x42, 0x31, 0x96, 0x69, 0x3c, 0xfa, 0xca, 0x86, 0x08, 0x6d, 0xf5, 0x02, 0x4d, 0x4c, 0x50, 0x07, 0x78, 0x44, 0x7f, - 0x8d, 0xbe, 0x1a, 0x0a, 0x95, 0xae, 0x46, 0xea, 0x9a, 0x9d, 0x73, 0xfe, 0xbe, 0x36, 0x9c, 0xc8, 0x98, 0x36, 0x05, - 0xbe, 0x01, 0x81, 0x7c, 0x03, 0x01, 0xe0, 0xaa, 0xe9, 0xcc, 0x5e, 0x01, 0x9c, 0x03, 0x01, 0x3c, 0xce, 0x3b, 0x1e, - 0x3f, 0xd2, 0x5f, 0xc5, 0x71, 0xef, 0x34, 0x0d, 0xdb, 0x7f, 0x05, 0xc6, 0x62, 0x28, 0xc7, 0xf3, 0x9d, 0x82, 0x64, - 0x8f, 0x52, 0x96, 0xae, 0x9a, 0xc8, 0x0e, 0xc5, 0xfa, 0x34, 0xa7, 0x8c, 0xa5, 0x6d, 0x39, 0x46, 0x1b, 0xaf, 0x1f, - 0xe3, 0xf1, 0xcd, 0x8d, 0x9e, 0x7c, 0xd0, 0x83, 0xdb, 0xdb, 0xdb, 0xd7, 0x3d, 0x66, 0xf3, 0xad, 0x58, 0x3c, 0x2b, - 0xe2, 0xc4, 0x69, 0x1d, 0x72, 0x80, 0x83, 0x9c, 0x84, 0x40, 0x3a, 0xc6, 0xa5, 0x16, 0x1d, 0xd4, 0x2c, 0xe7, 0x35, - 0xb0, 0xcc, 0x22, 0xc8, 0x06, 0x88, 0x6a, 0x9a, 0x8a, 0xd5, 0xf0, 0xa0, 0x54, 0xcd, 0x29, 0x95, 0xda, 0x37, 0x9c, - 0xad, 0x4e, 0x9f, 0x58, 0xb5, 0x09, 0xb7, 0xfe, 0xb5, 0xf6, 0x04, 0x6d, 0x25, 0x0d, 0x84, 0x7a, 0xbe, 0x4e, 0xef, - 0x28, 0x8a, 0xc7, 0x99, 0x89, 0xa7, 0x2a, 0x30, 0xf6, 0xad, 0x1d, 0x41, 0x41, 0xd2, 0x74, 0x1d, 0x70, 0x98, 0x46, - 0x27, 0x2c, 0xfe, 0x29, 0x7d, 0x28, 0x2f, 0x6a, 0x05, 0x4e, 0xf2, 0x77, 0xe1, 0x22, 0x92, 0x58, 0xe8, 0x97, 0x04, - 0x40, 0x22, 0x83, 0x57, 0xa3, 0x62, 0x2d, 0x54, 0x80, 0x9c, 0xa2, 0xf4, 0x56, 0xf1, 0x71, 0x29, 0x4a, 0x95, 0x52, - 0x99, 0x1b, 0x95, 0x02, 0xc2, 0xda, 0xc0, 0xd1, 0x05, 0x7c, 0x01, 0x41, 0x6b, 0xb9, 0x5b, 0xdb, 0x9e, 0x37, 0x32, - 0x9f, 0x99, 0xe6, 0x69, 0xf5, 0x51, 0xfd, 0xfd, 0x61, 0x89, 0x61, 0x36, 0x9e, 0xfe, 0xbe, 0xcd, 0x10, 0x6e, 0xfe, - 0x86, 0x21, 0xba, 0x03, 0x70, 0xcc, 0xd2, 0x1e, 0x0a, 0x59, 0x30, 0xc1, 0x1a, 0xaa, 0xf2, 0x94, 0xcf, 0x5e, 0x3e, - 0xb9, 0x05, 0x34, 0x35, 0x74, 0x71, 0xa3, 0x53, 0x5d, 0x95, 0x20, 0x7c, 0xdf, 0x15, 0xea, 0xb1, 0x39, 0xe0, 0xd4, - 0x00, 0x50, 0x2c, 0xf2, 0x5a, 0x8f, 0xed, 0x1f, 0xf4, 0x46, 0xbd, 0x01, 0xe2, 0xe9, 0x9c, 0x17, 0xfe, 0x11, 0xfd, - 0x3a, 0xf5, 0x67, 0x5c, 0x08, 0xa2, 0x5e, 0x4f, 0xc2, 0x7b, 0x71, 0x96, 0xc6, 0xc1, 0x59, 0x6f, 0x60, 0x2e, 0x02, - 0xc5, 0x59, 0x9a, 0x9f, 0x81, 0x58, 0x8e, 0xf0, 0x88, 0x35, 0xbb, 0x03, 0xc4, 0xc0, 0x52, 0x87, 0x24, 0xab, 0x8e, - 0xed, 0xf7, 0xdf, 0x8c, 0x0c, 0x6f, 0x3a, 0x22, 0xc2, 0xe8, 0xdf, 0x15, 0x08, 0x50, 0xb0, 0xcc, 0x6c, 0x67, 0x26, - 0x5d, 0xed, 0x59, 0x3d, 0x6f, 0x36, 0x79, 0x57, 0xef, 0x58, 0x4d, 0xcb, 0xa9, 0x69, 0x95, 0xd5, 0xb4, 0x49, 0x0e, - 0x35, 0x13, 0xfd, 0xbe, 0xc6, 0x47, 0xcd, 0xe7, 0x80, 0xcb, 0x86, 0xc9, 0x6f, 0x66, 0xd5, 0xbc, 0xdf, 0xf7, 0xe4, - 0x23, 0xf8, 0x85, 0xc4, 0x65, 0x6e, 0x8d, 0xe5, 0xd3, 0xd7, 0xc4, 0x67, 0x66, 0x10, 0x8f, 0xee, 0x8e, 0xa0, 0xbe, - 0x6e, 0x84, 0xd7, 0x31, 0x57, 0xd8, 0x4c, 0x4c, 0xdf, 0xc0, 0xe0, 0x79, 0xc2, 0x07, 0x6f, 0x39, 0xfa, 0x1b, 0xe9, - 0xcc, 0x14, 0x2c, 0xe4, 0xdc, 0x9f, 0xbc, 0x41, 0xe8, 0x64, 0x44, 0x7a, 0xd0, 0xe9, 0x04, 0x0d, 0xd9, 0xef, 0xaf, - 0xa0, 0x33, 0x5b, 0xa9, 0x94, 0xad, 0x8a, 0xca, 0x74, 0x5d, 0x17, 0x65, 0x05, 0x1d, 0x4b, 0x3f, 0x6f, 0x85, 0xcc, - 0xac, 0x9f, 0x59, 0xc8, 0x4f, 0x2b, 0x89, 0x35, 0x65, 0xdb, 0x27, 0x6a, 0x83, 0x34, 0xeb, 0x42, 0x75, 0x81, 0x73, - 0x67, 0xed, 0xf5, 0x46, 0xa8, 0x7f, 0xce, 0x47, 0xeb, 0x62, 0xed, 0x81, 0x4b, 0xcc, 0x2c, 0x9d, 0x2b, 0x0e, 0x8d, - 0xdc, 0x1f, 0x7d, 0x29, 0xd2, 0x9c, 0xf2, 0x00, 0x0d, 0xa2, 0x98, 0xdb, 0x6f, 0x81, 0xf4, 0x43, 0x6f, 0x81, 0xec, - 0xa3, 0x73, 0x4e, 0xde, 0x00, 0x38, 0x1d, 0x22, 0xe2, 0x56, 0x24, 0xe8, 0x58, 0x35, 0xbc, 0xb5, 0x70, 0x4f, 0x7b, - 0x69, 0xdc, 0x4b, 0xf3, 0xb3, 0xb4, 0xdf, 0x37, 0x00, 0x9a, 0x29, 0x22, 0xc3, 0xe3, 0x8c, 0x5c, 0x24, 0x2d, 0x04, - 0x53, 0xda, 0x7f, 0x35, 0x86, 0x04, 0x81, 0x80, 0xff, 0x5d, 0x78, 0x4f, 0x00, 0x6d, 0x93, 0x36, 0xe0, 0xaa, 0xc7, - 0x74, 0x60, 0xb6, 0xe4, 0x6c, 0xd5, 0xd9, 0x00, 0x94, 0x53, 0xa5, 0xf5, 0x94, 0xc7, 0x35, 0x45, 0x44, 0xaa, 0x2c, - 0xd4, 0x6f, 0xac, 0x27, 0x93, 0x55, 0x2e, 0x32, 0xe4, 0xa8, 0x4c, 0xef, 0x6b, 0x46, 0x88, 0x5d, 0xfa, 0xf9, 0x02, - 0x96, 0x6c, 0xfc, 0x09, 0x27, 0x6f, 0x09, 0x90, 0xb6, 0xb3, 0x76, 0x55, 0xed, 0x72, 0xdc, 0xda, 0xcd, 0x01, 0xc9, - 0xd7, 0x1b, 0x8d, 0x46, 0xda, 0x4f, 0x4e, 0xc0, 0x50, 0xf5, 0xd4, 0x52, 0xe8, 0xb1, 0x5a, 0x61, 0xeb, 0x76, 0xe4, - 0x32, 0x4b, 0x06, 0xf3, 0x85, 0x71, 0xfc, 0xca, 0x7c, 0xf4, 0xf1, 0x52, 0x59, 0xbb, 0x8e, 0xf8, 0xfa, 0x8f, 0xb2, - 0x5a, 0xdf, 0xf3, 0xae, 0x6a, 0x02, 0xbe, 0xa8, 0x62, 0x4b, 0xbf, 0xe3, 0x3d, 0xd9, 0xbb, 0xf8, 0xda, 0x0d, 0x76, - 0xc9, 0xf7, 0xbc, 0x45, 0x9d, 0xe7, 0x2b, 0x5f, 0x37, 0xaa, 0x74, 0x7b, 0x2f, 0x59, 0xe0, 0xda, 0x3b, 0x6a, 0x1a, - 0xeb, 0x99, 0x1f, 0x3d, 0x2c, 0x42, 0xb6, 0xf3, 0xb1, 0xf7, 0x55, 0xf3, 0xf4, 0xac, 0xa1, 0x37, 0xa9, 0xa1, 0x8f, - 0xbd, 0x28, 0xdb, 0xa7, 0xa6, 0x11, 0xbd, 0x86, 0x0d, 0x7d, 0xec, 0x2d, 0x39, 0x39, 0x24, 0x18, 0x9c, 0x1a, 0xf3, - 0xc7, 0x87, 0xd3, 0x19, 0xfe, 0x8e, 0x01, 0x95, 0x98, 0xcc, 0xa7, 0xc7, 0xb4, 0xa3, 0x00, 0x33, 0xaa, 0xf4, 0xf6, - 0xe9, 0x81, 0xed, 0x78, 0x59, 0x0f, 0x2d, 0xbd, 0x7b, 0x72, 0x74, 0x3b, 0x5e, 0x55, 0xe3, 0x4b, 0x39, 0xe4, 0x79, - 0x3e, 0x1b, 0x8d, 0x46, 0xc2, 0xa0, 0x73, 0x57, 0x7a, 0x03, 0x2b, 0x90, 0xc1, 0x45, 0xf5, 0xa1, 0x5c, 0x7a, 0x3b, - 0x75, 0x68, 0x57, 0xfe, 0x24, 0x3f, 0x1c, 0x8a, 0x91, 0x39, 0xc6, 0x01, 0xe7, 0xa4, 0x50, 0x72, 0x94, 0xac, 0x25, - 0x88, 0x4e, 0x69, 0x3c, 0x95, 0xf5, 0xda, 0x8a, 0xc8, 0xab, 0x11, 0xf2, 0x21, 0xf8, 0xc9, 0x03, 0xb5, 0xf8, 0xb5, - 0x16, 0xc4, 0x1e, 0xfb, 0x54, 0x29, 0x1d, 0xe2, 0x55, 0x01, 0x21, 0xc2, 0x80, 0x37, 0xd0, 0x0e, 0x4a, 0x70, 0xd8, - 0xe1, 0x3e, 0x22, 0x42, 0xf4, 0x6b, 0x2f, 0x9f, 0xc9, 0x70, 0xe5, 0xde, 0xa0, 0x9a, 0x33, 0x40, 0xac, 0xf4, 0x19, - 0xb8, 0x60, 0x02, 0xea, 0x29, 0x3e, 0x45, 0xff, 0x7a, 0xf3, 0xb0, 0xe9, 0xfa, 0xb4, 0x04, 0x54, 0x44, 0xcf, 0x7e, - 0x3e, 0x06, 0xf0, 0xce, 0xae, 0xcd, 0x48, 0x7b, 0xf9, 0x1b, 0x60, 0x58, 0x29, 0x49, 0xb4, 0x73, 0x4a, 0x04, 0xee, - 0x7c, 0x64, 0x4b, 0x3f, 0x4a, 0x81, 0x98, 0x3b, 0x9e, 0x24, 0xb2, 0x07, 0x1b, 0x39, 0x81, 0x5b, 0x0c, 0x78, 0x74, - 0x00, 0x2a, 0x57, 0x0a, 0x72, 0xaf, 0x39, 0x92, 0x3b, 0x7e, 0xe8, 0xfd, 0x30, 0xa8, 0x07, 0x3f, 0xf4, 0xce, 0x52, - 0x92, 0x3b, 0xc2, 0x33, 0x35, 0x25, 0x44, 0x7c, 0xf6, 0xc3, 0x20, 0x1f, 0xe0, 0x59, 0xa2, 0x45, 0x5a, 0xe4, 0x56, - 0x13, 0x35, 0x6e, 0xc2, 0x8b, 0x44, 0xd2, 0x10, 0xed, 0x3a, 0x8f, 0x88, 0x05, 0x80, 0x64, 0xf1, 0xd9, 0xbc, 0xa1, - 0xa8, 0x77, 0x13, 0xbe, 0x45, 0x77, 0x59, 0xec, 0xf7, 0xb7, 0x79, 0x5a, 0xf7, 0x74, 0xa8, 0x0c, 0xbe, 0x20, 0xd5, - 0x04, 0x78, 0xb4, 0xbf, 0x36, 0xc7, 0xab, 0x57, 0x9b, 0x23, 0x65, 0xa1, 0x4a, 0xd4, 0x6f, 0xb1, 0x9a, 0xf5, 0x10, - 0x91, 0x3b, 0xcb, 0x8c, 0xbd, 0xbd, 0xe0, 0x95, 0x9c, 0x55, 0xb1, 0x5d, 0x8e, 0xaf, 0x08, 0x6b, 0x2b, 0x09, 0xd0, - 0xd1, 0x7a, 0xac, 0x4d, 0x31, 0xf2, 0x2b, 0x85, 0x04, 0x5c, 0x74, 0x6c, 0x2d, 0x14, 0x1b, 0x2f, 0x40, 0x5f, 0xb2, - 0x33, 0x0d, 0xb0, 0xde, 0xe8, 0x55, 0xc4, 0x6d, 0xf9, 0x48, 0x85, 0x37, 0xb9, 0xa9, 0x32, 0x2b, 0x9b, 0x45, 0xbb, - 0x9f, 0x2a, 0x5e, 0x21, 0x6e, 0xbd, 0x51, 0x7b, 0x14, 0xa0, 0xf6, 0xd0, 0x42, 0x19, 0xa0, 0x4b, 0xd3, 0x0c, 0x00, - 0x19, 0x00, 0x64, 0xaa, 0x88, 0xcf, 0x04, 0xa8, 0xb4, 0xd5, 0x8d, 0x02, 0x27, 0xd2, 0x6b, 0x60, 0x5c, 0x60, 0xa5, - 0x8f, 0x6c, 0x64, 0xb0, 0xd8, 0x22, 0xc0, 0x2d, 0x47, 0xfa, 0x30, 0x0d, 0x27, 0xdb, 0x68, 0x0e, 0x93, 0x34, 0xbf, - 0x0f, 0xb3, 0x54, 0x42, 0x4b, 0xfc, 0x28, 0x6b, 0x8c, 0x58, 0x40, 0xfa, 0x3e, 0xbd, 0x28, 0xb2, 0x98, 0x20, 0xe1, - 0xac, 0xa7, 0x0e, 0xa0, 0x9a, 0x9c, 0x6b, 0x4d, 0xab, 0x67, 0xb5, 0xc9, 0x43, 0x16, 0xe8, 0xec, 0xc1, 0x98, 0xd4, - 0x72, 0x43, 0x8f, 0xec, 0xaf, 0x1c, 0xcf, 0x08, 0xdf, 0xf5, 0x0c, 0xa7, 0xfe, 0xbb, 0xa9, 0x81, 0x94, 0x29, 0x01, - 0x04, 0x19, 0x1c, 0x4d, 0x08, 0xe5, 0xe9, 0x98, 0x4c, 0x6d, 0x7e, 0x04, 0xc2, 0x11, 0xc1, 0x2b, 0x78, 0x6e, 0x68, - 0xdd, 0x72, 0x63, 0x67, 0x91, 0xa7, 0x09, 0x20, 0x8b, 0x17, 0x7c, 0x0b, 0xc8, 0x9c, 0x7a, 0x55, 0xc8, 0x9e, 0x3d, - 0x17, 0xd3, 0xd9, 0x3c, 0x78, 0x48, 0x68, 0xff, 0x62, 0xc2, 0x6f, 0xba, 0xab, 0xe4, 0xca, 0xd4, 0xba, 0x37, 0xd1, - 0x63, 0x2e, 0x77, 0xfa, 0xb4, 0xe2, 0x18, 0xf1, 0x0c, 0x56, 0x01, 0x39, 0x67, 0x43, 0x7e, 0x7d, 0x0e, 0xd8, 0x2d, - 0x2b, 0xe1, 0x45, 0xfc, 0x3a, 0x94, 0xd5, 0x02, 0xe4, 0x47, 0xce, 0x23, 0xf3, 0xcb, 0x57, 0xdb, 0xa1, 0x9c, 0x53, - 0x14, 0xd1, 0x72, 0x6a, 0x5a, 0x52, 0xc8, 0x0e, 0x3d, 0x05, 0x93, 0xa9, 0x2d, 0x7f, 0x6f, 0x13, 0x97, 0xe4, 0x9b, - 0x49, 0x64, 0x5f, 0x07, 0x58, 0xb3, 0x56, 0xdd, 0x43, 0x37, 0x04, 0x03, 0x44, 0x46, 0x28, 0xb3, 0xb9, 0xbe, 0x5b, - 0x0f, 0x06, 0x0a, 0xe6, 0x57, 0xd0, 0x4d, 0x8b, 0x4e, 0x71, 0x80, 0x9c, 0xb5, 0xae, 0x51, 0xa9, 0x2a, 0x0e, 0x1d, - 0xe6, 0xdd, 0xb2, 0x2a, 0xbb, 0x2c, 0xbd, 0x10, 0xa4, 0x46, 0x5d, 0x05, 0x8b, 0x94, 0x8a, 0x28, 0xde, 0x93, 0x5f, - 0x03, 0x13, 0xcf, 0xac, 0x1c, 0xa5, 0xf1, 0x1c, 0x10, 0x83, 0x14, 0x10, 0xa7, 0xfc, 0x0a, 0xd0, 0x44, 0x17, 0x51, - 0x98, 0xbd, 0x8d, 0xab, 0xa0, 0xb6, 0x9a, 0x7e, 0xef, 0x40, 0xc6, 0x9e, 0xd7, 0xfd, 0x7e, 0x4a, 0x8c, 0x7e, 0x18, - 0x85, 0x81, 0x7f, 0x8f, 0xa7, 0xfb, 0x26, 0x48, 0xcd, 0x2b, 0x0f, 0xf0, 0x8a, 0x2e, 0xb7, 0x36, 0xe5, 0x8a, 0xc6, - 0x85, 0xbf, 0x46, 0x70, 0xf8, 0xd4, 0x51, 0x6c, 0xb7, 0xa9, 0x72, 0x6a, 0x63, 0x30, 0x08, 0xe1, 0xbe, 0x95, 0xf1, - 0xfb, 0xc4, 0xcb, 0x67, 0xd1, 0x1c, 0x14, 0xa5, 0x99, 0xe6, 0x0b, 0x29, 0xa4, 0x9b, 0x00, 0x7d, 0x34, 0x08, 0xb5, - 0xba, 0xf2, 0x8f, 0xc4, 0x4b, 0xd5, 0xb4, 0x36, 0x4f, 0xb1, 0x46, 0x81, 0x98, 0x45, 0xf3, 0x86, 0x65, 0x74, 0x48, - 0xaa, 0xcb, 0xa5, 0x69, 0xc6, 0x1f, 0x56, 0x33, 0x54, 0x2b, 0x8e, 0x9a, 0xa0, 0x46, 0xe9, 0x06, 0x2e, 0x80, 0x7f, - 0xa3, 0x3b, 0x8e, 0x6a, 0x14, 0x29, 0x1a, 0xf0, 0x09, 0x62, 0xc4, 0x9a, 0xcd, 0x13, 0xd6, 0x9a, 0xba, 0x66, 0xf4, - 0xfb, 0x32, 0x64, 0xc8, 0x24, 0x21, 0x4f, 0x1f, 0x2e, 0xd7, 0x4f, 0xa4, 0xba, 0x00, 0x7e, 0xe5, 0x8a, 0xcd, 0x7a, - 0xbd, 0x39, 0xc0, 0xf5, 0xc2, 0xfa, 0x85, 0x8d, 0x2b, 0x38, 0xbf, 0x24, 0xf8, 0x5d, 0xf5, 0x23, 0xcc, 0x32, 0xa8, - 0x02, 0x32, 0xfe, 0x58, 0x48, 0xe7, 0xb9, 0x8b, 0x49, 0xfd, 0x66, 0xa4, 0x2e, 0x28, 0xb3, 0x74, 0x6e, 0x71, 0x82, - 0x80, 0xf3, 0xb0, 0x7a, 0x02, 0xc9, 0xbe, 0x7c, 0xec, 0xd3, 0x8c, 0x02, 0xd5, 0x11, 0xe0, 0xb3, 0x59, 0x3f, 0x84, - 0xfd, 0x03, 0x22, 0x0b, 0xf5, 0x37, 0x6f, 0xe4, 0xac, 0x21, 0x79, 0x20, 0xd5, 0xdc, 0xc7, 0x70, 0x6a, 0x2c, 0xf0, - 0xa5, 0x45, 0x6f, 0x2a, 0x78, 0x4d, 0xc8, 0xdc, 0x0b, 0xb4, 0xf6, 0x2d, 0xe0, 0x08, 0x11, 0x5c, 0x46, 0x29, 0x4e, - 0x7b, 0xbb, 0x5e, 0x80, 0xdc, 0xe6, 0x16, 0xe4, 0xf5, 0x3b, 0x17, 0xbf, 0x38, 0x45, 0x7a, 0x16, 0x5d, 0x60, 0xa0, - 0x0b, 0x32, 0x6f, 0xfc, 0xb3, 0x82, 0x95, 0x0b, 0xe8, 0xbd, 0x54, 0xac, 0xe4, 0x64, 0xdb, 0xa9, 0x3f, 0x4a, 0x65, - 0xbf, 0x3d, 0xb3, 0x26, 0xf0, 0xab, 0xc4, 0x7e, 0x89, 0x4c, 0xbe, 0xe9, 0xb1, 0xc9, 0x57, 0x86, 0x45, 0xa7, 0x96, - 0xc1, 0x39, 0x3d, 0x32, 0x38, 0xf7, 0x76, 0x56, 0x6d, 0x42, 0x18, 0x0a, 0x92, 0x40, 0xd3, 0xa5, 0x87, 0x75, 0xd3, - 0x9f, 0x9f, 0xb4, 0xa8, 0xb6, 0x6a, 0xdf, 0xba, 0x1f, 0x87, 0xd8, 0xc5, 0xaf, 0x12, 0xcf, 0x10, 0x91, 0xfa, 0x40, - 0x07, 0x26, 0x83, 0x27, 0x2e, 0xfb, 0x7d, 0x28, 0x6c, 0x36, 0x9e, 0x8f, 0xea, 0xe2, 0xe7, 0xe2, 0x01, 0x50, 0x1d, - 0x2a, 0xb0, 0xcb, 0xa1, 0x0c, 0x65, 0xc4, 0xa6, 0xb6, 0xdc, 0xf3, 0xfb, 0xab, 0x30, 0x07, 0x79, 0x47, 0xc3, 0xe3, - 0x9c, 0x81, 0x18, 0x06, 0x5f, 0xff, 0xe1, 0xc9, 0x3e, 0x6d, 0x7e, 0x38, 0x83, 0xef, 0x8e, 0xce, 0x3e, 0x22, 0xdd, - 0xcd, 0xd9, 0xba, 0x2c, 0xee, 0xd3, 0x58, 0x9c, 0xfd, 0x00, 0xa9, 0x3f, 0x9c, 0x15, 0xe5, 0xd9, 0x0f, 0xaa, 0x32, - 0x3f, 0x9c, 0xd1, 0x82, 0x1b, 0xfd, 0x6e, 0x4d, 0xbc, 0x7f, 0x54, 0x9a, 0x01, 0x6d, 0x09, 0x91, 0x59, 0x5a, 0xfd, - 0x08, 0x4a, 0x44, 0xc5, 0x8f, 0x2a, 0xa3, 0x5a, 0xad, 0x1d, 0xe7, 0x43, 0xa2, 0x91, 0xb2, 0x69, 0x42, 0xe2, 0x6a, - 0x09, 0xeb, 0x50, 0xcf, 0x4e, 0x9b, 0x6f, 0xc7, 0x79, 0xa0, 0x0e, 0x88, 0x9c, 0x5f, 0xe7, 0xa3, 0x2d, 0x7d, 0x0d, - 0xbe, 0x75, 0x38, 0xe4, 0xa3, 0x9d, 0xf9, 0xe9, 0x93, 0xb5, 0x52, 0xc6, 0x1d, 0x29, 0x72, 0x21, 0xe4, 0x8c, 0xdb, - 0xf6, 0x18, 0x70, 0x00, 0xf8, 0x87, 0x03, 0xfd, 0xde, 0xc9, 0xdf, 0x6a, 0xb7, 0xb4, 0xea, 0xf9, 0xb1, 0xc5, 0x9d, - 0xf1, 0xba, 0x36, 0x44, 0x6d, 0x7b, 0x89, 0x2d, 0xbd, 0x6f, 0x1a, 0xd4, 0x14, 0xd1, 0x4f, 0x58, 0x4d, 0xac, 0xe2, - 0xb0, 0x20, 0x25, 0x24, 0x31, 0x1c, 0xa3, 0x1d, 0x7a, 0x9c, 0x2e, 0x96, 0x9e, 0xdc, 0x77, 0x78, 0xb9, 0xf5, 0x7d, - 0x40, 0xd2, 0x2a, 0x9c, 0x7f, 0xf4, 0x42, 0x03, 0x8f, 0x5e, 0xe4, 0x55, 0x91, 0x89, 0x91, 0xa0, 0x51, 0x7e, 0x4b, - 0xe2, 0xcc, 0x19, 0xd6, 0xe2, 0x4c, 0x81, 0x85, 0x85, 0x04, 0xd0, 0x5d, 0x94, 0x94, 0x1e, 0x9c, 0x3d, 0xd9, 0x97, - 0xcd, 0xef, 0x04, 0x0f, 0x31, 0x5a, 0x00, 0x23, 0xce, 0xae, 0x5d, 0xde, 0x43, 0x58, 0xe6, 0xde, 0xef, 0x6f, 0xef, - 0xf2, 0x02, 0x42, 0x34, 0xcf, 0xa4, 0x62, 0xb5, 0x3c, 0x03, 0xc6, 0x3c, 0x11, 0x9f, 0x85, 0x95, 0x9c, 0x06, 0x55, - 0x47, 0xb1, 0x7a, 0x1b, 0xcf, 0x3d, 0xa0, 0xf8, 0xfe, 0x90, 0x00, 0x97, 0xbb, 0xcf, 0xde, 0x28, 0xd7, 0x54, 0xd2, - 0x23, 0xcf, 0x31, 0x5a, 0x32, 0x01, 0x8a, 0x67, 0x88, 0x93, 0x14, 0x56, 0xcf, 0x4d, 0x90, 0x8a, 0x7c, 0x7d, 0x42, - 0xf1, 0x45, 0xf3, 0x28, 0x6a, 0x58, 0xc8, 0x12, 0x38, 0x1e, 0x92, 0x59, 0x36, 0x47, 0x96, 0xf2, 0xb4, 0x3d, 0x45, - 0x3a, 0x3a, 0xb1, 0xc4, 0x6f, 0x6b, 0x7e, 0xbd, 0x48, 0x45, 0x60, 0xd2, 0xce, 0x56, 0xe6, 0x5e, 0x08, 0x43, 0x95, - 0x70, 0xef, 0x75, 0x3d, 0x0b, 0xe5, 0xa6, 0x68, 0x55, 0xcc, 0x1e, 0xa6, 0xc4, 0x0c, 0x53, 0xac, 0xbf, 0xb0, 0xe1, - 0x37, 0x89, 0x17, 0x83, 0xe1, 0x7a, 0xc9, 0xcb, 0xd9, 0xc6, 0x2c, 0x84, 0xc3, 0x61, 0x33, 0x29, 0x66, 0x4b, 0x08, - 0x73, 0x5d, 0xce, 0x0f, 0x87, 0xae, 0x96, 0xad, 0x85, 0x07, 0x0f, 0x55, 0x0b, 0x37, 0x0d, 0xcb, 0xe1, 0x67, 0x32, - 0x8b, 0xb1, 0x7d, 0x8d, 0xcf, 0xec, 0xcf, 0x17, 0xdd, 0xb3, 0x04, 0xc9, 0x37, 0xd6, 0x40, 0x3b, 0x36, 0x6b, 0x77, - 0xb8, 0x1a, 0x01, 0x49, 0xe9, 0x6e, 0xf4, 0x77, 0x65, 0x27, 0x4f, 0x09, 0x72, 0x47, 0x2b, 0xb0, 0xdf, 0x7d, 0xe3, - 0x4f, 0xb4, 0xd8, 0x83, 0x76, 0x1b, 0x5b, 0x42, 0x54, 0xd3, 0x9e, 0xcb, 0x95, 0x62, 0x69, 0xde, 0x4a, 0x1b, 0x3d, - 0x1f, 0xd6, 0xe7, 0xbe, 0x91, 0x03, 0x05, 0x63, 0xc4, 0x53, 0xeb, 0x20, 0x9a, 0xcd, 0x81, 0x06, 0x03, 0xcd, 0x23, - 0x3c, 0xb5, 0xd0, 0x41, 0x99, 0xb5, 0x61, 0x3f, 0x49, 0x4e, 0x96, 0xc7, 0xe1, 0x5b, 0xf8, 0x97, 0xcf, 0xb0, 0x49, - 0x4c, 0xb1, 0x3d, 0xfe, 0x56, 0x29, 0x2a, 0x3c, 0xb6, 0x20, 0xae, 0xb5, 0x1b, 0x51, 0x1b, 0x2a, 0x87, 0x7f, 0x09, - 0xfb, 0x08, 0xfb, 0x0d, 0x4d, 0x10, 0x06, 0xbb, 0xfe, 0x4c, 0x20, 0x44, 0x2c, 0xc4, 0x0b, 0xfe, 0x56, 0x49, 0x2a, - 0x3a, 0xe1, 0xb3, 0x45, 0x09, 0xbc, 0x75, 0x18, 0xd0, 0x27, 0x14, 0x29, 0x85, 0x30, 0x34, 0x13, 0x7a, 0x47, 0xff, - 0x8d, 0xd8, 0xc9, 0x26, 0xb9, 0x15, 0xf2, 0x81, 0xa4, 0x92, 0x60, 0x82, 0x95, 0x17, 0xca, 0x17, 0xee, 0x85, 0x52, - 0x6b, 0x2d, 0x68, 0xfd, 0xf2, 0x27, 0x89, 0x67, 0xf0, 0xf7, 0x40, 0xc6, 0xa0, 0xdb, 0x88, 0x6a, 0x92, 0x63, 0xfa, - 0x28, 0x9d, 0x67, 0xa0, 0x02, 0x3a, 0x5b, 0x67, 0x61, 0xbd, 0x2c, 0xca, 0x55, 0x2b, 0x52, 0x54, 0x96, 0x3e, 0x52, - 0x8f, 0x31, 0x2f, 0xcc, 0x93, 0x13, 0xf9, 0xe0, 0x11, 0x00, 0xe3, 0x51, 0x9e, 0x56, 0x1d, 0xa5, 0xf5, 0x03, 0xcb, - 0x80, 0x11, 0x38, 0x51, 0x06, 0x3c, 0xc2, 0x32, 0x30, 0x4f, 0xbb, 0x0c, 0x35, 0x88, 0x35, 0xaa, 0xae, 0xd4, 0x06, - 0x73, 0xa2, 0x28, 0xf9, 0x14, 0x4b, 0x2b, 0x8c, 0xa1, 0xa9, 0x2b, 0x8f, 0xac, 0x97, 0x9c, 0xb0, 0x27, 0xbb, 0x81, - 0x74, 0x0b, 0x1b, 0x85, 0x33, 0xe8, 0x5a, 0x96, 0x28, 0x17, 0xdd, 0x32, 0xa2, 0x4c, 0x84, 0xd4, 0xcf, 0x1e, 0xce, - 0xb4, 0xda, 0x6f, 0xec, 0xa4, 0x7d, 0x7b, 0xa4, 0xe8, 0x05, 0x83, 0xf6, 0x69, 0x8f, 0x94, 0x7a, 0xd6, 0xc8, 0x65, - 0x60, 0x4b, 0x97, 0xaa, 0x9e, 0xff, 0x02, 0xe5, 0x3b, 0x98, 0x19, 0x67, 0xb3, 0xdf, 0xf5, 0xe6, 0xf6, 0x64, 0x5f, - 0x37, 0xbf, 0xb3, 0x5e, 0x0f, 0xb6, 0x06, 0x99, 0xf8, 0x42, 0xb1, 0x50, 0x59, 0x85, 0x58, 0x41, 0xda, 0xff, 0x12, - 0xde, 0xef, 0xf0, 0xd6, 0x08, 0xcd, 0xca, 0x78, 0x98, 0x8f, 0x9e, 0xec, 0x45, 0xf3, 0x7b, 0x67, 0xd9, 0x56, 0xae, - 0x4a, 0x66, 0xfb, 0xfd, 0x28, 0x69, 0xce, 0x1e, 0xaf, 0x91, 0xd4, 0x01, 0x3e, 0x5e, 0x9f, 0xe1, 0x23, 0x95, 0x50, - 0x6a, 0x41, 0x55, 0x83, 0xd6, 0xc7, 0x7e, 0x6f, 0x3d, 0xa7, 0x8f, 0x1f, 0xcb, 0xe9, 0x96, 0x14, 0x61, 0xfc, 0xc0, - 0x60, 0xca, 0x4e, 0x9c, 0xba, 0xe4, 0xcd, 0x90, 0xde, 0x75, 0xab, 0xa4, 0x2e, 0x7b, 0x94, 0x08, 0x42, 0x1d, 0xac, - 0x5f, 0xec, 0x87, 0x30, 0xb3, 0x45, 0x7f, 0xd8, 0xac, 0xe6, 0x04, 0x88, 0x08, 0x68, 0xad, 0xf2, 0x3e, 0x70, 0xcc, - 0x17, 0x66, 0xcd, 0x0d, 0xe9, 0xd6, 0x9b, 0x2b, 0xed, 0x95, 0x14, 0xd0, 0xcf, 0x41, 0xe6, 0xf6, 0xd1, 0x2d, 0x57, - 0x2d, 0xf3, 0x5c, 0xda, 0x72, 0xc0, 0xa2, 0x85, 0x40, 0xcd, 0xce, 0xa5, 0xc3, 0x81, 0x82, 0x50, 0x57, 0xa2, 0x8a, - 0xb8, 0x3a, 0x8a, 0x16, 0xa2, 0x56, 0xab, 0x76, 0x39, 0xd9, 0x54, 0xc8, 0x96, 0x44, 0x90, 0x51, 0xb2, 0x57, 0x42, - 0x7d, 0x94, 0xab, 0x3d, 0xd3, 0x70, 0x80, 0x26, 0x60, 0xd3, 0x06, 0x7f, 0x0b, 0xdc, 0xcb, 0xe0, 0xcc, 0xb4, 0x4f, - 0xc3, 0x08, 0x38, 0xcd, 0x21, 0xe6, 0xcf, 0xef, 0x7a, 0x50, 0xc1, 0x83, 0x8e, 0xf4, 0xd7, 0xf5, 0xac, 0xc0, 0x33, - 0xf7, 0xc4, 0xf3, 0x37, 0x27, 0xd2, 0x8b, 0x1c, 0x1e, 0x68, 0x1a, 0xc4, 0x8c, 0xbf, 0x28, 0xcb, 0x70, 0x37, 0x5a, - 0x96, 0xc5, 0xca, 0x8b, 0xf4, 0x3e, 0x9e, 0x49, 0x31, 0x90, 0x98, 0x31, 0x33, 0xba, 0x8a, 0x75, 0x9c, 0xc3, 0xb8, - 0xb7, 0x27, 0x61, 0x85, 0xf6, 0xcf, 0x12, 0x7b, 0x5d, 0x00, 0x96, 0x43, 0xd6, 0xa0, 0x15, 0xde, 0xe9, 0xf6, 0x76, - 0x8f, 0x4b, 0x76, 0x14, 0x37, 0x80, 0x7e, 0x56, 0x43, 0xcb, 0x04, 0xb5, 0xcc, 0xba, 0x93, 0xc9, 0x14, 0xc9, 0xe5, - 0xdb, 0xb0, 0x37, 0xac, 0xc8, 0xe7, 0x8d, 0xdc, 0x1e, 0xde, 0x87, 0x2b, 0x11, 0x6b, 0x0b, 0x3a, 0xe9, 0xc8, 0x38, - 0xdc, 0x0b, 0xcd, 0x8d, 0x74, 0xff, 0xa4, 0x4a, 0xc2, 0x52, 0xc4, 0x70, 0x0b, 0x64, 0x7b, 0xb5, 0xad, 0x04, 0x25, - 0xf0, 0xc1, 0x7e, 0x2c, 0xc5, 0x32, 0xdd, 0x0a, 0xc0, 0x75, 0xe0, 0x7f, 0x4a, 0x44, 0x42, 0x77, 0xe7, 0x21, 0x8a, - 0x35, 0xf2, 0xbe, 0x41, 0x34, 0xf6, 0xd7, 0x20, 0xa7, 0x01, 0x99, 0x48, 0x31, 0x92, 0x05, 0x03, 0x1f, 0x40, 0xce, - 0xd7, 0x60, 0x92, 0x9b, 0xe6, 0x9e, 0x1f, 0xe4, 0xba, 0x83, 0x69, 0x1f, 0x74, 0x2f, 0xae, 0x35, 0xcb, 0xc1, 0x2b, - 0x26, 0xe2, 0x7f, 0xab, 0xbd, 0x92, 0xe5, 0x2c, 0xf3, 0x1b, 0x73, 0xd1, 0xc9, 0xe0, 0xaa, 0x21, 0xfc, 0x62, 0x96, - 0xcd, 0x79, 0x34, 0xcb, 0x74, 0xd4, 0x7f, 0xd1, 0x1c, 0x95, 0x02, 0x70, 0xea, 0x78, 0x01, 0xd6, 0xd0, 0x57, 0xba, - 0x69, 0xc5, 0x23, 0x8d, 0x31, 0x0a, 0x2a, 0x74, 0x10, 0xfa, 0x5b, 0x0d, 0x48, 0x1b, 0x4c, 0xd2, 0x24, 0x54, 0x3e, - 0xb8, 0xa0, 0x1b, 0xe6, 0xe5, 0xca, 0xe5, 0xaa, 0x49, 0xd5, 0xf2, 0xcb, 0x11, 0xf5, 0x5d, 0x2d, 0xb9, 0x54, 0x9b, - 0x4f, 0x8d, 0xb2, 0x46, 0x90, 0xc9, 0x51, 0xfa, 0x7d, 0xca, 0x85, 0x5b, 0x19, 0x93, 0xf5, 0xe1, 0xe0, 0x15, 0xdc, - 0xd4, 0xf8, 0x75, 0x4e, 0x84, 0xa2, 0xf6, 0x90, 0x08, 0x5b, 0xbb, 0x15, 0xba, 0xf7, 0xb8, 0x51, 0x9a, 0x47, 0xd9, - 0x26, 0x16, 0x95, 0xd7, 0x4b, 0xc0, 0x5a, 0xdc, 0x03, 0x5e, 0x54, 0x5a, 0xfa, 0x15, 0x2b, 0x00, 0x3d, 0x40, 0x0a, - 0x1b, 0x3f, 0x22, 0x03, 0xd6, 0x47, 0x2f, 0xf5, 0xfb, 0x7d, 0x63, 0xca, 0xff, 0xf0, 0x90, 0x03, 0x49, 0xa1, 0x28, - 0xeb, 0x1d, 0x4c, 0x20, 0xb8, 0x76, 0x92, 0xf6, 0xac, 0xe6, 0xd7, 0xeb, 0xda, 0x03, 0x7e, 0x2b, 0xdf, 0x22, 0xb1, - 0x7a, 0x6d, 0x5f, 0x6c, 0xf6, 0x69, 0x75, 0x63, 0x34, 0x0e, 0x82, 0xa5, 0xd5, 0x5b, 0xad, 0x72, 0xc8, 0x1b, 0x5e, - 0x81, 0x48, 0x65, 0x5d, 0x5d, 0x2b, 0xe7, 0xea, 0x5a, 0x70, 0xe4, 0x92, 0x2d, 0x79, 0x0e, 0xff, 0x85, 0xdc, 0x2b, - 0x0f, 0x87, 0xc2, 0xef, 0xf7, 0xd3, 0x19, 0x69, 0x65, 0x81, 0x3d, 0x6d, 0x5d, 0x7b, 0xa1, 0x7f, 0x38, 0xfc, 0x08, - 0x5e, 0x23, 0xfe, 0xe1, 0x50, 0xf6, 0xfb, 0x9f, 0xcc, 0x4d, 0xe6, 0x7c, 0xac, 0x94, 0xb2, 0x97, 0xa8, 0x74, 0x7f, - 0x9b, 0xf0, 0xde, 0xff, 0x1e, 0xfd, 0xef, 0xd1, 0x65, 0x4f, 0x85, 0x80, 0x25, 0x7c, 0x86, 0x37, 0x74, 0xa6, 0x2e, - 0xe7, 0x4c, 0xba, 0xbb, 0x2b, 0x3f, 0xf4, 0x9e, 0x86, 0x8a, 0xef, 0xcd, 0x4d, 0x1b, 0x7f, 0xad, 0x8e, 0x34, 0x09, - 0x1d, 0x17, 0xfd, 0xc3, 0xe1, 0x73, 0xa2, 0xf5, 0x69, 0xa9, 0xd2, 0xa7, 0x29, 0x1c, 0x25, 0x43, 0x8c, 0xeb, 0x16, - 0xa6, 0x03, 0xfb, 0x71, 0xf3, 0x55, 0xf2, 0xe2, 0x2c, 0x85, 0x6b, 0x6f, 0x3e, 0x4b, 0xe7, 0x53, 0xb0, 0xae, 0x0c, - 0xf3, 0x59, 0x3d, 0x0f, 0x20, 0x75, 0x08, 0x69, 0xd6, 0x34, 0xfc, 0x4b, 0xe5, 0x0a, 0xde, 0xda, 0xe3, 0xdd, 0xc0, - 0x45, 0xa9, 0x23, 0x7d, 0xd2, 0x46, 0xd3, 0x25, 0x95, 0xfc, 0x27, 0x91, 0xc7, 0x18, 0xb3, 0xf1, 0x9a, 0x78, 0x3f, - 0x8b, 0xfc, 0x55, 0x01, 0xd8, 0x45, 0x00, 0x86, 0x9c, 0xce, 0x1d, 0x49, 0xfc, 0xe7, 0xe4, 0xfb, 0x3f, 0xa6, 0x4b, - 0xfb, 0x58, 0x16, 0x77, 0xa5, 0xa8, 0xaa, 0xa3, 0xd2, 0x76, 0xb6, 0x5c, 0x0f, 0x4c, 0xa2, 0xfd, 0xbe, 0x64, 0x12, - 0x4d, 0x31, 0x14, 0x05, 0x6e, 0x8d, 0xbd, 0x69, 0xca, 0x15, 0x63, 0xf5, 0xc8, 0x58, 0x3f, 0x5f, 0xee, 0xde, 0xc6, - 0x5e, 0xea, 0x07, 0x29, 0x08, 0xc2, 0x1a, 0x4a, 0x29, 0x45, 0x3e, 0x38, 0x9f, 0x61, 0x2a, 0x51, 0xeb, 0x52, 0xaa, - 0xfc, 0x61, 0xa4, 0xf9, 0x30, 0x05, 0xbd, 0xec, 0xdf, 0x2b, 0x98, 0xff, 0xba, 0x3d, 0x58, 0x9f, 0xd6, 0x65, 0x1a, - 0x55, 0x44, 0x95, 0x17, 0xa6, 0xda, 0x04, 0x22, 0xf8, 0xb5, 0xb0, 0xf8, 0x7e, 0x7d, 0x72, 0x24, 0x68, 0xcc, 0x64, - 0xf9, 0x74, 0xe4, 0x7e, 0x61, 0x5f, 0xb9, 0x8e, 0xe7, 0x7f, 0x6e, 0xe6, 0xff, 0x00, 0x9d, 0x21, 0x8b, 0x6b, 0x6e, - 0x19, 0x2c, 0x70, 0xf6, 0x4b, 0x57, 0x0f, 0xf8, 0x9b, 0x79, 0xe2, 0x1a, 0xe8, 0x98, 0xaf, 0xd1, 0x55, 0x31, 0x9d, - 0x15, 0x03, 0xe0, 0xb2, 0xf5, 0x1b, 0x6b, 0x4e, 0xbc, 0xb1, 0x28, 0xaf, 0xe4, 0x82, 0xd0, 0xd7, 0x55, 0x98, 0x8d, - 0xab, 0x62, 0x53, 0x89, 0x62, 0x53, 0xf7, 0x48, 0x2d, 0x9b, 0x4f, 0x6b, 0x5b, 0x21, 0xfb, 0x57, 0xd1, 0x62, 0xf0, - 0x32, 0xac, 0x93, 0x51, 0x96, 0xae, 0xa7, 0xc0, 0xaf, 0x17, 0xc0, 0x59, 0x64, 0x5e, 0x79, 0xef, 0xec, 0x01, 0x5b, - 0x34, 0x9e, 0x02, 0x39, 0x2a, 0xfd, 0x91, 0x37, 0x46, 0xa7, 0x27, 0xfa, 0xfd, 0x7c, 0x4a, 0x31, 0x5f, 0x7f, 0x05, - 0x78, 0xae, 0x5a, 0x2e, 0x40, 0x5f, 0x86, 0x3a, 0xa8, 0x44, 0xa9, 0x15, 0xc3, 0x88, 0x85, 0xbf, 0x0a, 0x24, 0x72, - 0xa6, 0xc0, 0x66, 0x15, 0x25, 0xa1, 0x12, 0x95, 0x92, 0xad, 0x09, 0x6a, 0xe9, 0x7d, 0x51, 0xd6, 0xfb, 0x0a, 0x1c, - 0x25, 0x23, 0x6d, 0x96, 0x93, 0x66, 0x5c, 0x81, 0x32, 0x17, 0xfd, 0x60, 0xff, 0xaa, 0x3c, 0xbf, 0x91, 0xf9, 0x2c, - 0xf7, 0x1d, 0x9d, 0xd3, 0x76, 0x5c, 0xa0, 0xcc, 0x2d, 0xa7, 0xad, 0x96, 0x3c, 0x26, 0xef, 0x59, 0xb0, 0xed, 0xbf, - 0x48, 0x90, 0x62, 0x11, 0xe6, 0x13, 0xaa, 0x6c, 0xfe, 0x0e, 0xa1, 0xb6, 0x38, 0xb0, 0xc7, 0x2e, 0x4c, 0xc4, 0x7f, - 0x0b, 0x96, 0xc4, 0x30, 0x2b, 0x45, 0x18, 0xef, 0xc0, 0xfb, 0x67, 0x53, 0x89, 0xd1, 0x19, 0x3a, 0xb9, 0x9f, 0x3d, - 0xa4, 0x75, 0x72, 0xf6, 0xf6, 0xf5, 0xd9, 0x0f, 0xbd, 0x41, 0x31, 0x4a, 0xe3, 0x41, 0xef, 0x87, 0xb3, 0xd5, 0x06, - 0xd0, 0x32, 0xc5, 0x59, 0x4c, 0xa6, 0x34, 0x11, 0x9f, 0x91, 0x61, 0xf0, 0xac, 0x4e, 0xc4, 0x19, 0x4d, 0x4c, 0xf7, - 0x35, 0x4a, 0x93, 0x6f, 0x47, 0x61, 0x0e, 0x2f, 0x97, 0x62, 0x53, 0x89, 0x18, 0xec, 0x94, 0x6a, 0x9e, 0xe5, 0xed, - 0xb3, 0x38, 0x1f, 0x75, 0xc8, 0x2a, 0x1d, 0xf8, 0xdb, 0x13, 0x69, 0x57, 0xa5, 0x2b, 0x20, 0xf4, 0x00, 0x38, 0xe9, - 0xca, 0x9f, 0x87, 0x83, 0x48, 0x20, 0xd4, 0x82, 0x39, 0x99, 0x46, 0x74, 0x43, 0x7a, 0x85, 0x7d, 0x06, 0x66, 0x21, - 0xa5, 0x79, 0x70, 0x73, 0xb5, 0x18, 0xba, 0x2b, 0x56, 0x8e, 0xc2, 0x6a, 0x2d, 0xa2, 0x1a, 0x59, 0x8f, 0xc1, 0x79, - 0x07, 0x22, 0x00, 0x14, 0x39, 0x78, 0xc6, 0xa3, 0x7e, 0x3f, 0x52, 0x41, 0x39, 0x09, 0xfd, 0xa2, 0xd0, 0x2f, 0x0d, - 0x47, 0x19, 0xf3, 0xaf, 0xa1, 0xe6, 0x08, 0xa8, 0xff, 0x0f, 0x6f, 0xdf, 0xc2, 0xdd, 0xb6, 0x8d, 0xad, 0xfb, 0x57, - 0x2c, 0xde, 0x54, 0x25, 0x22, 0x48, 0x96, 0xdc, 0xa4, 0x33, 0xa5, 0x0c, 0xeb, 0xb8, 0x79, 0xb4, 0xe9, 0x34, 0x8f, - 0xc6, 0x69, 0xa7, 0x53, 0x5d, 0x1d, 0x97, 0x26, 0x61, 0x8b, 0x0d, 0x0d, 0xa8, 0x24, 0xe5, 0x47, 0x25, 0xfe, 0xf7, - 0xbb, 0xf6, 0xc6, 0x93, 0x14, 0xe5, 0x64, 0xe6, 0x9e, 0x7b, 0x57, 0xd6, 0x8a, 0x45, 0x10, 0xc4, 0x1b, 0x1b, 0x1b, - 0xfb, 0xf1, 0xed, 0x3b, 0x16, 0x9b, 0x70, 0x01, 0xb8, 0x9d, 0x13, 0xea, 0x8c, 0xf7, 0x58, 0x13, 0x98, 0xd3, 0x84, - 0xa0, 0x30, 0xd7, 0xc1, 0xc2, 0x00, 0xd0, 0xbb, 0xf6, 0x68, 0xcb, 0x49, 0x97, 0x60, 0xf1, 0xdc, 0xc0, 0xe2, 0xd5, - 0xc5, 0xa2, 0xba, 0xe6, 0x5a, 0x6e, 0x61, 0x53, 0xca, 0x2a, 0x86, 0x00, 0x02, 0xcd, 0x98, 0x61, 0x77, 0xdc, 0xe5, - 0x48, 0xd6, 0x45, 0xc1, 0xc5, 0x4e, 0x60, 0xe8, 0x66, 0x5c, 0x32, 0x73, 0x70, 0x35, 0xc3, 0x3a, 0xa9, 0x28, 0xc0, - 0xae, 0x2e, 0x40, 0xf6, 0xc2, 0x50, 0xd7, 0xcd, 0x6c, 0xb9, 0x0e, 0x7c, 0x5d, 0xba, 0xf0, 0x25, 0x05, 0x2f, 0x57, - 0x52, 0x94, 0xd9, 0x0d, 0xff, 0xd1, 0xbe, 0x6c, 0xc6, 0x92, 0x42, 0x3b, 0xd2, 0xd7, 0xed, 0xee, 0x68, 0x31, 0x8e, - 0x2d, 0xc7, 0xb7, 0x54, 0xba, 0xd7, 0xa3, 0xea, 0x85, 0xd0, 0xd6, 0xb9, 0x96, 0x59, 0x9a, 0x72, 0xf1, 0x4a, 0xa4, - 0x59, 0xe2, 0x25, 0xc7, 0x3a, 0x56, 0xb5, 0x0b, 0x82, 0xe5, 0xc2, 0x24, 0x3f, 0xcf, 0x4a, 0x8c, 0x1d, 0xdc, 0x68, - 0x54, 0x2b, 0xea, 0x94, 0x89, 0x81, 0x21, 0xdf, 0x63, 0xf0, 0x6d, 0x56, 0x26, 0xc0, 0xf0, 0x63, 0xa2, 0xbe, 0xa4, - 0xa7, 0x10, 0xf0, 0x41, 0x85, 0xe6, 0x7e, 0xce, 0x11, 0xfc, 0xda, 0xaa, 0xcc, 0x81, 0xc9, 0xd6, 0x2a, 0x48, 0xc4, - 0xbd, 0xcb, 0xe6, 0x7a, 0x11, 0x2d, 0xd4, 0x5d, 0xa8, 0x17, 0x6f, 0xb7, 0xbd, 0x44, 0xd1, 0x01, 0x27, 0x3f, 0x0d, - 0x5e, 0xc6, 0x59, 0xce, 0xd3, 0x83, 0x4a, 0x1e, 0xa8, 0x0d, 0x75, 0xa0, 0x9c, 0x39, 0x60, 0xe7, 0x7d, 0x59, 0x1d, - 0xe8, 0x35, 0x7d, 0xa0, 0xdb, 0x79, 0x00, 0x17, 0x0c, 0xdc, 0xb9, 0x57, 0xd9, 0x0d, 0x17, 0x07, 0xa0, 0x0c, 0xb4, - 0xc6, 0x03, 0x75, 0x59, 0x8d, 0xd4, 0xc4, 0xe8, 0x18, 0xd6, 0x89, 0x3e, 0x98, 0x03, 0xfa, 0x1d, 0x84, 0xb5, 0x6f, - 0xbd, 0x5d, 0xe9, 0x83, 0x36, 0xa0, 0x3f, 0x2e, 0x4d, 0x1f, 0x74, 0xe0, 0x78, 0x15, 0x1d, 0xb8, 0x31, 0xa4, 0x1a, - 0xb4, 0xd5, 0xc8, 0x2a, 0x50, 0xbc, 0xe1, 0x2d, 0xde, 0x9d, 0x6b, 0xc9, 0xc6, 0x7b, 0x89, 0x18, 0x5f, 0x99, 0xa8, - 0xe2, 0x4c, 0x9c, 0x7a, 0xa9, 0xbc, 0xd6, 0x4e, 0x32, 0xc2, 0xf8, 0x96, 0x95, 0xd4, 0xdf, 0x21, 0xe6, 0x16, 0x69, - 0x0e, 0x83, 0x17, 0x61, 0x45, 0x66, 0xbc, 0xdf, 0x97, 0x33, 0x19, 0x95, 0x33, 0x71, 0x58, 0x46, 0x0a, 0xac, 0x6d, - 0x9f, 0x08, 0xe8, 0x41, 0x09, 0x90, 0x2f, 0x00, 0xaa, 0x1e, 0x12, 0xfe, 0x3c, 0x24, 0xf5, 0xe9, 0x14, 0xfa, 0x14, - 0xda, 0x7a, 0xc5, 0x15, 0xc4, 0xab, 0xba, 0x31, 0xb2, 0x8d, 0x0a, 0x5a, 0x3c, 0x96, 0x67, 0xb5, 0x61, 0x6c, 0x4e, - 0xad, 0x7f, 0xbd, 0xd9, 0x60, 0xca, 0xe6, 0x42, 0xad, 0xc2, 0x90, 0x44, 0xb7, 0xa5, 0x17, 0x49, 0xc4, 0xc2, 0x66, - 0xb5, 0x36, 0xbf, 0x09, 0x03, 0x92, 0x89, 0x14, 0xf7, 0xb3, 0x25, 0xce, 0x5d, 0x3c, 0x9e, 0x57, 0x7d, 0xad, 0xa5, - 0x45, 0xa6, 0xcd, 0xf7, 0xfa, 0x32, 0xa4, 0xa9, 0xa8, 0x21, 0x8d, 0x3a, 0x33, 0xe8, 0xbe, 0x5d, 0xde, 0xb2, 0x1a, - 0x61, 0x02, 0xbc, 0xd2, 0x19, 0x74, 0xa3, 0xf1, 0x40, 0x2c, 0xab, 0x51, 0xb1, 0x16, 0x02, 0x81, 0x87, 0x21, 0xc7, - 0xcc, 0x12, 0x92, 0xec, 0x2f, 0xfe, 0xad, 0x8a, 0xb3, 0x50, 0xc4, 0xb7, 0x06, 0xd9, 0xbb, 0xb2, 0xae, 0xdd, 0x75, - 0xe4, 0xe7, 0xc4, 0xc2, 0x6a, 0xff, 0xa1, 0x79, 0xd4, 0x1a, 0x67, 0x01, 0x6d, 0x4d, 0xab, 0x1b, 0x0e, 0xf7, 0xa8, - 0x8e, 0x45, 0x69, 0xb0, 0x89, 0x3d, 0xb2, 0x5c, 0xb4, 0x8e, 0x19, 0x34, 0xa0, 0xbf, 0xcb, 0xae, 0xd7, 0xd7, 0x08, - 0xe0, 0x56, 0x22, 0xeb, 0x24, 0x95, 0x7f, 0x49, 0x7b, 0xd4, 0xb5, 0x3d, 0x95, 0xff, 0x6d, 0x9b, 0x2a, 0x87, 0x16, - 0x53, 0x1e, 0xbb, 0x39, 0x0b, 0x54, 0x47, 0x82, 0x28, 0x50, 0x5b, 0x2f, 0x98, 0x7a, 0xa7, 0x4c, 0xd1, 0x01, 0x02, - 0x5d, 0x98, 0x33, 0xec, 0x33, 0x8e, 0x18, 0xb3, 0x54, 0x62, 0x30, 0xf5, 0x31, 0x46, 0x35, 0xad, 0x15, 0xa0, 0xeb, - 0xa7, 0x1b, 0xf8, 0x13, 0x15, 0x35, 0x1a, 0x6a, 0x8d, 0xa4, 0x50, 0x34, 0x51, 0xa1, 0xc8, 0xd2, 0x42, 0xc7, 0x55, - 0xe8, 0x24, 0x12, 0x96, 0x80, 0x86, 0x09, 0xd1, 0x49, 0x05, 0xde, 0x1a, 0xc0, 0x99, 0x8f, 0x8b, 0x72, 0x5d, 0x68, - 0x83, 0xb9, 0xef, 0xe3, 0x1b, 0xfe, 0xea, 0xb9, 0x33, 0xaa, 0x6f, 0x59, 0xeb, 0x7b, 0x5a, 0x90, 0xef, 0x43, 0x4e, - 0xd1, 0x81, 0x89, 0x9d, 0x6c, 0xd0, 0x18, 0xa3, 0xac, 0x75, 0xd4, 0x8b, 0xb7, 0x3a, 0x14, 0x8b, 0x36, 0xc1, 0x7b, - 0xc0, 0x53, 0x44, 0x1b, 0x1e, 0x0a, 0x63, 0x55, 0x8d, 0x4f, 0x25, 0x6b, 0xe9, 0xc1, 0x0a, 0x9e, 0xae, 0x13, 0x1e, - 0x82, 0x1e, 0x89, 0xb0, 0x93, 0xb0, 0x98, 0xc7, 0x0b, 0x38, 0x4e, 0x0a, 0x02, 0x6a, 0x07, 0x7d, 0x05, 0x9f, 0x2f, - 0xd0, 0xfd, 0x55, 0xa2, 0x07, 0x18, 0x5a, 0x10, 0x37, 0xa3, 0xa0, 0x8e, 0xae, 0xe3, 0x55, 0x43, 0x45, 0xc2, 0xe7, - 0x05, 0xd8, 0x0e, 0x29, 0xf5, 0x14, 0x68, 0xa1, 0x12, 0xa5, 0x1f, 0x06, 0xbe, 0x43, 0x63, 0x60, 0x6b, 0x1d, 0xa0, - 0xa1, 0x9f, 0x31, 0x4d, 0xad, 0x33, 0x54, 0x3e, 0xf3, 0xee, 0x99, 0xd1, 0x72, 0x66, 0xd1, 0x18, 0xf4, 0x6d, 0x34, - 0x45, 0x71, 0x4e, 0x3e, 0x0b, 0x8a, 0x38, 0xcd, 0xe2, 0x1c, 0xfc, 0x36, 0xe3, 0x02, 0x33, 0x26, 0x71, 0xc5, 0xaf, - 0x64, 0x01, 0xda, 0xee, 0x5c, 0xa5, 0xd6, 0x35, 0x08, 0xc8, 0xbe, 0x07, 0xab, 0x97, 0x86, 0x8e, 0xca, 0x79, 0x77, - 0x69, 0x53, 0x88, 0x58, 0x84, 0x60, 0xd3, 0x4c, 0x97, 0xec, 0x34, 0x54, 0xda, 0x1c, 0x08, 0x75, 0x84, 0xc6, 0xfd, - 0xd3, 0x30, 0xb6, 0x9a, 0x62, 0x6b, 0xf7, 0xb6, 0xdd, 0xfe, 0x5a, 0x7a, 0xe9, 0x34, 0x27, 0x3d, 0xc6, 0x7e, 0x2d, - 0xc3, 0x62, 0x64, 0x3b, 0x42, 0x60, 0xc9, 0x79, 0x9f, 0xfa, 0xaf, 0x68, 0x39, 0x4f, 0xc0, 0x74, 0x44, 0x07, 0xcb, - 0x05, 0xca, 0x8e, 0x01, 0xdd, 0x81, 0xc1, 0x15, 0xfd, 0x3e, 0x58, 0x65, 0x98, 0x0b, 0xc9, 0x92, 0xa4, 0x0c, 0x9e, - 0xa7, 0x1e, 0x1c, 0xfc, 0x9a, 0x29, 0x73, 0x17, 0x65, 0x7d, 0xba, 0x24, 0xd3, 0x14, 0x19, 0x88, 0x75, 0xb8, 0xc9, - 0xd2, 0x28, 0x51, 0x22, 0xb2, 0x25, 0xfa, 0x47, 0x1a, 0x8a, 0xa5, 0x23, 0xf7, 0x22, 0x55, 0x22, 0x54, 0xcc, 0x53, - 0x3c, 0xa9, 0xd3, 0x3a, 0x1d, 0x61, 0xe8, 0x49, 0x50, 0xca, 0xd5, 0x30, 0x50, 0x25, 0xd5, 0x4b, 0x61, 0x53, 0x6c, - 0xb7, 0xfa, 0x62, 0x25, 0xe6, 0xf1, 0x02, 0x5f, 0x0a, 0x1c, 0xc5, 0x7f, 0x70, 0x2f, 0xec, 0x94, 0xda, 0x1e, 0xd4, - 0x8e, 0x28, 0xa1, 0xff, 0xe0, 0x70, 0x91, 0xf8, 0x56, 0xea, 0x10, 0x80, 0x68, 0x11, 0x72, 0xae, 0x0e, 0x52, 0xc3, - 0x0d, 0xed, 0x08, 0xff, 0x0d, 0xd7, 0x67, 0x9c, 0xd1, 0x9b, 0x6a, 0x46, 0x0d, 0xe5, 0xeb, 0x41, 0x1b, 0xa3, 0x3e, - 0x1b, 0x38, 0xac, 0x10, 0x85, 0x36, 0xec, 0xa4, 0x54, 0xa2, 0x85, 0xa1, 0x54, 0x7f, 0x09, 0x15, 0x27, 0xdc, 0x99, - 0x51, 0x96, 0x8c, 0x4f, 0xcb, 0x63, 0x31, 0x1d, 0x0c, 0x4a, 0x52, 0x19, 0x0b, 0x3d, 0xb8, 0x1e, 0x78, 0xfe, 0x3d, - 0x70, 0x0b, 0xf1, 0x90, 0x91, 0xc5, 0x90, 0x1b, 0x9c, 0xfc, 0x16, 0x27, 0x57, 0x8d, 0x4a, 0x15, 0xc7, 0x9a, 0xa8, - 0x16, 0xfc, 0xa3, 0x0c, 0x03, 0xf4, 0x49, 0x0a, 0xc0, 0x64, 0x30, 0xe5, 0x77, 0x20, 0x51, 0x3a, 0x57, 0x37, 0xa4, - 0x9f, 0x45, 0xc1, 0x2f, 0x79, 0xc1, 0x45, 0xe2, 0x0a, 0xb0, 0xbc, 0x83, 0xed, 0x75, 0x54, 0x51, 0x85, 0xc9, 0x6b, - 0x7a, 0x1c, 0x71, 0xe3, 0xfd, 0x67, 0x7a, 0x6c, 0x31, 0x5b, 0xad, 0x63, 0x83, 0xcf, 0x1c, 0x83, 0x0b, 0xba, 0x96, - 0xd8, 0x1a, 0xaa, 0x61, 0x45, 0x60, 0xe0, 0x02, 0x0e, 0xc2, 0x12, 0xc5, 0xb1, 0x95, 0xbc, 0x22, 0x0d, 0x29, 0xed, - 0x03, 0xc3, 0xd1, 0x26, 0x39, 0xbe, 0xcd, 0xb2, 0x9b, 0xc0, 0xf9, 0xa2, 0x73, 0xd2, 0x4c, 0x58, 0x1b, 0xbc, 0xcf, - 0x9b, 0xf3, 0x6b, 0xff, 0x90, 0x50, 0x15, 0xf7, 0x86, 0xb7, 0xe3, 0xde, 0x38, 0xe1, 0xd7, 0x5c, 0x2c, 0x74, 0xa8, - 0x16, 0x73, 0xc9, 0xf2, 0x5b, 0xeb, 0xdd, 0x92, 0xa4, 0x56, 0x40, 0xfb, 0x2c, 0x0b, 0x6a, 0x22, 0x00, 0xe4, 0x0f, - 0x7f, 0x81, 0xd0, 0x19, 0xfe, 0xf6, 0x18, 0x5c, 0x91, 0xc2, 0xbd, 0x43, 0x20, 0xac, 0xe9, 0xe6, 0x4e, 0x6d, 0xc0, - 0x17, 0xe3, 0xfe, 0x8c, 0xa9, 0xa7, 0xdf, 0x66, 0x72, 0x57, 0xd7, 0xed, 0x91, 0x65, 0xf8, 0x08, 0x57, 0x0a, 0xe0, - 0x66, 0xc2, 0x5f, 0x0c, 0x33, 0xa9, 0x3e, 0x01, 0x4c, 0x35, 0x1d, 0xdc, 0x27, 0x08, 0x0c, 0xa0, 0x12, 0x2d, 0x46, - 0x37, 0xca, 0x11, 0xcd, 0xc0, 0xad, 0xe9, 0x56, 0x18, 0x6f, 0x3d, 0x68, 0xa1, 0x67, 0x1a, 0x4e, 0xfc, 0x07, 0xcd, - 0xbc, 0x2a, 0x20, 0x80, 0x56, 0x46, 0xf0, 0xd6, 0xfa, 0x68, 0x8e, 0x10, 0x9f, 0xb0, 0x24, 0x9a, 0xb0, 0x78, 0xa6, - 0xf8, 0x31, 0xa1, 0x9b, 0xa6, 0xb6, 0xe9, 0x03, 0xd2, 0x5f, 0x5c, 0xb3, 0x7e, 0xca, 0xb2, 0xf6, 0xed, 0xa1, 0xe2, - 0xc5, 0xb4, 0x19, 0x07, 0x31, 0x51, 0xc5, 0xf8, 0x5f, 0x70, 0x5f, 0x6a, 0x05, 0x88, 0xcc, 0x5d, 0xf5, 0xf4, 0xfb, - 0xcd, 0x6c, 0x39, 0x10, 0x2a, 0xbf, 0x33, 0x48, 0xfa, 0x74, 0x68, 0x3f, 0xb0, 0x49, 0xd4, 0x16, 0x7a, 0xfe, 0xb8, - 0xd4, 0x4d, 0xbc, 0xbc, 0x36, 0x35, 0xa2, 0x15, 0x32, 0x54, 0xb6, 0x0e, 0x58, 0xdf, 0xdf, 0x87, 0xbb, 0x8b, 0x9a, - 0x86, 0x5a, 0xf7, 0xdc, 0xb5, 0x28, 0x38, 0xf1, 0x07, 0x18, 0x8b, 0x0b, 0x49, 0xad, 0xe3, 0x31, 0xe9, 0x47, 0x0b, - 0x99, 0xdc, 0xa8, 0xab, 0x93, 0x33, 0xc5, 0x3c, 0x81, 0x0b, 0x70, 0xd9, 0xf6, 0x57, 0x54, 0xea, 0x52, 0x6e, 0xaf, - 0x28, 0x4d, 0x0f, 0x69, 0x7b, 0x15, 0xe7, 0x6d, 0xc1, 0x05, 0xff, 0x4c, 0xc1, 0x85, 0x75, 0xb0, 0xee, 0xb8, 0x53, - 0xf6, 0x84, 0x27, 0xca, 0xb4, 0x36, 0xb8, 0xeb, 0x06, 0x63, 0x62, 0xec, 0x77, 0x97, 0x3c, 0xf9, 0x88, 0x2c, 0xf8, - 0xb7, 0x99, 0x00, 0xcf, 0x64, 0xf7, 0x4a, 0xe5, 0xff, 0xde, 0xbf, 0xda, 0xda, 0x77, 0xd6, 0xfc, 0xd3, 0xb3, 0x1e, - 0xee, 0x1c, 0x26, 0x3f, 0x56, 0x67, 0x40, 0x37, 0xd7, 0x32, 0xe5, 0x80, 0x0c, 0x60, 0x2d, 0x92, 0xd1, 0x80, 0x0f, - 0xad, 0x2c, 0xdb, 0xbe, 0xd3, 0xea, 0x82, 0xb0, 0x97, 0xc0, 0x4d, 0xf7, 0xd7, 0x66, 0x66, 0x4e, 0xd7, 0x4a, 0x34, - 0x5d, 0x1a, 0x5b, 0xcb, 0x52, 0x85, 0xf1, 0xbe, 0xf7, 0x24, 0x9b, 0xe6, 0xc7, 0xcb, 0x69, 0x6e, 0xa9, 0xdb, 0xc6, - 0x2d, 0x1b, 0x40, 0x43, 0xec, 0x5a, 0x5b, 0x39, 0xe0, 0xe5, 0xf6, 0x20, 0x9a, 0xaf, 0x15, 0xa1, 0xa7, 0x4a, 0x84, - 0x3e, 0x4d, 0x9b, 0x7d, 0xb0, 0xab, 0x6a, 0xdd, 0x08, 0x79, 0x34, 0x48, 0x35, 0x23, 0xff, 0xf6, 0x86, 0x17, 0x97, - 0xb9, 0xbc, 0x05, 0x38, 0x64, 0x52, 0x1b, 0x85, 0xe5, 0x35, 0xb8, 0xf3, 0xa3, 0xe3, 0x38, 0x13, 0xa3, 0x1c, 0xe3, - 0xb6, 0x22, 0x52, 0xb2, 0x4e, 0x9c, 0x01, 0x1e, 0xb2, 0x3f, 0x69, 0x3a, 0xb4, 0x6b, 0x81, 0xe1, 0x7d, 0x81, 0xbb, - 0xca, 0xd9, 0xc9, 0x26, 0xb7, 0x8b, 0xbe, 0x39, 0xc3, 0xba, 0x23, 0xa5, 0xb5, 0xb1, 0xe8, 0xba, 0x83, 0xb5, 0x66, - 0xd0, 0x16, 0xa1, 0xe4, 0x43, 0xee, 0xa4, 0xfd, 0x2b, 0xa0, 0xc1, 0x79, 0x96, 0xde, 0x59, 0xab, 0xfc, 0x8d, 0x16, - 0xe2, 0x44, 0x31, 0x75, 0xe2, 0x9b, 0x28, 0xd1, 0xe7, 0x67, 0x62, 0xdc, 0x40, 0x20, 0xf5, 0x7b, 0x8c, 0xaf, 0x51, - 0x84, 0x09, 0x5c, 0x07, 0xa2, 0xd8, 0x9e, 0xa8, 0x8d, 0xe5, 0x08, 0x3a, 0x21, 0xc4, 0x3b, 0x28, 0xc3, 0x58, 0x5d, - 0x1c, 0x68, 0x83, 0xa5, 0xaf, 0x5b, 0xeb, 0xdc, 0x10, 0x0a, 0xe3, 0x04, 0xa6, 0x18, 0x24, 0x75, 0xd6, 0x59, 0x26, - 0xa8, 0xb2, 0x63, 0xd2, 0x79, 0x1f, 0xa0, 0xbb, 0x6b, 0xd1, 0x14, 0x5f, 0x77, 0xee, 0xa0, 0x7d, 0x5c, 0xbf, 0xd6, - 0x22, 0x37, 0xf8, 0xf3, 0x96, 0x08, 0x8b, 0xc0, 0x59, 0x6b, 0xf2, 0x55, 0x23, 0x1c, 0x98, 0x92, 0x4c, 0xc3, 0x5e, - 0xa2, 0x6c, 0xba, 0xb7, 0xdb, 0x5e, 0x6f, 0xaf, 0x88, 0xab, 0xc7, 0x58, 0xe5, 0xdd, 0xcc, 0xed, 0x9d, 0x6a, 0x2d, - 0x76, 0x6f, 0xda, 0x7e, 0x8a, 0x1d, 0xb5, 0xd6, 0x6e, 0x37, 0x9c, 0x50, 0x43, 0xbe, 0x15, 0x55, 0x5a, 0x9d, 0x6e, - 0x0c, 0xda, 0x21, 0xb4, 0xb5, 0xc8, 0xe0, 0x46, 0xf9, 0xdc, 0x09, 0x9d, 0x54, 0xc8, 0x55, 0xa7, 0x2e, 0xd8, 0x5c, - 0xf3, 0x6a, 0x29, 0xd3, 0x48, 0x50, 0xb4, 0x39, 0x8f, 0x4a, 0x9a, 0xc8, 0xb5, 0xa8, 0x22, 0x59, 0xa3, 0x5e, 0xd4, - 0x6a, 0x0c, 0x10, 0x90, 0xe9, 0xbc, 0xe9, 0x41, 0x15, 0xcc, 0x86, 0x32, 0x92, 0xd3, 0xf7, 0x60, 0x69, 0x8f, 0x1c, - 0x6b, 0xbd, 0xaf, 0xce, 0x16, 0xdf, 0xea, 0x09, 0xc1, 0x14, 0x66, 0x0f, 0x44, 0x84, 0x6b, 0x1a, 0x43, 0x4e, 0xbb, - 0xc4, 0x65, 0x4d, 0xb7, 0x84, 0x3d, 0xdc, 0xae, 0x64, 0x27, 0x6e, 0x9e, 0x34, 0x37, 0x57, 0xb0, 0x93, 0x62, 0x3e, - 0x06, 0xed, 0x97, 0x54, 0xd7, 0x2e, 0xcd, 0xad, 0xc7, 0x83, 0x80, 0x06, 0x83, 0xc2, 0xf0, 0xaf, 0x13, 0xe3, 0xe1, - 0x49, 0x03, 0x82, 0xa4, 0x5c, 0x84, 0x63, 0xdf, 0x88, 0x7e, 0x32, 0x95, 0xc7, 0x1c, 0x2d, 0xde, 0xa1, 0xd5, 0x09, - 0x04, 0xf4, 0x12, 0xa1, 0x24, 0x46, 0x55, 0x68, 0x44, 0x50, 0x9e, 0x96, 0xbf, 0x54, 0xd5, 0x21, 0xa0, 0x90, 0xf6, - 0x15, 0x85, 0xb2, 0x4d, 0x62, 0x68, 0x86, 0x5f, 0xce, 0x27, 0x0b, 0x3d, 0x03, 0x03, 0x39, 0x3f, 0x5a, 0xe8, 0x59, - 0x18, 0xc8, 0xf9, 0x57, 0x8b, 0xda, 0xad, 0x03, 0x4d, 0x40, 0x3c, 0x17, 0x8e, 0x4e, 0x4a, 0xab, 0xb2, 0x05, 0x74, - 0xf3, 0x10, 0x41, 0xff, 0x87, 0x3d, 0x04, 0x9d, 0x5c, 0x68, 0x47, 0x6e, 0x40, 0xdb, 0x21, 0x09, 0xec, 0x15, 0x93, - 0x0a, 0x13, 0x8b, 0xe8, 0x98, 0x8d, 0xc1, 0x10, 0x5b, 0x7d, 0x70, 0xcc, 0xc6, 0x53, 0x9f, 0x04, 0x01, 0xa3, 0xfb, - 0xbd, 0x01, 0x07, 0xbf, 0xc3, 0xab, 0xf4, 0xc9, 0x46, 0xa0, 0x9b, 0xbe, 0xbb, 0x1b, 0x7a, 0x17, 0x57, 0x70, 0xaa, - 0x76, 0xf7, 0x24, 0x74, 0x93, 0x69, 0xc7, 0xea, 0x35, 0xc4, 0x0d, 0xf9, 0x95, 0xd1, 0x68, 0x64, 0x53, 0x42, 0x42, - 0x0c, 0xe7, 0xd0, 0xcc, 0x69, 0xb9, 0x7c, 0x75, 0xeb, 0xd9, 0x80, 0x0c, 0x33, 0xbd, 0x63, 0xb2, 0x7e, 0x80, 0xb2, - 0xea, 0x31, 0xb4, 0x43, 0xef, 0x91, 0xe3, 0x87, 0x07, 0xdf, 0x64, 0xfc, 0xc4, 0xe1, 0xda, 0xc3, 0xb9, 0xf0, 0x5d, - 0xd6, 0x8c, 0xcc, 0xa1, 0xf3, 0xec, 0xe3, 0x78, 0x0f, 0xe3, 0xe4, 0xd3, 0x2c, 0x94, 0x37, 0x5e, 0xd3, 0xff, 0xa8, - 0xf4, 0x66, 0x87, 0x43, 0x4e, 0x57, 0xb0, 0xe2, 0x66, 0x55, 0x68, 0xf8, 0x59, 0xe4, 0x8d, 0x23, 0x5e, 0x93, 0xa8, - 0xea, 0x3e, 0xef, 0x6d, 0xc4, 0xd2, 0x8e, 0x71, 0x00, 0x70, 0xa2, 0x56, 0x0d, 0xbb, 0xd2, 0xb8, 0x56, 0x07, 0x31, - 0x22, 0x25, 0x6c, 0x95, 0x38, 0x12, 0xca, 0xdf, 0x00, 0x84, 0xc5, 0x50, 0x1c, 0x6f, 0x0d, 0xeb, 0x03, 0xec, 0x87, - 0x2e, 0xd0, 0x34, 0xa7, 0x54, 0x33, 0x00, 0x48, 0x02, 0xfe, 0xe8, 0xe9, 0xa6, 0xa1, 0xb2, 0xcd, 0xf3, 0xd0, 0xb2, - 0xba, 0x82, 0x07, 0x7a, 0xea, 0x4a, 0x06, 0xc6, 0x55, 0x1d, 0x7b, 0x9b, 0xfd, 0xed, 0xd1, 0x2a, 0xf2, 0x9d, 0x4d, - 0x6a, 0x9a, 0x05, 0x90, 0xa2, 0x71, 0xe9, 0x0b, 0x3d, 0x9d, 0x00, 0xad, 0xd7, 0x96, 0x8a, 0xf6, 0xfb, 0x28, 0x46, - 0x8d, 0x0b, 0x05, 0x56, 0x61, 0x82, 0xc2, 0x21, 0xc2, 0x08, 0xa1, 0xdf, 0x95, 0xe1, 0xc6, 0x17, 0x64, 0x10, 0x0d, - 0xd7, 0xa2, 0x43, 0x11, 0x39, 0x5e, 0xb4, 0x2d, 0x55, 0x35, 0x27, 0x4d, 0x5b, 0x02, 0x6f, 0x22, 0x03, 0xb6, 0xf3, - 0x4f, 0x1b, 0x22, 0x57, 0xe1, 0x02, 0x86, 0xef, 0x88, 0x6b, 0x41, 0x74, 0x53, 0x9b, 0x7a, 0x1b, 0x76, 0x88, 0x8e, - 0xa6, 0x78, 0x74, 0xc8, 0x3d, 0x77, 0xcf, 0x6d, 0x11, 0xdf, 0x7e, 0x82, 0xdc, 0x35, 0x9d, 0xbd, 0x14, 0x61, 0x50, - 0xb7, 0x6c, 0xa0, 0x58, 0xc7, 0x4e, 0x50, 0x80, 0x01, 0x5c, 0xfe, 0x02, 0x3a, 0x36, 0x18, 0x54, 0x04, 0x9f, 0x14, - 0xb6, 0x4d, 0x83, 0xfc, 0x11, 0xef, 0x86, 0x0e, 0xaf, 0x2d, 0x79, 0x20, 0x5e, 0x61, 0x9f, 0x28, 0x61, 0xff, 0x82, - 0x82, 0xee, 0x28, 0x2f, 0x57, 0x85, 0xab, 0xd2, 0x00, 0x54, 0xd9, 0xf1, 0x5c, 0x6b, 0x4a, 0x5a, 0xc0, 0x4a, 0x49, - 0xdd, 0xf9, 0x4d, 0x70, 0xdc, 0x92, 0xa9, 0xf0, 0xad, 0xba, 0x51, 0xe5, 0xb1, 0x44, 0x91, 0x8e, 0x3d, 0xdb, 0x39, - 0x58, 0x03, 0xe0, 0x29, 0x6c, 0x2f, 0xce, 0x04, 0x7c, 0xee, 0xb4, 0xcb, 0x96, 0xb9, 0x04, 0x8a, 0xfa, 0x61, 0x9c, - 0x97, 0x1d, 0x5f, 0xee, 0x8e, 0xb6, 0xf7, 0xd0, 0x1b, 0xb1, 0x31, 0x5e, 0x9f, 0x47, 0x4d, 0x3f, 0x7b, 0x86, 0x2b, - 0x4b, 0x41, 0x1e, 0x68, 0xaa, 0x47, 0x18, 0x1d, 0x02, 0xd3, 0x94, 0x9f, 0xb0, 0xf1, 0x74, 0x38, 0x34, 0x64, 0xd0, - 0x6b, 0x26, 0x86, 0x02, 0xfb, 0x0c, 0x5a, 0x67, 0x26, 0xae, 0xf1, 0x69, 0xfb, 0x0a, 0x5a, 0xdd, 0xa1, 0x4c, 0xee, - 0x1c, 0x0c, 0x1f, 0x68, 0xc9, 0x14, 0x4c, 0x15, 0xde, 0x10, 0xa9, 0x64, 0x6f, 0x96, 0xd6, 0x61, 0xdf, 0x2e, 0x14, - 0x5a, 0x68, 0xe2, 0x57, 0x19, 0xe2, 0xa7, 0xae, 0x33, 0xff, 0x36, 0xed, 0x53, 0x83, 0x58, 0x38, 0x12, 0x83, 0x88, - 0x5f, 0x9c, 0x2a, 0xdb, 0x09, 0xa1, 0x62, 0xe3, 0xa1, 0x6b, 0xdd, 0x38, 0x92, 0x2a, 0x0c, 0xa5, 0xd0, 0x78, 0x6a, - 0xb8, 0xef, 0x85, 0x0e, 0x5f, 0x87, 0x59, 0xdc, 0x66, 0x8d, 0xa4, 0xc6, 0x38, 0x15, 0x26, 0x4e, 0xa5, 0x5c, 0x45, - 0x02, 0x03, 0xe5, 0xd9, 0xc2, 0x20, 0xc0, 0x24, 0x26, 0x19, 0x5b, 0x0b, 0x61, 0xc2, 0xd8, 0xb9, 0xc2, 0x34, 0x75, - 0x91, 0xfa, 0xcd, 0xc0, 0x64, 0x41, 0x43, 0x7e, 0x8f, 0x46, 0x6b, 0xaa, 0xa6, 0x00, 0xc3, 0x38, 0x4a, 0x35, 0xfe, - 0x2d, 0x42, 0x6d, 0x86, 0x01, 0x80, 0x6d, 0xde, 0xc9, 0x4c, 0x54, 0xaf, 0x04, 0x42, 0xa0, 0x39, 0xfb, 0xa9, 0xb8, - 0xda, 0x99, 0x05, 0xa3, 0x68, 0xb7, 0x57, 0x3e, 0x1f, 0x38, 0xa1, 0x3c, 0x55, 0x17, 0xa8, 0x97, 0xb2, 0x78, 0x2d, - 0x53, 0xde, 0x0a, 0x91, 0x79, 0x20, 0xd9, 0x87, 0x7c, 0x04, 0xe7, 0x15, 0x3a, 0x95, 0x9b, 0x6d, 0xa2, 0xcc, 0x92, - 0x24, 0x63, 0x81, 0xb1, 0x79, 0x09, 0x66, 0x52, 0x33, 0x63, 0xf8, 0x35, 0xc4, 0x19, 0xdb, 0x39, 0x09, 0x37, 0xfb, - 0x79, 0x60, 0x88, 0x52, 0x2e, 0x5a, 0xa2, 0x61, 0x6b, 0xc7, 0xeb, 0xc9, 0x35, 0xe1, 0x3e, 0x6c, 0xc4, 0x9a, 0x8c, - 0x31, 0xae, 0xcd, 0x8d, 0xac, 0x1f, 0x2d, 0xf0, 0x60, 0x4c, 0x59, 0x7f, 0x02, 0x99, 0x56, 0x52, 0xd6, 0xf9, 0xc2, - 0x88, 0x99, 0x54, 0xa2, 0x77, 0xfb, 0xc6, 0x67, 0x75, 0x17, 0x51, 0xbf, 0xb5, 0xdf, 0x93, 0x7a, 0xb8, 0xf7, 0x1f, - 0x14, 0xd6, 0xa0, 0x32, 0xe2, 0x32, 0xa2, 0x3c, 0x73, 0xa0, 0x9b, 0x26, 0x45, 0x9c, 0x9e, 0xaf, 0xe2, 0xa2, 0xe4, - 0x29, 0x54, 0xaa, 0xa9, 0x5b, 0xd4, 0x9b, 0x80, 0xbd, 0x21, 0x92, 0x24, 0x6b, 0x69, 0x6c, 0xc5, 0x2e, 0x0d, 0xd2, - 0xb3, 0x37, 0xe2, 0xd2, 0xcb, 0x0a, 0x0d, 0x69, 0xa9, 0x77, 0x16, 0x2a, 0x99, 0xbf, 0xe2, 0x3f, 0x83, 0x5a, 0x81, - 0x8e, 0x36, 0x29, 0xc6, 0x33, 0x60, 0xc4, 0x77, 0x83, 0x59, 0x3d, 0x40, 0x5c, 0x34, 0x41, 0xa9, 0x77, 0xc4, 0x8e, - 0x9f, 0x9a, 0x3c, 0xbc, 0x0b, 0x39, 0x67, 0xf0, 0xe9, 0xc3, 0x2c, 0x51, 0x6b, 0x1d, 0x89, 0x91, 0x9a, 0x01, 0x34, - 0x1d, 0x94, 0x39, 0x8f, 0x45, 0x30, 0xeb, 0x99, 0xc4, 0xa8, 0xc7, 0xf5, 0x2f, 0xd0, 0x50, 0xfb, 0xcd, 0xca, 0xf2, - 0xac, 0xba, 0xff, 0x1c, 0x0e, 0x6c, 0x6a, 0x2b, 0xe8, 0xf1, 0xba, 0x92, 0x57, 0x57, 0xaa, 0xdb, 0x7e, 0x21, 0x46, - 0x4e, 0xd7, 0xb8, 0x96, 0xce, 0xab, 0x05, 0xeb, 0x75, 0xa7, 0x9b, 0xc5, 0xdd, 0x2c, 0xa3, 0x81, 0xb0, 0xb6, 0xf3, - 0x89, 0xe6, 0xcf, 0x9a, 0x6d, 0xf7, 0xf1, 0x16, 0xc4, 0x2c, 0x00, 0x88, 0xf4, 0x20, 0x0a, 0x96, 0x59, 0xca, 0x03, - 0x2a, 0xf7, 0x71, 0x94, 0x85, 0xd2, 0xcb, 0x59, 0xc6, 0x4f, 0x9b, 0xc6, 0x5a, 0x67, 0x85, 0x32, 0xb4, 0x36, 0xba, - 0xd3, 0x55, 0x86, 0xd8, 0x7e, 0x12, 0x67, 0x0b, 0x70, 0x7f, 0xcc, 0x50, 0x68, 0xe8, 0x2c, 0x23, 0x4d, 0x34, 0x7c, - 0xd7, 0x9e, 0x41, 0x46, 0x71, 0xb2, 0xce, 0x2b, 0xe9, 0x46, 0x9f, 0xb5, 0x91, 0x30, 0xf7, 0x10, 0xfd, 0x2a, 0x06, - 0x8f, 0x72, 0x9f, 0xd7, 0x46, 0x27, 0xd3, 0x32, 0xd2, 0xee, 0xfc, 0xa4, 0x5e, 0x66, 0xa9, 0xd6, 0x61, 0xfb, 0x0c, - 0x7b, 0x6b, 0x4c, 0x7a, 0x13, 0x52, 0xc3, 0x48, 0x7c, 0x3a, 0xa3, 0x46, 0x08, 0x68, 0xcb, 0xf1, 0x77, 0xf8, 0x0c, - 0x43, 0x53, 0x60, 0xa9, 0xe2, 0x16, 0x76, 0xc3, 0xd7, 0x7c, 0xb2, 0x6a, 0x01, 0x08, 0x66, 0xe5, 0xeb, 0x5d, 0xbc, - 0x12, 0xea, 0x73, 0x6d, 0x06, 0x80, 0x2c, 0x28, 0xe5, 0x8e, 0x9f, 0x52, 0xe9, 0x60, 0x89, 0xa2, 0xed, 0xe5, 0xf4, - 0x8d, 0x8e, 0x8d, 0x1f, 0xd2, 0x73, 0x01, 0xdb, 0x85, 0xfc, 0xd6, 0x5e, 0xbd, 0x44, 0x45, 0x6a, 0xdb, 0xac, 0x07, - 0xf8, 0x72, 0x83, 0x26, 0x61, 0x04, 0x65, 0xca, 0x14, 0xc0, 0xe0, 0xa6, 0x1a, 0x05, 0x93, 0x56, 0x23, 0x61, 0x4b, - 0x3d, 0xc9, 0x72, 0xd3, 0x07, 0xa7, 0xda, 0x23, 0xe8, 0xd1, 0x0e, 0x27, 0x2d, 0xfb, 0xb5, 0x82, 0xa3, 0x93, 0xab, - 0x21, 0x6a, 0xe6, 0xbd, 0xb6, 0x23, 0x43, 0xca, 0x65, 0x18, 0x08, 0xa6, 0x1c, 0xf3, 0xf4, 0xd8, 0x7a, 0x46, 0x44, - 0x0f, 0x9c, 0x7d, 0xa6, 0x5b, 0x75, 0x25, 0x01, 0xd1, 0xf1, 0xeb, 0x27, 0xaf, 0xae, 0xe3, 0x2b, 0x83, 0xa2, 0xd4, - 0xb0, 0x88, 0x51, 0xa6, 0x7d, 0x95, 0x84, 0xc1, 0xfb, 0xf9, 0xfd, 0x8f, 0x2a, 0x4b, 0xed, 0xf7, 0x60, 0x63, 0x45, - 0x55, 0x3f, 0x97, 0xbc, 0x68, 0x0a, 0xb0, 0xf6, 0x59, 0xa2, 0x40, 0xee, 0xf7, 0x36, 0xcd, 0x7c, 0x13, 0x35, 0x6e, - 0x36, 0xac, 0x37, 0xae, 0xdb, 0xa5, 0xb6, 0x64, 0x47, 0x56, 0x22, 0x67, 0x16, 0x83, 0x19, 0x3f, 0x2a, 0x0c, 0x4a, - 0xc3, 0x06, 0x55, 0xa9, 0xf8, 0xbd, 0x11, 0xc1, 0xa9, 0x63, 0x55, 0x61, 0x4c, 0x03, 0x66, 0x5b, 0x51, 0x6b, 0x50, - 0x07, 0xa5, 0xb4, 0x35, 0x01, 0xd9, 0x7e, 0x65, 0x05, 0x35, 0xbf, 0xff, 0x65, 0x0c, 0xf9, 0x9a, 0x52, 0x50, 0x49, - 0xc0, 0xce, 0xa0, 0xd1, 0x53, 0x25, 0x0c, 0xa4, 0x20, 0x78, 0x02, 0x94, 0x2f, 0xa2, 0xc6, 0x6a, 0xb7, 0xaf, 0x4e, - 0x8d, 0xd1, 0x16, 0x10, 0x5a, 0x48, 0x8f, 0x2e, 0xfb, 0xb8, 0x8d, 0x75, 0x20, 0xf1, 0xe0, 0x04, 0xdb, 0xb9, 0xba, - 0x46, 0x23, 0xa1, 0xf9, 0x43, 0xa3, 0x01, 0xaf, 0x69, 0x05, 0x0a, 0xf5, 0x1c, 0x47, 0x43, 0x67, 0x87, 0x14, 0x44, - 0x6c, 0xd0, 0xc2, 0xbe, 0x3d, 0x1f, 0x9a, 0x7d, 0x3d, 0x4f, 0x16, 0xa4, 0xa6, 0xd2, 0x7d, 0xee, 0x96, 0x90, 0xb5, - 0xea, 0x50, 0x56, 0x1e, 0xe0, 0x78, 0xa1, 0x64, 0xfe, 0x0e, 0x93, 0x1a, 0xa5, 0x31, 0xa1, 0x31, 0x62, 0x01, 0x4b, - 0x82, 0xf6, 0x7a, 0xa0, 0x7e, 0x19, 0x84, 0x0a, 0x67, 0x7a, 0x22, 0xf1, 0x29, 0xe5, 0xea, 0xd3, 0x82, 0xd4, 0xd3, - 0x82, 0x39, 0xd0, 0x4b, 0xdf, 0xca, 0xaf, 0x6c, 0x7c, 0xb4, 0xbb, 0x77, 0xcd, 0x85, 0x75, 0x0c, 0x71, 0xb1, 0x85, - 0xdf, 0x9c, 0x9a, 0x02, 0xb0, 0xe1, 0xa9, 0x2e, 0xcb, 0x37, 0x6a, 0x22, 0xb3, 0x38, 0x24, 0x11, 0x48, 0xb6, 0x9b, - 0x9b, 0xdb, 0x08, 0xb6, 0xbd, 0x85, 0xda, 0x50, 0x7f, 0x79, 0xdb, 0x7d, 0xcf, 0xf0, 0x72, 0x4f, 0xee, 0xdd, 0xb4, - 0xa1, 0xfc, 0x7e, 0xff, 0x2a, 0xf9, 0xbf, 0xaa, 0x64, 0xbf, 0x55, 0x66, 0xdd, 0x16, 0xef, 0x77, 0x1d, 0xb7, 0x1c, - 0xa3, 0x41, 0x60, 0x4d, 0x81, 0x81, 0xf4, 0xa4, 0x31, 0x4d, 0x74, 0x74, 0x65, 0xc6, 0x0c, 0x1e, 0x5d, 0x80, 0xe6, - 0x30, 0x9d, 0xe7, 0x31, 0x00, 0x07, 0xf8, 0x47, 0x1e, 0xa1, 0xfe, 0xe9, 0x3c, 0x0f, 0xce, 0x83, 0x41, 0x39, 0x08, - 0xf4, 0x27, 0xae, 0x39, 0xc1, 0x02, 0x74, 0x6e, 0x31, 0x83, 0xb8, 0x93, 0xd6, 0xcc, 0x21, 0x3e, 0x4e, 0xa6, 0x83, - 0x41, 0x4c, 0x36, 0x00, 0xd2, 0x17, 0x2f, 0xac, 0x73, 0x50, 0xa1, 0x17, 0x64, 0xab, 0xee, 0xa2, 0x59, 0xb1, 0x57, - 0xed, 0x34, 0xef, 0xf7, 0xf3, 0x79, 0x39, 0x08, 0x1a, 0x15, 0x16, 0xc6, 0xfb, 0x8f, 0x36, 0xbf, 0x34, 0x3a, 0x69, - 0x82, 0x11, 0x6b, 0x4f, 0x51, 0xbd, 0xe2, 0x69, 0x46, 0x1b, 0xb7, 0x63, 0xa5, 0x7c, 0x01, 0x51, 0x3c, 0x30, 0x64, - 0xad, 0xbc, 0x3b, 0x07, 0xaf, 0xcb, 0x8d, 0x37, 0x47, 0x14, 0x60, 0x37, 0x85, 0x71, 0x52, 0x73, 0xd1, 0x45, 0x4d, - 0x3c, 0x83, 0x9d, 0xae, 0xde, 0x4a, 0xb4, 0x1a, 0xef, 0xc5, 0xbb, 0x66, 0xe3, 0x6f, 0xe4, 0x81, 0x2e, 0xf3, 0xe0, - 0x12, 0x10, 0x67, 0x0f, 0xe2, 0xea, 0x00, 0x4b, 0x3d, 0x08, 0x06, 0x16, 0x39, 0xa4, 0x5d, 0xad, 0x1e, 0x8a, 0x48, - 0x9d, 0xc7, 0x60, 0xc0, 0x64, 0x1a, 0x52, 0x93, 0x69, 0xaf, 0x50, 0x90, 0x36, 0xd6, 0x5a, 0x40, 0x1b, 0x0e, 0x8b, - 0x1d, 0xbb, 0x61, 0x77, 0xba, 0x75, 0x28, 0x94, 0x30, 0x90, 0x75, 0xdd, 0x3c, 0xd4, 0x1a, 0x9e, 0x08, 0x7a, 0x50, - 0x8d, 0xf6, 0xd3, 0x43, 0x79, 0xd2, 0x1e, 0x0b, 0x70, 0xd1, 0xc3, 0x97, 0x2f, 0x04, 0x5e, 0xb4, 0x77, 0x90, 0xe7, - 0xcc, 0xa7, 0xca, 0x07, 0xb1, 0xe1, 0x96, 0xe1, 0x43, 0xfb, 0xf8, 0x56, 0x20, 0x93, 0xba, 0xa3, 0xa9, 0xad, 0xdd, - 0xd1, 0x38, 0x26, 0xd0, 0x6f, 0xca, 0x51, 0xca, 0xc4, 0xd4, 0xb2, 0x64, 0x27, 0xbd, 0x5c, 0x79, 0x43, 0xa5, 0xec, - 0x64, 0xd9, 0xe6, 0xfc, 0xd2, 0x46, 0x42, 0xbf, 0xaf, 0xdd, 0x81, 0xf0, 0x8d, 0x5a, 0x6f, 0xc8, 0xcb, 0x86, 0x88, - 0xe5, 0x10, 0x33, 0x70, 0xbc, 0x90, 0xca, 0xb5, 0xbb, 0x68, 0xaa, 0xea, 0x76, 0xb6, 0x72, 0x41, 0x4b, 0xbc, 0x95, - 0x02, 0xab, 0x48, 0x9d, 0x5e, 0x4f, 0x25, 0xee, 0xfb, 0x28, 0xb6, 0x1f, 0x01, 0xdb, 0xd8, 0x38, 0x1a, 0x1b, 0xb7, - 0x88, 0x0d, 0xbe, 0x8a, 0x2a, 0x5a, 0x70, 0x80, 0xe0, 0x6e, 0x4b, 0x6a, 0x69, 0xe6, 0x10, 0xf7, 0x15, 0x0f, 0xd0, - 0xbe, 0x8b, 0x23, 0x4e, 0x05, 0xd8, 0xd6, 0xb5, 0xce, 0x59, 0x2d, 0x07, 0x6c, 0x26, 0x7a, 0xfe, 0x69, 0xd5, 0x48, - 0xc4, 0xb0, 0xca, 0x46, 0xca, 0x0a, 0xed, 0x41, 0xe9, 0x12, 0x2e, 0xbe, 0x00, 0x2f, 0xdb, 0xfb, 0x95, 0xdd, 0xe7, - 0x4b, 0xec, 0x1f, 0xe6, 0x55, 0x13, 0x3c, 0xf2, 0x1a, 0x6f, 0xef, 0x61, 0xe2, 0x73, 0xa5, 0x10, 0x5e, 0xa5, 0x34, - 0x94, 0x00, 0x0c, 0x92, 0xa0, 0x86, 0x2b, 0x6d, 0x9b, 0x41, 0x2a, 0x63, 0xd8, 0xdd, 0xea, 0xad, 0xfe, 0x4f, 0xab, - 0x70, 0x51, 0xc9, 0x62, 0x4c, 0x02, 0x9d, 0x53, 0x2d, 0x37, 0x81, 0x05, 0xcf, 0x77, 0xc9, 0x11, 0x28, 0xec, 0x04, - 0x70, 0x43, 0x09, 0xfb, 0x19, 0x6f, 0x43, 0x39, 0x7b, 0x69, 0x25, 0x4f, 0x6e, 0x5f, 0x52, 0x41, 0x13, 0x32, 0x15, - 0x76, 0xff, 0xb6, 0x36, 0xec, 0xf3, 0x50, 0x8e, 0xa4, 0xc0, 0xc5, 0x41, 0xe7, 0x00, 0xf6, 0x07, 0xb9, 0x8c, 0xcd, - 0x67, 0xd2, 0xef, 0xab, 0xf7, 0xcf, 0xf2, 0x2c, 0xf9, 0xb8, 0xf3, 0xde, 0xf0, 0x34, 0x4b, 0x06, 0x54, 0x22, 0xa6, - 0xd6, 0x55, 0x31, 0x5c, 0x6a, 0x17, 0xe3, 0x06, 0xc9, 0x88, 0xf7, 0x52, 0x87, 0x18, 0x31, 0xbe, 0xc8, 0x0e, 0x49, - 0xc9, 0xe9, 0xb2, 0xee, 0xec, 0xb9, 0x16, 0xcd, 0xa0, 0x31, 0xdc, 0x8e, 0xf7, 0x92, 0x5e, 0x01, 0x2a, 0x40, 0x74, - 0xcf, 0x02, 0xd7, 0xf0, 0xe6, 0x92, 0x68, 0x6c, 0xe9, 0x69, 0x4b, 0x34, 0xb0, 0x57, 0x26, 0x24, 0xd5, 0xc6, 0x01, - 0x16, 0xb1, 0xae, 0x3f, 0x86, 0x05, 0x00, 0xb5, 0x1a, 0xa4, 0x57, 0xfa, 0x92, 0x50, 0x95, 0x84, 0x60, 0x74, 0x22, - 0xe1, 0x65, 0x40, 0xe3, 0xcc, 0x24, 0x5a, 0xd8, 0xe0, 0x80, 0x3e, 0xaf, 0x4c, 0xa2, 0xb1, 0x21, 0x0f, 0x28, 0xb7, - 0x69, 0x00, 0x83, 0x0f, 0x92, 0x24, 0xfa, 0x6a, 0x69, 0x92, 0x40, 0x50, 0x82, 0xf2, 0x0d, 0xfa, 0x53, 0xe9, 0xf9, - 0x58, 0xfe, 0xe6, 0x1d, 0x4a, 0xdf, 0x87, 0x05, 0xc8, 0x14, 0x75, 0xc5, 0x34, 0x63, 0x27, 0x59, 0xb7, 0x31, 0x89, - 0xe7, 0x69, 0x77, 0x57, 0x28, 0x97, 0x2e, 0xf0, 0x2b, 0xcb, 0x10, 0xc7, 0xfa, 0x59, 0xbc, 0x62, 0xa7, 0x21, 0xd7, - 0x78, 0xe9, 0xcf, 0xe2, 0x15, 0xce, 0x10, 0xad, 0x5a, 0x09, 0x44, 0xf9, 0xaf, 0xda, 0xc0, 0x21, 0xee, 0x13, 0x0c, - 0x72, 0x51, 0x79, 0x0f, 0x04, 0xf2, 0xb6, 0x82, 0x88, 0x34, 0xb3, 0xeb, 0x30, 0x22, 0xd5, 0x4e, 0x92, 0xf9, 0xf2, - 0x07, 0x99, 0x09, 0xef, 0x1b, 0x78, 0x6c, 0x36, 0xcb, 0xa6, 0x98, 0x2f, 0x54, 0x30, 0x07, 0xf7, 0x89, 0x8a, 0x4b, - 0x51, 0xf9, 0x4f, 0xd8, 0x05, 0x2f, 0xc6, 0x83, 0xd7, 0x6b, 0x04, 0xd8, 0xaf, 0xfc, 0x27, 0x6f, 0xcc, 0xfe, 0xb2, - 0x6e, 0x7c, 0x99, 0x89, 0xf8, 0xc0, 0x47, 0x77, 0x94, 0x8f, 0xee, 0xbd, 0x4c, 0x7f, 0x34, 0xa0, 0x44, 0x46, 0x65, - 0xc5, 0x57, 0x2b, 0x9e, 0xce, 0xee, 0x92, 0x28, 0x1b, 0x55, 0x5c, 0xc0, 0xf4, 0x82, 0xe3, 0x5d, 0xb2, 0xbe, 0xc8, - 0x92, 0x57, 0x10, 0x7b, 0x60, 0x25, 0x15, 0x16, 0x3f, 0x2c, 0x33, 0xb5, 0x98, 0x85, 0xac, 0xa4, 0xe0, 0xc1, 0xec, - 0x26, 0x89, 0xfe, 0x5a, 0x7a, 0x48, 0x6a, 0x66, 0xca, 0x36, 0xb5, 0x23, 0xd4, 0xc6, 0xd7, 0x91, 0x6e, 0xb4, 0x05, - 0x00, 0xdc, 0xb3, 0x45, 0x1a, 0x49, 0x26, 0x86, 0x93, 0x9a, 0x71, 0x93, 0x5e, 0x60, 0x6a, 0x5c, 0xb3, 0x8a, 0x26, - 0xce, 0x42, 0x06, 0xf4, 0xfe, 0x80, 0x97, 0x83, 0xcf, 0x19, 0xdc, 0x7f, 0xd0, 0x1a, 0xb8, 0x3c, 0x2e, 0xfa, 0x7d, - 0x79, 0x5c, 0x6c, 0xb7, 0xe5, 0x49, 0xdc, 0xef, 0xcb, 0x93, 0xd8, 0xf0, 0x0f, 0x4a, 0xb1, 0x6d, 0xcc, 0x0d, 0x12, - 0x9a, 0x4b, 0x88, 0x5a, 0x34, 0x82, 0x3f, 0x34, 0xcb, 0xb9, 0x88, 0xf2, 0xe3, 0xa4, 0xdf, 0xef, 0x2d, 0x67, 0x62, - 0x90, 0x0f, 0x93, 0x28, 0x1f, 0x26, 0x9e, 0x13, 0xe2, 0xb7, 0x9e, 0x13, 0xa2, 0xa2, 0x81, 0x2b, 0x38, 0x33, 0x00, - 0x51, 0xc0, 0xa7, 0x7f, 0x54, 0xd7, 0x52, 0xe8, 0x5a, 0x62, 0x55, 0x4b, 0xa2, 0x2b, 0xa8, 0xd9, 0x4d, 0x11, 0x96, - 0x58, 0x0a, 0x5d, 0xb2, 0x3f, 0x96, 0xc0, 0x13, 0xe5, 0xbc, 0xda, 0x00, 0x03, 0x1b, 0xe1, 0x9d, 0xc3, 0x84, 0x93, - 0x58, 0xd7, 0x80, 0x76, 0xba, 0xa9, 0xe9, 0x25, 0x5d, 0xd1, 0x2b, 0xe4, 0x67, 0x2f, 0xc1, 0x60, 0xe9, 0x98, 0xe5, - 0xd3, 0xc1, 0xe0, 0x92, 0xac, 0x58, 0x39, 0x0f, 0xe3, 0x41, 0xb8, 0x9e, 0xe5, 0xc3, 0xcb, 0xe8, 0x92, 0x90, 0x2f, - 0x8a, 0x05, 0xed, 0xad, 0x46, 0xe5, 0xc7, 0x0c, 0xc2, 0xfb, 0xa5, 0xb3, 0x30, 0x33, 0x71, 0x3e, 0x56, 0xa3, 0x3b, - 0xba, 0x82, 0xf8, 0x35, 0x70, 0x23, 0x21, 0x11, 0x74, 0xe4, 0x8a, 0xae, 0xe8, 0x9a, 0x4a, 0x33, 0xc3, 0x18, 0xad, - 0xdb, 0x1e, 0x27, 0x09, 0x38, 0x26, 0xbb, 0xe2, 0xa3, 0xb1, 0x2a, 0xbc, 0xeb, 0x3b, 0x42, 0x7b, 0xbd, 0xc4, 0x0d, - 0xd2, 0x2f, 0xed, 0x41, 0x02, 0x46, 0x64, 0xa4, 0x06, 0xca, 0x8c, 0x8c, 0xa4, 0x66, 0x52, 0x71, 0x48, 0x62, 0x7f, - 0x48, 0xd4, 0x38, 0x24, 0xfe, 0x38, 0xe4, 0x7a, 0x1c, 0x90, 0xbb, 0x5f, 0xb2, 0x31, 0x4d, 0xd9, 0x98, 0xae, 0xd5, - 0xa8, 0xd0, 0x6b, 0x7a, 0xa1, 0xa9, 0xe3, 0x39, 0x7b, 0x0d, 0x07, 0xf6, 0x20, 0xcc, 0x67, 0xf1, 0xf0, 0x75, 0xf4, - 0x9a, 0x90, 0x2f, 0x24, 0xbd, 0x51, 0x97, 0x32, 0x08, 0x84, 0x78, 0x0d, 0xce, 0xa5, 0x2e, 0xd4, 0xc9, 0xb5, 0xd9, - 0x71, 0xf8, 0x74, 0xd5, 0x78, 0xba, 0x80, 0x88, 0x3e, 0x68, 0xa5, 0xd2, 0xef, 0x87, 0x97, 0xac, 0x9c, 0x9f, 0x87, - 0x63, 0x02, 0x38, 0x3c, 0x7a, 0x38, 0x2f, 0x47, 0x77, 0xf4, 0x72, 0x74, 0x4f, 0xc0, 0xc2, 0x6b, 0x3c, 0x5d, 0x1f, - 0xb3, 0x78, 0x3a, 0x18, 0xac, 0x91, 0xaa, 0xab, 0xdc, 0x6b, 0xb2, 0xa0, 0x97, 0x38, 0x11, 0x04, 0x18, 0xfa, 0x4c, - 0xac, 0x0d, 0x0d, 0x7f, 0xcd, 0xe0, 0xe3, 0x7b, 0x76, 0x39, 0xba, 0xa7, 0x77, 0xec, 0xf5, 0x76, 0x3c, 0x05, 0x66, - 0x6a, 0x35, 0x0b, 0xef, 0x8f, 0xaf, 0x66, 0x57, 0xec, 0x3e, 0xba, 0x3f, 0x81, 0x86, 0x5e, 0xb3, 0x7b, 0x04, 0x5c, - 0x4a, 0x1f, 0x2f, 0x07, 0xaf, 0xc9, 0xe1, 0x60, 0x90, 0x92, 0x28, 0xbc, 0x09, 0xbd, 0x56, 0xbe, 0xa6, 0xf7, 0x84, - 0xae, 0xd8, 0x1d, 0x8e, 0xc6, 0x15, 0xc3, 0x0f, 0x2e, 0xd8, 0x7d, 0x7d, 0x13, 0x7a, 0xbb, 0x39, 0x11, 0x9d, 0x20, - 0x46, 0xe8, 0x6b, 0xe0, 0x68, 0x96, 0x0b, 0x33, 0x01, 0x4f, 0xe6, 0x22, 0xa3, 0x45, 0xa1, 0x19, 0x88, 0xb3, 0x12, - 0x10, 0x4b, 0xa2, 0xee, 0x37, 0x1b, 0x9d, 0xc3, 0x72, 0xee, 0xf7, 0x7b, 0x95, 0xa1, 0x07, 0x88, 0x9c, 0xd9, 0x49, - 0x0f, 0x7a, 0x3e, 0x3d, 0xc0, 0x4f, 0xf4, 0xaa, 0x41, 0x9c, 0xcc, 0x5f, 0x96, 0xd1, 0xb7, 0x1e, 0x7d, 0xf8, 0xbe, - 0x9b, 0xf2, 0x88, 0xfc, 0xdf, 0xa7, 0x3c, 0x65, 0x1e, 0xbd, 0xae, 0x3c, 0x10, 0x3c, 0x6f, 0x4d, 0x2a, 0x8d, 0x44, - 0x35, 0x3a, 0x5f, 0xc5, 0xa0, 0x8d, 0x44, 0x6d, 0x83, 0x7e, 0x42, 0x0b, 0x2b, 0x88, 0x90, 0x73, 0xf4, 0x1c, 0x0c, - 0x52, 0x21, 0x54, 0x8e, 0x5a, 0x94, 0x68, 0x08, 0x92, 0xcb, 0x92, 0xab, 0xf0, 0x39, 0x84, 0xaa, 0xd3, 0xc7, 0x99, - 0x08, 0x1b, 0x7a, 0x1c, 0xfa, 0x00, 0xf0, 0x3f, 0xef, 0x90, 0x8b, 0x92, 0x5f, 0xe1, 0xd9, 0xdc, 0x26, 0x18, 0x05, - 0x4b, 0x44, 0x33, 0xb4, 0x0d, 0x62, 0x3f, 0x96, 0x04, 0xeb, 0x91, 0x34, 0x1e, 0x95, 0xe6, 0x88, 0xf0, 0xa3, 0xf8, - 0x28, 0x7a, 0x1a, 0x1b, 0x12, 0xc9, 0x91, 0x44, 0xf2, 0x01, 0x10, 0x4e, 0x82, 0xfe, 0xe2, 0xae, 0xc9, 0xae, 0x85, - 0xc4, 0xa0, 0x3f, 0x2d, 0x99, 0x96, 0xdd, 0xab, 0x1e, 0xfb, 0x8a, 0x20, 0x77, 0x4c, 0xff, 0xe9, 0xf5, 0xe1, 0x5f, - 0x4b, 0x9c, 0x41, 0xeb, 0xf9, 0xa2, 0x3a, 0x33, 0xf3, 0x06, 0x37, 0xf2, 0xba, 0xac, 0x5d, 0x97, 0x2f, 0xf9, 0x01, - 0xbf, 0xab, 0xb8, 0x48, 0xcb, 0x83, 0x9f, 0xaa, 0x36, 0x9e, 0x53, 0xb9, 0x5e, 0xb9, 0x38, 0x2b, 0xca, 0x38, 0xd5, - 0x93, 0xba, 0x18, 0x6b, 0xd8, 0x86, 0xdf, 0x23, 0xea, 0x4a, 0x5a, 0x8e, 0x9e, 0x52, 0xae, 0x9a, 0x29, 0x97, 0xeb, - 0x3c, 0xff, 0x71, 0x27, 0x15, 0xa7, 0xb8, 0x99, 0x82, 0x54, 0xa9, 0xe5, 0x02, 0xaa, 0xe7, 0xa8, 0xe5, 0x6e, 0x69, - 0x76, 0x80, 0x73, 0xdb, 0x54, 0x1f, 0x2b, 0xb3, 0x0b, 0x2f, 0xb9, 0x71, 0x7f, 0x32, 0x65, 0x58, 0x30, 0x0a, 0x6d, - 0x56, 0x5d, 0x69, 0xfb, 0x42, 0xeb, 0x34, 0x0c, 0x57, 0x7e, 0xbc, 0x80, 0x74, 0x01, 0xe3, 0x78, 0x51, 0x32, 0x31, - 0x6e, 0x8f, 0xde, 0x0a, 0xe2, 0x73, 0xb6, 0x02, 0xe9, 0xf7, 0x7b, 0xc2, 0xdb, 0x75, 0x1d, 0x6d, 0xf7, 0xc4, 0x29, - 0xa3, 0x72, 0x15, 0x8b, 0xef, 0xe2, 0x95, 0x81, 0x4c, 0x56, 0xc7, 0x63, 0x63, 0x4c, 0xa7, 0xff, 0x48, 0x42, 0xbf, - 0x10, 0x0a, 0x3e, 0xeb, 0xa5, 0x95, 0x27, 0xb7, 0x87, 0x65, 0x5c, 0xa3, 0x57, 0xe2, 0x4a, 0xf7, 0xcd, 0x48, 0x21, - 0xf5, 0xc8, 0x57, 0x4d, 0x01, 0xbd, 0x19, 0xfb, 0x66, 0x2a, 0xcc, 0xdb, 0x9e, 0x31, 0x57, 0x08, 0x56, 0xaa, 0xec, - 0xf6, 0x9d, 0x1a, 0x53, 0x31, 0x83, 0x29, 0xb6, 0x9d, 0xc5, 0xa4, 0x5b, 0xf9, 0xa7, 0x9d, 0xfb, 0x65, 0xde, 0xe1, - 0xae, 0xa8, 0xdf, 0x02, 0x17, 0x9a, 0x15, 0x65, 0xd5, 0x96, 0x0d, 0xdb, 0xc6, 0x1b, 0x59, 0x28, 0x36, 0xc0, 0xb2, - 0xe7, 0xbe, 0x85, 0x07, 0x88, 0x9b, 0x70, 0xcf, 0x2e, 0x6a, 0xb8, 0x31, 0x7c, 0x5e, 0x49, 0xbe, 0x2b, 0x8d, 0xb9, - 0xf4, 0xa9, 0xd2, 0xc4, 0x70, 0xb2, 0x18, 0x71, 0x91, 0x2e, 0xea, 0xcc, 0xae, 0x85, 0x4f, 0x78, 0x19, 0xce, 0xf9, - 0xc2, 0xe8, 0xa6, 0x74, 0xe9, 0x05, 0x8b, 0x75, 0xa7, 0x37, 0x2b, 0x8d, 0x95, 0x12, 0x71, 0x6b, 0x96, 0x09, 0x94, - 0xa5, 0xac, 0x95, 0xf0, 0xa6, 0x68, 0xd9, 0x4a, 0x1a, 0x79, 0xcf, 0x1c, 0xdc, 0xc7, 0xbe, 0x47, 0x4c, 0x64, 0x13, - 0x98, 0x14, 0x0d, 0x1d, 0xd0, 0xae, 0xba, 0xf0, 0xcd, 0xa8, 0x07, 0x83, 0xdc, 0x92, 0x44, 0xac, 0x20, 0xc5, 0x0a, - 0xd6, 0x35, 0x2b, 0xe6, 0xf9, 0x82, 0x5e, 0x32, 0x39, 0x4f, 0x17, 0x74, 0xc5, 0xe4, 0x7c, 0x8d, 0x37, 0xa1, 0x4b, - 0x38, 0x21, 0xc9, 0x26, 0x56, 0x0a, 0xd8, 0x4b, 0xbc, 0xbc, 0xe1, 0x99, 0xaa, 0x69, 0xd9, 0x95, 0xe2, 0x00, 0xe3, - 0x8b, 0x32, 0x0c, 0xcb, 0xe1, 0x25, 0x58, 0x4b, 0x1c, 0x86, 0xab, 0x39, 0x5f, 0xa8, 0xdf, 0x10, 0x75, 0x3e, 0x09, - 0x15, 0xbb, 0x60, 0xf7, 0x02, 0x99, 0x5e, 0xcf, 0xf9, 0x42, 0x8d, 0x84, 0x2e, 0xf8, 0xda, 0x1a, 0x9b, 0xc4, 0x9e, - 0xa0, 0x65, 0x16, 0xcf, 0xc7, 0x8b, 0x28, 0xae, 0x61, 0x19, 0x9e, 0xa9, 0x99, 0x69, 0xc9, 0x7f, 0x12, 0xb5, 0xa1, - 0x89, 0xbe, 0xc1, 0x2a, 0xf2, 0x87, 0xc7, 0x47, 0x97, 0x40, 0xc6, 0xce, 0xae, 0x64, 0xe6, 0x43, 0xdf, 0x47, 0x06, - 0xf7, 0xdc, 0x94, 0x33, 0xae, 0x82, 0x44, 0x19, 0xb8, 0x7b, 0x35, 0x4b, 0xc6, 0x5a, 0x84, 0xef, 0x1e, 0x15, 0x45, - 0x9f, 0x49, 0xd3, 0x80, 0xee, 0x23, 0xc1, 0x1c, 0xe8, 0xbd, 0x42, 0x87, 0xcb, 0x6a, 0x9b, 0x09, 0xf8, 0x8b, 0x04, - 0xf9, 0xad, 0xd0, 0xab, 0x1a, 0x83, 0x2a, 0xda, 0x45, 0x2c, 0xfd, 0xfb, 0x88, 0x1f, 0x65, 0xf3, 0x4f, 0x73, 0x8f, - 0x57, 0x12, 0x06, 0x3f, 0xa4, 0x66, 0x93, 0xcc, 0xdb, 0x2b, 0xf6, 0x1e, 0x3a, 0xea, 0x51, 0x6b, 0xbc, 0xaf, 0x5e, - 0x72, 0x0a, 0x31, 0x4a, 0x28, 0x3a, 0x09, 0x06, 0x70, 0xbb, 0x84, 0x14, 0x77, 0x83, 0xdd, 0x34, 0xaf, 0x79, 0x51, - 0x70, 0xb1, 0xae, 0xaa, 0xc0, 0x0f, 0x68, 0x38, 0x5f, 0xec, 0x86, 0x30, 0x1c, 0xd3, 0xd6, 0x35, 0x0c, 0xc2, 0x8c, - 0x61, 0x24, 0x04, 0xaf, 0x7f, 0xd1, 0x57, 0x34, 0x89, 0x57, 0xdf, 0xf2, 0xbf, 0x32, 0x5e, 0x28, 0x22, 0x0d, 0x22, - 0xa4, 0x6e, 0xe2, 0x1b, 0x99, 0x26, 0x05, 0x14, 0x02, 0x8c, 0x02, 0x2a, 0xb1, 0xa1, 0xa9, 0xf8, 0x5b, 0x2d, 0x3e, - 0xf8, 0xa9, 0xe9, 0x78, 0x34, 0xae, 0x5b, 0x9d, 0x51, 0x41, 0x67, 0xa0, 0x47, 0xad, 0xa8, 0xa7, 0x41, 0x2b, 0xc1, - 0x34, 0xd2, 0xbc, 0x75, 0x0f, 0x81, 0x57, 0xa6, 0xc5, 0x3b, 0x0f, 0xe8, 0xe6, 0xdc, 0x07, 0x4f, 0x1e, 0xd3, 0x73, - 0x87, 0x9e, 0x5c, 0xb1, 0x93, 0xaa, 0x87, 0xda, 0x7b, 0x33, 0x42, 0x41, 0xbf, 0x8f, 0x29, 0xd0, 0x8d, 0xa0, 0xf6, - 0xae, 0xee, 0x3f, 0x94, 0xbb, 0x1c, 0xbe, 0xe3, 0x2c, 0x37, 0x80, 0xa5, 0x22, 0x6b, 0x05, 0x1e, 0x05, 0xa8, 0x4b, - 0x65, 0x08, 0x5b, 0xcc, 0xe1, 0x50, 0xd9, 0xad, 0x5a, 0x0d, 0x25, 0x39, 0x2e, 0x47, 0xe0, 0x10, 0xba, 0x2e, 0x07, - 0xe5, 0x68, 0x99, 0x55, 0xef, 0xf1, 0xb7, 0x66, 0x1d, 0x92, 0x6c, 0x1f, 0xeb, 0xc0, 0x2d, 0xeb, 0x30, 0xfd, 0x68, - 0x90, 0x02, 0xd0, 0x64, 0x23, 0x70, 0x09, 0xc0, 0x7b, 0xfb, 0x8f, 0x08, 0xb5, 0x32, 0xdd, 0xcb, 0x58, 0xa8, 0xef, - 0x1b, 0x49, 0x50, 0x42, 0x33, 0xa1, 0x72, 0x2c, 0x05, 0xef, 0x3c, 0xd2, 0x39, 0xa9, 0x33, 0xf1, 0x1e, 0xc4, 0x69, - 0xe1, 0x03, 0x7b, 0x0b, 0x82, 0x73, 0x16, 0xf4, 0x1e, 0x6f, 0xb3, 0x5a, 0x6a, 0xa3, 0x07, 0x0a, 0xe0, 0x77, 0x83, - 0x7b, 0x04, 0xf9, 0x6a, 0x0c, 0xd7, 0x4a, 0xde, 0x86, 0x7c, 0x58, 0xd0, 0x23, 0x32, 0xb0, 0xcf, 0x62, 0x18, 0xd3, - 0x23, 0x72, 0x6c, 0x9f, 0xa5, 0x1b, 0xc0, 0x81, 0xd4, 0xa3, 0x4a, 0x8f, 0xa0, 0x41, 0xbf, 0xda, 0x16, 0x59, 0x92, - 0xf5, 0x43, 0x69, 0x14, 0x31, 0x50, 0x25, 0x88, 0xa8, 0xc5, 0xbf, 0x1e, 0xcc, 0x75, 0x8f, 0xb9, 0x40, 0x98, 0x83, - 0x01, 0x07, 0x71, 0x1b, 0x84, 0xe6, 0x80, 0xd9, 0xdc, 0x45, 0x82, 0xde, 0x5b, 0xc3, 0xcc, 0x8e, 0xfe, 0x70, 0x2b, - 0xc1, 0x37, 0x59, 0x6b, 0xd4, 0x79, 0x71, 0x08, 0x04, 0xc1, 0x9b, 0x42, 0x55, 0x7b, 0xd5, 0x03, 0x1b, 0x6f, 0xd5, - 0x8f, 0xed, 0x76, 0x3c, 0x15, 0xee, 0xda, 0x2f, 0x28, 0x9c, 0x7c, 0x4a, 0xfe, 0xf5, 0xde, 0x64, 0x70, 0x60, 0x64, - 0xf8, 0xd2, 0xdb, 0xbf, 0xf0, 0xb5, 0x96, 0xee, 0x89, 0x41, 0x49, 0x1e, 0x1f, 0x29, 0xfa, 0xb7, 0x57, 0x56, 0x3e, - 0xb5, 0xd3, 0xbf, 0xdd, 0x9a, 0xf5, 0x79, 0x3c, 0x9a, 0x6c, 0xb7, 0xbd, 0xb8, 0xd2, 0x1e, 0x6b, 0x7a, 0x41, 0xa0, - 0x73, 0x3d, 0x39, 0x3c, 0x82, 0xa8, 0x08, 0xcd, 0xb8, 0x9b, 0x65, 0x43, 0x22, 0xe3, 0xc7, 0xe9, 0x2c, 0x1b, 0x82, - 0x1d, 0xee, 0x45, 0x25, 0x2e, 0x47, 0xad, 0x0d, 0x4e, 0xcf, 0x93, 0x10, 0x42, 0x39, 0x60, 0x65, 0x77, 0xea, 0xcf, - 0xbd, 0x32, 0x13, 0x52, 0x93, 0xd5, 0xed, 0x94, 0xee, 0x61, 0x9a, 0x1f, 0x98, 0x11, 0x1c, 0x70, 0x6f, 0x7f, 0xd5, - 0x1f, 0xc3, 0x24, 0xd3, 0xe4, 0x14, 0xc9, 0x2f, 0xd2, 0x53, 0x48, 0xda, 0xa1, 0xa7, 0x8a, 0x00, 0x4e, 0xa8, 0xfd, - 0x18, 0x7e, 0xc3, 0xb8, 0x7f, 0xdb, 0x7c, 0xed, 0xa6, 0x22, 0x7a, 0x42, 0xb1, 0x4c, 0x4d, 0x4e, 0x93, 0xac, 0x48, - 0x20, 0x6a, 0xa3, 0x6a, 0x46, 0xf4, 0x95, 0x8b, 0xf9, 0xa8, 0x08, 0x9f, 0x57, 0xeb, 0xff, 0x0c, 0xe1, 0x33, 0x0a, - 0x37, 0x80, 0xcb, 0x2b, 0xae, 0x2e, 0xc2, 0xa7, 0x4f, 0xe8, 0xc1, 0xe4, 0xeb, 0x23, 0x7a, 0x70, 0xf4, 0xd5, 0x53, - 0x02, 0xb0, 0x68, 0x57, 0x17, 0xe1, 0xd1, 0xd3, 0xa7, 0xf4, 0xe0, 0x9b, 0x6f, 0xe8, 0xc1, 0xe4, 0xab, 0xa3, 0x46, - 0xda, 0xe4, 0xe9, 0x37, 0xf4, 0xe0, 0xeb, 0x27, 0x8d, 0xb4, 0xa3, 0xf1, 0x53, 0x7a, 0xf0, 0xf7, 0xaf, 0x4d, 0xda, - 0xdf, 0x20, 0xdb, 0x37, 0x47, 0xf8, 0x9f, 0x49, 0x9b, 0x3c, 0xfd, 0x8a, 0x1e, 0x4c, 0xc6, 0x50, 0xc9, 0x53, 0x57, - 0xc9, 0x78, 0x02, 0x1f, 0x7f, 0x05, 0xff, 0xfd, 0x8d, 0x04, 0x0b, 0x5a, 0x49, 0x96, 0x0b, 0xd4, 0x9f, 0xa1, 0x88, - 0x13, 0x55, 0x13, 0x09, 0x0f, 0x31, 0xb3, 0xfa, 0x26, 0x0e, 0x03, 0xe2, 0xd2, 0xa1, 0x20, 0x7a, 0x30, 0x1e, 0x3d, - 0x25, 0x81, 0x0f, 0x4f, 0x77, 0xeb, 0x83, 0x8c, 0xe5, 0x62, 0x9e, 0x7d, 0x91, 0x9b, 0xd8, 0x0a, 0x1e, 0x80, 0xd5, - 0x47, 0x3f, 0x57, 0x25, 0xe7, 0xd9, 0x17, 0x95, 0xdc, 0xcd, 0xf5, 0x6b, 0x0b, 0x50, 0xde, 0x5f, 0xb5, 0xec, 0xb6, - 0x50, 0xa1, 0xd3, 0x5a, 0xa3, 0xcf, 0x3e, 0x62, 0xfa, 0x60, 0xe0, 0xdd, 0xb0, 0xff, 0xb1, 0x53, 0x4e, 0xeb, 0x1b, - 0x8d, 0x42, 0x8d, 0xca, 0x43, 0xc2, 0x4e, 0xa0, 0xe8, 0xc1, 0x00, 0x78, 0x02, 0x0f, 0xf7, 0xed, 0xdf, 0x2c, 0xe3, - 0x63, 0x47, 0x19, 0x3f, 0xa1, 0x0c, 0x01, 0x8d, 0x7a, 0x98, 0xdd, 0xf4, 0xb0, 0xd1, 0xad, 0x5e, 0xb2, 0x54, 0x27, - 0x53, 0xd3, 0x33, 0xd8, 0xd7, 0xba, 0x96, 0x07, 0x46, 0x14, 0x2d, 0x2f, 0x0f, 0x52, 0x3e, 0xab, 0xd8, 0x3f, 0x96, - 0xa8, 0xde, 0x8a, 0x1a, 0x6f, 0x64, 0x36, 0xab, 0xd8, 0x77, 0xe6, 0x0d, 0x70, 0x33, 0xec, 0x57, 0xf5, 0xe4, 0x07, - 0xce, 0xe0, 0xd2, 0xb6, 0x47, 0x99, 0x18, 0x01, 0x56, 0x40, 0x06, 0x0e, 0x3c, 0x00, 0x3a, 0xe8, 0x8f, 0xf6, 0x76, - 0xab, 0x52, 0x9a, 0x7d, 0xb6, 0x30, 0x80, 0x86, 0x79, 0x9b, 0xb8, 0xb2, 0x7f, 0x6b, 0xc8, 0x4b, 0x50, 0xb8, 0xd5, - 0x2c, 0x6f, 0xa7, 0x30, 0x84, 0x10, 0xfc, 0x61, 0xc9, 0x00, 0x70, 0x20, 0xc0, 0x60, 0xac, 0x65, 0x40, 0xcd, 0x96, - 0x8f, 0x36, 0x5c, 0xa9, 0x27, 0x81, 0x33, 0xb8, 0x94, 0x45, 0xc2, 0xdf, 0x6a, 0xb1, 0x3f, 0x5a, 0x3f, 0xfa, 0xbe, - 0x3d, 0x1e, 0xac, 0x7d, 0x8f, 0x8f, 0xf4, 0x67, 0x8d, 0xeb, 0xc0, 0xa6, 0xe5, 0x1b, 0x2f, 0x6a, 0x2b, 0xf1, 0x28, - 0x81, 0x37, 0x30, 0x11, 0x29, 0x0c, 0x52, 0x2d, 0x70, 0x0c, 0xca, 0x1b, 0x0b, 0xb1, 0x54, 0x5d, 0xdd, 0x60, 0x0b, - 0x22, 0x43, 0xf0, 0x70, 0xfb, 0x6d, 0xa9, 0x02, 0x47, 0xf5, 0xfb, 0x5c, 0xfa, 0x6e, 0x4f, 0xc6, 0x8e, 0x1c, 0xa7, - 0x7e, 0x2a, 0x1c, 0xfc, 0x37, 0xa9, 0x6b, 0x63, 0xb9, 0x92, 0x32, 0xcb, 0xb2, 0xb0, 0x93, 0x50, 0xcb, 0x3d, 0x2a, - 0x0f, 0x92, 0x2f, 0xe4, 0x10, 0xc9, 0x02, 0xa3, 0x50, 0x90, 0xe1, 0x84, 0x8a, 0xd1, 0x5a, 0x94, 0xcb, 0xec, 0xb2, - 0x0a, 0x37, 0x4a, 0xa1, 0xcc, 0x29, 0xfa, 0x76, 0x83, 0x03, 0x09, 0x89, 0xb2, 0xf2, 0x4d, 0xfc, 0x26, 0x44, 0xb0, - 0x3a, 0xae, 0x6d, 0xa1, 0xb8, 0xb7, 0x3f, 0x79, 0xda, 0xc5, 0x1f, 0x19, 0x17, 0x50, 0x17, 0x8b, 0x69, 0x38, 0xb1, - 0xb1, 0x6f, 0xdc, 0x17, 0x56, 0xd3, 0x03, 0x50, 0xdf, 0xa5, 0x12, 0x23, 0xa8, 0xaf, 0x8c, 0x7d, 0x6c, 0x8f, 0x31, - 0x39, 0x83, 0x58, 0xc3, 0x2a, 0x67, 0xa6, 0xfa, 0x46, 0xd8, 0x09, 0x00, 0x37, 0x42, 0x6b, 0x74, 0x64, 0x92, 0x2a, - 0xc4, 0xf3, 0x52, 0x85, 0x6f, 0xcd, 0x08, 0x1d, 0x83, 0x37, 0x95, 0x6d, 0x64, 0x26, 0x7d, 0xc1, 0xa0, 0x39, 0xb6, - 0x75, 0x14, 0x56, 0x5b, 0x59, 0x76, 0x02, 0x70, 0x03, 0xd9, 0xb1, 0xb9, 0x78, 0xce, 0xaa, 0x79, 0xb6, 0x88, 0x4c, - 0x50, 0xc0, 0xa5, 0xb0, 0x0c, 0xda, 0x9b, 0x3d, 0xb2, 0x1d, 0x87, 0xd0, 0x0d, 0xf7, 0x11, 0x8c, 0xa7, 0xdd, 0x14, - 0xac, 0x20, 0x1a, 0x21, 0x1e, 0x66, 0xcc, 0xe2, 0x7b, 0xa5, 0x29, 0x4f, 0x55, 0x4b, 0x20, 0x70, 0x14, 0x42, 0x5d, - 0xec, 0x1a, 0x25, 0xb8, 0x4c, 0x8d, 0x60, 0x06, 0x3b, 0x76, 0xa4, 0xb6, 0x4b, 0xce, 0xe9, 0x50, 0x4d, 0x69, 0xa9, - 0xa7, 0x54, 0xfb, 0x1a, 0x8a, 0x79, 0x89, 0x1e, 0x7a, 0xe0, 0x7a, 0xa0, 0x1d, 0xf2, 0x4a, 0x3a, 0x31, 0x11, 0x74, - 0x5a, 0x6d, 0xc2, 0xce, 0x8d, 0x74, 0xcb, 0x6a, 0xe4, 0x1d, 0x43, 0xb3, 0x23, 0x5e, 0xf8, 0x81, 0xba, 0x00, 0x22, - 0x64, 0x6f, 0x8b, 0xcc, 0x11, 0xcd, 0xb2, 0xf2, 0x25, 0x94, 0xc5, 0x11, 0x5b, 0x57, 0xc0, 0xb5, 0x14, 0x4c, 0x2e, - 0x79, 0xc4, 0x53, 0x44, 0x04, 0x3c, 0x55, 0xda, 0xf5, 0x9d, 0x96, 0x10, 0x9a, 0xa5, 0x40, 0xdc, 0x5c, 0x14, 0xe7, - 0xda, 0x06, 0xb2, 0x00, 0xfa, 0xf6, 0x63, 0x76, 0xed, 0x85, 0x83, 0xdd, 0x5c, 0x67, 0xe2, 0x39, 0xbf, 0xcc, 0x04, - 0x4f, 0x11, 0xec, 0xea, 0xce, 0x3c, 0x70, 0xc7, 0xb6, 0x81, 0xe5, 0xdb, 0xb7, 0xb0, 0x60, 0xca, 0x50, 0x2b, 0x25, - 0x32, 0x11, 0x09, 0xc8, 0xec, 0x33, 0x77, 0xaf, 0x33, 0xf1, 0x3a, 0xbe, 0x03, 0x6f, 0x8a, 0x06, 0x3f, 0x3d, 0xba, - 0xc0, 0x2f, 0x11, 0x49, 0x14, 0x62, 0xd8, 0x62, 0x44, 0x2c, 0x44, 0x8e, 0x1d, 0x13, 0xca, 0x95, 0xa0, 0xb5, 0x35, - 0x04, 0x5e, 0xfc, 0x69, 0xd5, 0xbd, 0xeb, 0x4c, 0x18, 0xfb, 0x8c, 0xeb, 0xf8, 0x8e, 0x95, 0x0a, 0xcc, 0x02, 0xe3, - 0xdc, 0xb7, 0xa5, 0x24, 0xd7, 0x99, 0x30, 0x02, 0x92, 0xeb, 0xf8, 0x8e, 0x36, 0x65, 0x1c, 0xda, 0x8a, 0xce, 0x8b, - 0xf3, 0xbb, 0x3b, 0xfc, 0x12, 0x43, 0xad, 0x8c, 0xfb, 0x7d, 0x90, 0x98, 0x49, 0xdb, 0x94, 0x99, 0x8c, 0xa4, 0x46, - 0x0b, 0xa9, 0x28, 0x1f, 0x4c, 0xc8, 0xee, 0x4a, 0xb5, 0x8c, 0xa8, 0xfd, 0x2a, 0x14, 0xb3, 0x71, 0x34, 0x21, 0x74, - 0xd2, 0xb1, 0xde, 0x4d, 0x6b, 0x21, 0xd3, 0xe8, 0x69, 0xe4, 0xf9, 0x74, 0x16, 0xac, 0x9a, 0x16, 0xc7, 0x8c, 0x4f, - 0x8b, 0xc1, 0x80, 0x68, 0x97, 0xc2, 0x0d, 0xd6, 0x03, 0xa6, 0x34, 0x2e, 0xde, 0x9a, 0x69, 0xf5, 0x4b, 0xa9, 0x42, - 0xd2, 0x7b, 0x06, 0x24, 0x99, 0x74, 0xc1, 0x6e, 0x41, 0xa2, 0xe8, 0xf9, 0xdf, 0xa9, 0x2d, 0xb8, 0xeb, 0xc1, 0xd8, - 0x8c, 0xee, 0xeb, 0x19, 0xff, 0xa1, 0xb6, 0x05, 0x51, 0x9f, 0x4a, 0xd6, 0xeb, 0x48, 0x54, 0x21, 0x17, 0xe1, 0x67, - 0x47, 0x43, 0x0c, 0x51, 0xed, 0xb1, 0x40, 0xac, 0xaf, 0x2f, 0x78, 0x81, 0xd3, 0xcf, 0xdc, 0xe5, 0x0a, 0xb6, 0x05, - 0xad, 0x0c, 0x8d, 0x7a, 0x13, 0xbf, 0x89, 0xec, 0x65, 0x41, 0x17, 0xf9, 0x1c, 0x85, 0xac, 0x79, 0x18, 0x56, 0xc3, - 0xf6, 0x20, 0x92, 0xc3, 0xf6, 0x24, 0x34, 0x1a, 0x03, 0x0b, 0x64, 0x87, 0x46, 0xe0, 0x22, 0xb4, 0xf2, 0xb7, 0x63, - 0x70, 0xe1, 0xb2, 0x88, 0x2c, 0x43, 0x1d, 0xbf, 0xa9, 0xdd, 0x04, 0xd5, 0x2b, 0x74, 0x9a, 0xc2, 0xaa, 0x94, 0x49, - 0x3e, 0xfc, 0x7a, 0x29, 0x0b, 0xcc, 0xe4, 0x75, 0xd9, 0xa3, 0xaf, 0xed, 0xf6, 0x0e, 0x4c, 0xc1, 0xba, 0x4f, 0xde, - 0xd7, 0x8f, 0x3b, 0x7b, 0x02, 0x46, 0xb1, 0x2a, 0x47, 0x53, 0x48, 0xa9, 0x7d, 0x50, 0xea, 0x8f, 0xe1, 0x52, 0x68, - 0x8e, 0xdd, 0x02, 0x26, 0x01, 0xfb, 0x0c, 0xa9, 0x1e, 0xd3, 0x8e, 0x7d, 0x8e, 0x36, 0xb0, 0x24, 0xe0, 0xf0, 0x8f, - 0x32, 0x59, 0xfb, 0x57, 0x77, 0x91, 0x36, 0x43, 0xb6, 0xcc, 0x17, 0xc0, 0xe7, 0xc3, 0xae, 0x8d, 0x4a, 0x94, 0x4d, - 0x44, 0x92, 0xc2, 0x96, 0xc7, 0x20, 0xed, 0x51, 0x4c, 0x57, 0x05, 0x4f, 0x32, 0x94, 0x52, 0x24, 0xda, 0x27, 0x38, - 0x87, 0x37, 0xb8, 0x1f, 0x55, 0x40, 0x78, 0x15, 0x72, 0x3a, 0x4a, 0xa9, 0xb6, 0x80, 0x51, 0xd4, 0x03, 0x44, 0x79, - 0x19, 0xc8, 0xf1, 0xb6, 0xdb, 0x09, 0x5d, 0xb1, 0xe5, 0x70, 0x42, 0x91, 0x94, 0x5c, 0x61, 0xb9, 0xd7, 0xa0, 0xf3, - 0xb8, 0x60, 0xbd, 0x17, 0x80, 0x45, 0x70, 0x0e, 0x7f, 0x63, 0x42, 0x6f, 0xe0, 0x6f, 0x4e, 0xe8, 0x6b, 0x16, 0x5e, - 0x0f, 0xaf, 0xc8, 0x61, 0x98, 0x0e, 0x26, 0x4a, 0x30, 0x76, 0xcf, 0x96, 0x65, 0xa8, 0x12, 0x57, 0x87, 0x97, 0xe4, - 0xf1, 0x25, 0xbd, 0xa3, 0xb7, 0xf4, 0x8c, 0xbe, 0x05, 0xc2, 0x7f, 0x7f, 0x3c, 0xe1, 0xc3, 0xc9, 0x93, 0x7e, 0xbf, - 0x77, 0xd1, 0xef, 0xf7, 0xce, 0x8d, 0x01, 0x85, 0xde, 0x45, 0x57, 0x35, 0xd5, 0xbf, 0xae, 0xeb, 0xc5, 0xf4, 0xad, - 0xda, 0xb8, 0x09, 0xcf, 0xf2, 0xf0, 0xfa, 0xf0, 0x9e, 0x0c, 0xf1, 0xf1, 0x32, 0x97, 0xb2, 0x08, 0xaf, 0x0e, 0xef, - 0x09, 0x7d, 0x7b, 0x02, 0x7a, 0x53, 0xac, 0xef, 0xed, 0xe3, 0x7b, 0x5d, 0x1b, 0xa1, 0x2f, 0xc2, 0x04, 0xb6, 0xc9, - 0x1d, 0xb3, 0x77, 0xed, 0xc9, 0x18, 0x62, 0x99, 0xdc, 0x7b, 0xe5, 0xdd, 0x3f, 0xbe, 0x23, 0x87, 0x77, 0xe0, 0x29, - 0x6a, 0xc9, 0xdf, 0x2c, 0xbc, 0x65, 0xad, 0x1a, 0x1e, 0xdf, 0xd3, 0xb3, 0x56, 0x23, 0x1e, 0xdf, 0x93, 0x28, 0xbc, - 0x65, 0x57, 0xf4, 0x8c, 0x5d, 0x13, 0x7a, 0xd1, 0xef, 0x9f, 0xf7, 0xfb, 0xb2, 0xdf, 0xff, 0x47, 0x1c, 0x86, 0xf1, - 0xb0, 0x20, 0x87, 0x92, 0xde, 0x1f, 0x4e, 0xf8, 0x57, 0x64, 0x16, 0xea, 0xe6, 0xab, 0x05, 0x67, 0x55, 0xde, 0x2a, - 0xd7, 0x3d, 0x05, 0x6b, 0x85, 0x7b, 0xa6, 0x9e, 0xde, 0xd2, 0x5b, 0x56, 0xd0, 0x33, 0x16, 0x93, 0xe8, 0x06, 0x5a, - 0x71, 0x31, 0x2b, 0xa2, 0x5b, 0x7a, 0xc6, 0xce, 0x67, 0x71, 0x74, 0x46, 0xdf, 0xb2, 0x7c, 0x38, 0x81, 0xbc, 0x67, - 0xc3, 0x5b, 0x72, 0xf8, 0x96, 0x44, 0xe1, 0x5b, 0xfd, 0xfb, 0x9e, 0x5e, 0xf1, 0xf0, 0x2d, 0xf5, 0xaa, 0x79, 0x4b, - 0x4c, 0xf5, 0x8d, 0xda, 0xdf, 0x92, 0xc8, 0x1f, 0xcc, 0xb7, 0xd6, 0x9e, 0xe6, 0x91, 0xa3, 0x8d, 0x69, 0x19, 0x82, - 0xbe, 0xb9, 0x0c, 0x6f, 0x09, 0x99, 0x36, 0xc7, 0x0e, 0x06, 0x74, 0xf6, 0x28, 0x4a, 0x08, 0xbd, 0xf5, 0x4b, 0xbd, - 0xc5, 0x31, 0x34, 0x23, 0xa4, 0xd2, 0xce, 0x30, 0x0d, 0xd7, 0xc1, 0x2b, 0x0d, 0xd6, 0x71, 0xd1, 0xef, 0x87, 0xeb, - 0x7e, 0x1f, 0x22, 0xdd, 0x17, 0x33, 0x13, 0xdb, 0xcd, 0x91, 0x4d, 0x7a, 0x0b, 0xda, 0xff, 0x57, 0x83, 0x01, 0x74, - 0xc6, 0x2b, 0x29, 0xbc, 0x1d, 0xbc, 0x7a, 0x7c, 0x4f, 0x54, 0x1d, 0x05, 0x15, 0x32, 0x2c, 0xe8, 0x6b, 0x9a, 0x01, - 0xe0, 0xd7, 0xab, 0xc1, 0x80, 0x44, 0xe6, 0x33, 0x32, 0x7d, 0x75, 0xfc, 0x76, 0x3a, 0x18, 0xbc, 0x32, 0xdb, 0xe4, - 0x2f, 0xb6, 0xa7, 0x14, 0x58, 0x7f, 0xe7, 0xfd, 0xfe, 0x5f, 0x27, 0x31, 0xb9, 0x28, 0x78, 0xfc, 0x71, 0xda, 0x6c, - 0xcb, 0x5f, 0x2e, 0xaa, 0xda, 0x79, 0xbf, 0xbf, 0xee, 0xf7, 0xcf, 0x00, 0xbb, 0x68, 0xe6, 0x7c, 0x3d, 0x41, 0xda, - 0x32, 0x77, 0x14, 0x49, 0x93, 0x1c, 0x1a, 0x43, 0xdb, 0x62, 0xd5, 0xb6, 0x59, 0x47, 0x06, 0x16, 0x47, 0xcd, 0x8a, - 0xe2, 0x9a, 0x44, 0x61, 0xef, 0x7c, 0xbb, 0x3d, 0x63, 0x8c, 0xc5, 0x04, 0xa4, 0x1f, 0xfe, 0xeb, 0xb3, 0xba, 0x11, - 0x43, 0x4c, 0x48, 0x64, 0x36, 0x37, 0x4b, 0x7b, 0x08, 0x44, 0x1c, 0x36, 0xfd, 0x7b, 0x73, 0x2f, 0x17, 0xb5, 0xe3, - 0x5b, 0x7f, 0x03, 0x10, 0x22, 0xc9, 0x42, 0x3e, 0xc3, 0x31, 0x28, 0x33, 0x00, 0x32, 0x8f, 0xd4, 0xcc, 0x4b, 0x00, - 0x01, 0x26, 0xdb, 0xed, 0x68, 0x3c, 0x9e, 0xd0, 0x82, 0x8d, 0xfe, 0xf6, 0xf4, 0x71, 0xf5, 0x38, 0x0c, 0x82, 0x41, - 0x46, 0x5a, 0x7a, 0x0a, 0xbb, 0x58, 0xab, 0x43, 0x30, 0x82, 0xd7, 0xec, 0xe3, 0x4d, 0xf6, 0xd9, 0xec, 0x23, 0x12, - 0xd6, 0x06, 0xe3, 0xc8, 0x45, 0xda, 0xd2, 0xdb, 0xed, 0x61, 0x30, 0xb9, 0x48, 0x3f, 0xc1, 0x76, 0xfa, 0xfc, 0x9b, - 0x07, 0xe3, 0x09, 0x07, 0xa3, 0xbb, 0x28, 0xe8, 0x33, 0x6d, 0xbb, 0xad, 0xfc, 0x4b, 0xe0, 0x1b, 0x4c, 0x05, 0x1d, - 0x9b, 0x65, 0xe1, 0x06, 0x15, 0x51, 0x47, 0xcb, 0xa0, 0xaa, 0x95, 0xed, 0x1c, 0x50, 0x4b, 0xac, 0xca, 0xc4, 0x2d, - 0x30, 0x0c, 0x19, 0xea, 0x72, 0x4f, 0xab, 0xdf, 0x78, 0x21, 0x0d, 0x7c, 0x86, 0x13, 0x11, 0x7a, 0xdc, 0x1a, 0xf7, - 0xb9, 0x35, 0xf1, 0x09, 0x6e, 0xad, 0x44, 0x12, 0x6b, 0x60, 0x49, 0xcd, 0xe5, 0x28, 0x61, 0x27, 0x25, 0xe3, 0xb3, - 0x32, 0x4a, 0x68, 0x0c, 0x0f, 0x92, 0x89, 0x99, 0x8c, 0x12, 0xb4, 0x4f, 0x74, 0x11, 0x06, 0xff, 0x04, 0xcc, 0x7e, - 0x9a, 0xc3, 0x5f, 0x49, 0xa6, 0xc9, 0x31, 0x04, 0x84, 0x38, 0x1e, 0xcf, 0xe2, 0x70, 0x4c, 0xa2, 0xe4, 0x04, 0x9e, - 0xe0, 0xbf, 0x22, 0x1c, 0x93, 0x5a, 0xdf, 0x61, 0xa4, 0xba, 0xdc, 0x26, 0x0c, 0xe0, 0xca, 0xc6, 0xb3, 0x49, 0x64, - 0xa5, 0xbb, 0xf2, 0xf1, 0x68, 0xfc, 0x94, 0x4c, 0xe3, 0x50, 0x0e, 0x12, 0x42, 0xc1, 0xbb, 0x37, 0x2c, 0x87, 0x89, - 0x86, 0x67, 0x03, 0x36, 0xaf, 0x74, 0x6c, 0x9e, 0x84, 0x13, 0x10, 0x86, 0x09, 0x39, 0xd6, 0x3d, 0x48, 0x29, 0xfa, - 0x3c, 0xc7, 0x7e, 0xea, 0x23, 0x08, 0xb3, 0xa3, 0x96, 0x8a, 0xaf, 0x00, 0xe8, 0x12, 0x07, 0x87, 0xda, 0x33, 0x5f, - 0xcc, 0xc2, 0xd2, 0xa3, 0x52, 0xa6, 0xba, 0x43, 0xd1, 0xa0, 0xfc, 0xa6, 0x41, 0x87, 0x82, 0x0c, 0x26, 0xb4, 0x3c, - 0x99, 0xf0, 0xaf, 0x20, 0x80, 0x47, 0x23, 0xe2, 0x97, 0xc2, 0x89, 0x81, 0xf0, 0x2a, 0xc8, 0x40, 0xa5, 0xb5, 0x6a, - 0xcc, 0xc8, 0x56, 0x7c, 0x00, 0x61, 0x52, 0x0e, 0x6e, 0xe5, 0x3a, 0x4f, 0x21, 0x2a, 0xd8, 0x3a, 0xaf, 0x0e, 0xae, - 0xc0, 0x92, 0x3d, 0xae, 0x20, 0x4e, 0xd8, 0x7a, 0x05, 0xd8, 0xb9, 0x8f, 0x36, 0x65, 0x7d, 0xa0, 0xbe, 0x3b, 0xc0, - 0x96, 0xc3, 0xab, 0x4a, 0x1e, 0x4c, 0xc6, 0xe3, 0xf1, 0xe8, 0x77, 0x38, 0x3a, 0x80, 0xd0, 0x92, 0xc8, 0xf0, 0xc9, - 0x00, 0x8d, 0xbb, 0xae, 0xb8, 0x37, 0x2e, 0x14, 0x65, 0xa5, 0x93, 0x09, 0x01, 0xf1, 0xb3, 0xe9, 0x1b, 0xec, 0x2b, - 0xae, 0xe3, 0x9f, 0xec, 0x7e, 0x62, 0x56, 0xb4, 0x5a, 0xa9, 0xa3, 0x77, 0x6f, 0xcf, 0x5e, 0x7d, 0x78, 0xf5, 0xcb, - 0x8b, 0xf3, 0x57, 0x6f, 0x5e, 0xbe, 0x7a, 0xf3, 0xea, 0xc3, 0xbf, 0x1e, 0x60, 0xb0, 0x7d, 0x5b, 0x11, 0x3b, 0xf6, - 0xde, 0x3d, 0xc6, 0xab, 0xc5, 0x17, 0xce, 0x1e, 0xb9, 0x5b, 0x2c, 0xc0, 0x26, 0x18, 0x6e, 0x41, 0x50, 0xcd, 0x68, - 0x54, 0xfa, 0x9e, 0x80, 0x8c, 0x46, 0x85, 0x6c, 0x3c, 0xac, 0xd8, 0x0a, 0xb9, 0x78, 0xc7, 0x70, 0xf0, 0x91, 0xfd, - 0xad, 0x38, 0x13, 0x6e, 0x47, 0x5b, 0xb3, 0x22, 0xe0, 0xf3, 0xb5, 0x16, 0x95, 0xc7, 0x85, 0xa8, 0xbd, 0x6d, 0x9f, - 0x43, 0x42, 0x3d, 0x22, 0xd7, 0xc1, 0xfb, 0x36, 0xc8, 0x1e, 0x1f, 0x79, 0x4f, 0xca, 0x33, 0xd4, 0xe7, 0x68, 0xf8, - 0xa8, 0xf1, 0x8c, 0x4e, 0xcc, 0xb5, 0xd1, 0xa1, 0x9e, 0x17, 0xb0, 0xbf, 0x95, 0x18, 0x1b, 0xa2, 0x3d, 0xa4, 0x88, - 0xf5, 0xe1, 0x74, 0xbf, 0xbb, 0x37, 0xa3, 0xef, 0xe0, 0xf8, 0x51, 0xaa, 0x09, 0xa4, 0x45, 0x81, 0xd2, 0x95, 0x21, - 0xb7, 0x3d, 0x0b, 0x0b, 0xf3, 0x33, 0x6c, 0x10, 0x40, 0x7b, 0xd9, 0xb1, 0x24, 0xd0, 0x2c, 0x5e, 0xeb, 0xfa, 0xe7, - 0xe5, 0xcb, 0x44, 0x3b, 0x5f, 0x7c, 0x07, 0x21, 0x86, 0xfd, 0x2b, 0x42, 0x63, 0xc2, 0xdd, 0x24, 0xbb, 0x4b, 0x8b, - 0xb9, 0x57, 0x5d, 0xc7, 0x78, 0xdc, 0xed, 0xb9, 0x52, 0x34, 0x6f, 0x5d, 0x60, 0x0f, 0xd4, 0xbc, 0x8e, 0x97, 0x2c, - 0x04, 0x6c, 0xc6, 0x43, 0xbb, 0x48, 0x9c, 0xdf, 0x3b, 0x9d, 0x90, 0xc3, 0xa3, 0x29, 0x1f, 0xb2, 0x92, 0x8a, 0x01, - 0x2b, 0xeb, 0x1d, 0x6a, 0xce, 0xdb, 0x84, 0x5c, 0xec, 0xd2, 0x70, 0x31, 0xe4, 0x0f, 0x5d, 0x92, 0x3e, 0xf0, 0x86, - 0x43, 0xb5, 0x6d, 0x2e, 0x86, 0x34, 0xe5, 0x74, 0x97, 0xca, 0x80, 0x10, 0xe9, 0x3a, 0xae, 0x48, 0xad, 0x8f, 0xaa, - 0xd4, 0x49, 0x3a, 0x6e, 0xb2, 0xcd, 0x27, 0x2e, 0xd9, 0xea, 0x76, 0xed, 0x5f, 0xab, 0xdb, 0x17, 0x66, 0x20, 0x7f, - 0x7f, 0x20, 0xaa, 0x89, 0x81, 0xe8, 0x02, 0x2a, 0xf8, 0x07, 0x78, 0x79, 0xf2, 0x48, 0x2b, 0x40, 0xf7, 0x9d, 0x1d, - 0x5d, 0x7b, 0xbc, 0x31, 0x8b, 0xad, 0x25, 0xce, 0x59, 0xe5, 0x3b, 0xcb, 0xab, 0xb2, 0x15, 0xba, 0x8e, 0x60, 0xbf, - 0x85, 0x1d, 0x7d, 0xf7, 0xb6, 0x01, 0x10, 0xa5, 0xb0, 0x72, 0x67, 0xbf, 0xf0, 0xce, 0x7e, 0x61, 0xcf, 0x7e, 0xbb, - 0x09, 0x94, 0x0f, 0x2b, 0xb4, 0xec, 0xa5, 0x14, 0x95, 0x69, 0xf2, 0xb8, 0xa9, 0xcb, 0x42, 0x5a, 0xcc, 0x0f, 0x2d, - 0xed, 0x7a, 0x32, 0xa6, 0x12, 0xd5, 0x23, 0xdf, 0x63, 0xab, 0x0e, 0x4b, 0xf2, 0xf0, 0x3d, 0xf3, 0x7f, 0xf6, 0x06, - 0xb9, 0xef, 0x6e, 0xf7, 0x7f, 0x73, 0xa1, 0x83, 0xdb, 0x5a, 0x2a, 0x3c, 0x75, 0x75, 0x5c, 0xe0, 0x5d, 0x2d, 0x7d, - 0xf8, 0xae, 0xf6, 0x2e, 0xd3, 0xcb, 0xae, 0x02, 0xd4, 0x20, 0xb1, 0xbe, 0xe6, 0x45, 0x96, 0xd4, 0x56, 0xa1, 0xf1, - 0x96, 0x43, 0x68, 0x0f, 0xef, 0xe0, 0x02, 0x39, 0x2c, 0x21, 0xf4, 0x63, 0x65, 0x04, 0x80, 0x3e, 0x8b, 0xfd, 0x96, - 0x87, 0x19, 0x19, 0xf8, 0x12, 0xbf, 0x52, 0xfa, 0xe2, 0xe2, 0xc3, 0x9d, 0xcc, 0x04, 0xbd, 0x4a, 0x6c, 0x76, 0x29, - 0xdb, 0x31, 0x3f, 0xfc, 0x2f, 0x30, 0x1a, 0x84, 0xd7, 0x96, 0xec, 0x50, 0x74, 0xcc, 0x72, 0x05, 0x47, 0x6d, 0xe9, - 0xca, 0x2c, 0x5b, 0xd7, 0xcf, 0x6a, 0x98, 0xe9, 0x33, 0xe5, 0x2d, 0xc8, 0xbe, 0x90, 0xbb, 0x9f, 0xea, 0x8a, 0x05, - 0x39, 0x99, 0x8c, 0xa7, 0x44, 0x0c, 0x06, 0xad, 0xe4, 0x63, 0x4c, 0x1e, 0x0e, 0x77, 0x98, 0x4b, 0xa1, 0xfb, 0xe1, - 0xf5, 0x01, 0xea, 0x6b, 0x6c, 0x49, 0xb2, 0xa9, 0xd8, 0x9f, 0x60, 0x16, 0x0b, 0xc4, 0xd1, 0xc1, 0x2f, 0xce, 0x17, - 0x00, 0xb2, 0x0c, 0xcb, 0x4c, 0x0b, 0x8b, 0xca, 0x54, 0xf9, 0xc8, 0x16, 0x4c, 0x1e, 0x8f, 0x67, 0x7e, 0xcf, 0x1d, - 0x83, 0x43, 0x48, 0x34, 0xb1, 0xc6, 0x2f, 0x7e, 0x16, 0x8c, 0xe3, 0x50, 0x9e, 0xc8, 0xc6, 0x77, 0x25, 0x89, 0xc6, - 0xc6, 0x54, 0x59, 0x5f, 0x25, 0xaa, 0x61, 0x42, 0x1e, 0x17, 0xe4, 0xb0, 0xa0, 0x4b, 0x7f, 0x2c, 0x31, 0xfd, 0x30, - 0x3e, 0x9c, 0x8c, 0xc9, 0xe3, 0xf8, 0xf1, 0xc4, 0xc0, 0x0d, 0xfb, 0x39, 0xf2, 0xe1, 0x92, 0x1c, 0x36, 0xab, 0x04, - 0x53, 0x54, 0xd3, 0x33, 0xbf, 0x92, 0x64, 0xb0, 0x1c, 0xa4, 0x8f, 0x5b, 0x79, 0xb1, 0x56, 0x3d, 0xde, 0xeb, 0x63, - 0x3e, 0x25, 0xa2, 0x71, 0x63, 0x58, 0xd3, 0xeb, 0xf8, 0x0f, 0x59, 0x44, 0xa5, 0x04, 0x44, 0x42, 0x50, 0x6f, 0x67, - 0x97, 0x59, 0x12, 0x8b, 0x34, 0x4a, 0x6b, 0x42, 0xd3, 0x13, 0x36, 0x19, 0xcf, 0x52, 0x96, 0x1e, 0x4f, 0x9e, 0xce, - 0x26, 0x4f, 0xa3, 0xa3, 0x71, 0x94, 0x0e, 0x06, 0x90, 0x7c, 0x34, 0x06, 0x17, 0x3b, 0xf8, 0xcd, 0x8e, 0x60, 0xe8, - 0x4e, 0x90, 0x25, 0x2c, 0xa0, 0x69, 0x9f, 0xd7, 0x24, 0x3d, 0x9c, 0x97, 0xaa, 0x27, 0xf1, 0x1d, 0x5d, 0x7b, 0x0e, - 0x2e, 0x7e, 0x0b, 0x2f, 0x5d, 0x0b, 0x2f, 0x77, 0x5b, 0x28, 0x4c, 0xdc, 0x14, 0xf9, 0xff, 0xe3, 0x86, 0xb1, 0xef, - 0x2e, 0x61, 0x16, 0xd7, 0x4d, 0x36, 0x5a, 0x15, 0xb2, 0x92, 0x70, 0x9b, 0x50, 0xa2, 0xb0, 0x51, 0xbc, 0x5a, 0xe5, - 0xda, 0x45, 0x6c, 0x5e, 0x51, 0x00, 0x77, 0x81, 0x38, 0xc5, 0xc0, 0x42, 0x1b, 0x03, 0xb9, 0xbf, 0x78, 0x21, 0x99, - 0x55, 0xfb, 0x98, 0x7b, 0xe4, 0x1f, 0x21, 0x18, 0xa3, 0x8a, 0x93, 0xf1, 0x4c, 0x61, 0x5d, 0x7c, 0x4a, 0xde, 0xfb, - 0x6f, 0x1c, 0x45, 0xf6, 0x68, 0x06, 0x3d, 0x41, 0xe4, 0x3c, 0xe2, 0xec, 0xc9, 0xe4, 0x65, 0xe0, 0x7e, 0x06, 0x2b, - 0xfd, 0x75, 0xb7, 0x19, 0x6b, 0xdb, 0xa3, 0x7b, 0x61, 0x84, 0xa2, 0x7f, 0xe1, 0x3b, 0x53, 0x2f, 0xe0, 0x12, 0xaa, - 0x81, 0x5d, 0x5f, 0x5d, 0xf1, 0x12, 0x40, 0x84, 0x32, 0xd1, 0xef, 0xf7, 0xfe, 0x30, 0xd0, 0xa4, 0x25, 0x2f, 0x5e, - 0x67, 0xc2, 0x3a, 0xe3, 0x40, 0x53, 0x81, 0xfa, 0x7f, 0xac, 0xec, 0x33, 0x1d, 0x93, 0x99, 0xff, 0x38, 0x9c, 0x90, - 0xa8, 0xf9, 0x9a, 0x7c, 0xe2, 0x34, 0xfd, 0xc4, 0x15, 0xed, 0x3f, 0x90, 0x99, 0x1b, 0x0e, 0x19, 0xea, 0x2f, 0x1d, - 0xf3, 0x64, 0xf4, 0x3a, 0x31, 0x3b, 0x11, 0xac, 0x9a, 0x41, 0x14, 0xf6, 0x02, 0x1e, 0xd4, 0xb5, 0x2c, 0x9e, 0xc2, - 0xec, 0x83, 0x1a, 0x51, 0x1c, 0xb3, 0xf1, 0x2c, 0x94, 0xe1, 0x04, 0xec, 0x7b, 0x27, 0x63, 0xb8, 0x0f, 0xc8, 0xf0, - 0x63, 0x15, 0x62, 0xe7, 0x20, 0xed, 0x63, 0x85, 0x8a, 0x09, 0x80, 0x08, 0x84, 0xbc, 0xfd, 0xbe, 0x54, 0x49, 0xf8, - 0xba, 0xc4, 0x94, 0x42, 0x7d, 0xf0, 0x9f, 0x48, 0xd5, 0x1d, 0xd3, 0xaf, 0xd6, 0x8f, 0x3f, 0x13, 0x8a, 0x4f, 0x77, - 0x29, 0xf1, 0x1d, 0x04, 0x77, 0x96, 0xa0, 0x83, 0xa8, 0xd0, 0x8c, 0xed, 0x61, 0x7e, 0x57, 0xec, 0xe7, 0x77, 0xc5, - 0xff, 0x3b, 0x7e, 0x57, 0x3c, 0xc4, 0x18, 0x56, 0x16, 0x1a, 0x7e, 0x16, 0x8c, 0x83, 0xe8, 0x3f, 0xe7, 0x13, 0xf7, - 0xf2, 0xd4, 0xd7, 0x99, 0x98, 0xee, 0x61, 0x9a, 0x7d, 0x82, 0x82, 0xb0, 0x8a, 0xbb, 0xf4, 0x64, 0x5d, 0xd9, 0x5b, - 0x2b, 0x19, 0x62, 0x9e, 0x07, 0x58, 0xa3, 0xb0, 0xf2, 0x80, 0xee, 0x51, 0xb5, 0x41, 0x9c, 0x08, 0x1e, 0xc6, 0xcc, - 0x4a, 0xdf, 0xb7, 0x5b, 0xa3, 0xc2, 0x7c, 0x90, 0x8b, 0x82, 0xec, 0xe6, 0xe3, 0xd9, 0x38, 0x0a, 0xb1, 0x01, 0xff, - 0x31, 0x63, 0xd5, 0x90, 0xcd, 0x77, 0x32, 0x52, 0x3b, 0x26, 0x4f, 0x93, 0x5d, 0xd2, 0x3b, 0xe0, 0x1d, 0xf2, 0x73, - 0x70, 0x67, 0x93, 0x86, 0xdf, 0x92, 0x57, 0x71, 0x91, 0x55, 0xcb, 0xeb, 0x2c, 0x41, 0xa6, 0x0b, 0x5e, 0x7c, 0x36, - 0xd3, 0xe5, 0x7d, 0xac, 0x0f, 0x18, 0x4f, 0x29, 0x5e, 0x37, 0x44, 0xe9, 0xeb, 0x96, 0x67, 0x85, 0xba, 0x3c, 0xa9, - 0x98, 0xed, 0x59, 0x09, 0x4e, 0xa7, 0x60, 0x82, 0xaf, 0x7f, 0xba, 0xde, 0x27, 0x80, 0x0b, 0x0a, 0x35, 0xa7, 0x85, - 0x5c, 0x19, 0x2c, 0x27, 0x0b, 0xdd, 0x09, 0x98, 0xa1, 0x52, 0xe0, 0x05, 0x0a, 0xfe, 0xa2, 0x81, 0x11, 0x7d, 0xe9, - 0x7e, 0x93, 0x81, 0x41, 0xba, 0x34, 0x27, 0xc2, 0xd8, 0x71, 0x3b, 0x45, 0xda, 0x8a, 0x72, 0xc6, 0xd9, 0x7b, 0x75, - 0xa5, 0x00, 0x03, 0xbc, 0xcd, 0x6d, 0x74, 0x91, 0xa0, 0xd7, 0x82, 0xd2, 0x79, 0x03, 0x77, 0xb3, 0x8c, 0x8c, 0x70, - 0xf1, 0x71, 0xe5, 0xb1, 0xe0, 0x9e, 0xfd, 0x42, 0x2c, 0x8d, 0x66, 0x1a, 0x8c, 0xd9, 0xbc, 0x60, 0x81, 0x42, 0x05, - 0x0a, 0x2c, 0x67, 0xda, 0xd2, 0xb4, 0x1a, 0xf2, 0xc3, 0x23, 0xb4, 0x36, 0xad, 0x06, 0xfc, 0xf0, 0xa8, 0x8e, 0xb2, - 0x63, 0xc8, 0x72, 0xe2, 0x67, 0x50, 0xaf, 0xeb, 0xc8, 0xa4, 0x98, 0xec, 0x7e, 0x7d, 0xa9, 0x3f, 0xaa, 0x1b, 0x70, - 0xfd, 0x00, 0x04, 0xb0, 0x01, 0x38, 0x04, 0xaa, 0xc1, 0xd2, 0x88, 0x60, 0x51, 0xa6, 0xd0, 0xbe, 0x86, 0xde, 0x1b, - 0x0d, 0xff, 0x05, 0xee, 0x22, 0x72, 0xe5, 0x7f, 0x82, 0xc0, 0x5f, 0x51, 0xa6, 0x95, 0x29, 0xfe, 0x27, 0x5a, 0xbd, - 0x42, 0x39, 0x6b, 0x5a, 0xf3, 0x41, 0xb4, 0x26, 0x42, 0x35, 0x63, 0x08, 0xfe, 0xad, 0x2c, 0xd3, 0x96, 0xaa, 0x4a, - 0x7d, 0x68, 0xbc, 0xd6, 0x0a, 0x67, 0xf9, 0x38, 0xf2, 0x5e, 0x63, 0xe8, 0xd8, 0xc4, 0x59, 0xca, 0xa9, 0xd4, 0xd9, - 0x9b, 0x43, 0x19, 0x39, 0xc0, 0xe9, 0x84, 0x8d, 0xa7, 0xc9, 0xb1, 0x9c, 0x26, 0x0e, 0x32, 0x3f, 0x67, 0x18, 0x59, - 0xd5, 0x80, 0xb0, 0x28, 0x1b, 0x4a, 0x5b, 0x80, 0x49, 0x4e, 0x08, 0x99, 0x62, 0x28, 0x8a, 0x7c, 0xa4, 0xfb, 0x61, - 0xbd, 0x59, 0xdd, 0x17, 0xef, 0x34, 0xc0, 0x69, 0x98, 0x40, 0x20, 0xf0, 0x22, 0xbe, 0xcd, 0xc4, 0x15, 0x78, 0x0c, - 0x0f, 0xe0, 0x4b, 0x70, 0x93, 0x4b, 0xd9, 0xaf, 0x55, 0x98, 0xe3, 0xda, 0x02, 0x06, 0x0d, 0x56, 0x0f, 0xa2, 0xc3, - 0xa5, 0xb4, 0xd9, 0x55, 0x80, 0xd8, 0x98, 0x42, 0x2c, 0x0b, 0xb6, 0xb6, 0xec, 0xd9, 0x4f, 0xaa, 0x69, 0x68, 0x9d, - 0x70, 0x2a, 0xae, 0x72, 0x88, 0xa2, 0x32, 0x88, 0xc1, 0x1d, 0xc9, 0xe3, 0xf3, 0x1e, 0x89, 0xf0, 0x92, 0x80, 0x5b, - 0x59, 0x2c, 0xc3, 0x15, 0x5d, 0x8e, 0xee, 0xe8, 0x7a, 0x74, 0x4b, 0xc7, 0x74, 0xf2, 0xf7, 0x31, 0x58, 0x64, 0xeb, - 0xd4, 0x7b, 0xba, 0x1e, 0x2d, 0xe9, 0x37, 0x63, 0x7a, 0xf4, 0x37, 0x30, 0xe1, 0xc3, 0xc3, 0x84, 0x5e, 0x82, 0x63, - 0x17, 0xa9, 0xd1, 0x53, 0xd3, 0x37, 0x38, 0xac, 0x46, 0xf9, 0x90, 0x8f, 0x72, 0xca, 0x47, 0xc5, 0xb0, 0x1a, 0x81, - 0xa7, 0x63, 0x35, 0xe4, 0xa3, 0x8a, 0xf2, 0xd1, 0xc5, 0xb0, 0x1a, 0x5d, 0x90, 0x66, 0xd3, 0x5f, 0x55, 0xfc, 0xba, - 0x64, 0x29, 0x6c, 0x0b, 0x58, 0xbe, 0x9e, 0x57, 0x54, 0xea, 0xaf, 0x6a, 0x73, 0x32, 0x5b, 0xce, 0xde, 0x5e, 0x77, - 0x39, 0xb1, 0x78, 0xdc, 0x36, 0x1d, 0xae, 0xbe, 0x9c, 0xa8, 0x93, 0x5e, 0x21, 0x3f, 0x8c, 0xa7, 0x42, 0x9d, 0x43, - 0x60, 0x26, 0x31, 0x0b, 0x63, 0x86, 0xcd, 0xd4, 0x69, 0xa0, 0xc0, 0xc9, 0x46, 0x9e, 0x8b, 0x62, 0x36, 0xca, 0x29, - 0xbc, 0x8f, 0x09, 0x89, 0x04, 0x9c, 0x55, 0x27, 0xd5, 0xa8, 0x80, 0x98, 0x23, 0x2c, 0xc4, 0x47, 0xe8, 0x97, 0xfa, - 0xc8, 0x43, 0x02, 0xcf, 0xb0, 0xaf, 0xc5, 0x20, 0x86, 0x23, 0xde, 0x56, 0x56, 0xcd, 0xc2, 0x04, 0x2a, 0xab, 0x86, - 0xa5, 0xa9, 0xac, 0xa0, 0xd9, 0xa8, 0xf2, 0x2b, 0xab, 0x70, 0x8c, 0x12, 0x42, 0xa2, 0x52, 0x57, 0x06, 0xea, 0x93, - 0x84, 0x85, 0xa5, 0xae, 0xec, 0x42, 0x7d, 0x74, 0xe1, 0x57, 0x76, 0x01, 0x2e, 0xa4, 0x83, 0xc4, 0xbf, 0x4a, 0xe5, - 0x69, 0xfb, 0x3a, 0xd8, 0x58, 0x55, 0x74, 0xc3, 0xef, 0xaa, 0x22, 0x8e, 0x4a, 0xea, 0x62, 0x40, 0xe3, 0xc2, 0x88, - 0x24, 0xd5, 0x6b, 0x14, 0xfc, 0x21, 0x41, 0x54, 0x1a, 0x83, 0x57, 0x67, 0xd2, 0xb5, 0x52, 0x2b, 0x2a, 0x06, 0xe5, - 0xa0, 0x80, 0xfb, 0x53, 0xde, 0x5a, 0x48, 0x3f, 0x41, 0x44, 0x65, 0x28, 0x6f, 0xf0, 0x4f, 0x0c, 0x9e, 0xcc, 0x56, - 0x69, 0x98, 0x8c, 0xee, 0x69, 0x3c, 0x5a, 0x22, 0x1c, 0x0c, 0x5b, 0xa7, 0x0a, 0x6f, 0xfd, 0x12, 0xd2, 0xef, 0x68, - 0x3c, 0xba, 0xa5, 0xa9, 0xb5, 0x39, 0x35, 0x50, 0x57, 0xbd, 0x31, 0xbd, 0x8b, 0xe0, 0xf5, 0x7d, 0xb4, 0xa4, 0xb0, - 0x95, 0x4e, 0xf3, 0xec, 0x4a, 0x44, 0x29, 0x45, 0x04, 0xc2, 0x35, 0x22, 0x07, 0x2e, 0x35, 0xda, 0xe0, 0x7a, 0x00, - 0x65, 0x68, 0xb8, 0xc0, 0xe5, 0x20, 0x1e, 0x2d, 0x3d, 0x32, 0xb5, 0xd4, 0x17, 0x59, 0x84, 0x8f, 0x76, 0x36, 0x5a, - 0x8a, 0x67, 0xc4, 0xc2, 0xb8, 0x82, 0x21, 0xd4, 0x85, 0x95, 0xa6, 0x20, 0xe9, 0x02, 0x47, 0xf6, 0xc2, 0xb8, 0x0a, - 0x37, 0x60, 0x5a, 0x74, 0x0f, 0xe6, 0x51, 0xa0, 0x70, 0x70, 0x09, 0xd2, 0x4f, 0x28, 0xdb, 0x39, 0x4a, 0x93, 0xc3, - 0x9b, 0xa0, 0x74, 0x67, 0x82, 0x90, 0x76, 0x75, 0x93, 0x2d, 0xe9, 0x1b, 0x6c, 0xef, 0xd0, 0xa9, 0xa8, 0xa0, 0xfa, - 0xdc, 0x82, 0xc9, 0x92, 0x0d, 0xc2, 0x96, 0x30, 0x3d, 0xd3, 0x6b, 0xc0, 0x9e, 0x3e, 0x3c, 0xda, 0x99, 0xef, 0x62, - 0xf6, 0xe6, 0xb0, 0x8c, 0xc6, 0xca, 0x82, 0x37, 0xb7, 0xc4, 0x6e, 0xc9, 0xc6, 0xd3, 0xe5, 0x71, 0x39, 0x5d, 0x22, - 0xb1, 0x33, 0x74, 0x8b, 0xf1, 0xf9, 0x72, 0x41, 0x13, 0x3c, 0xdb, 0x58, 0x35, 0x5f, 0x1a, 0xb4, 0x94, 0x94, 0xe1, - 0x7a, 0x5b, 0xa2, 0xff, 0xbf, 0xba, 0xf8, 0xa5, 0x00, 0x2f, 0xc1, 0x58, 0x00, 0x08, 0xf7, 0x60, 0x5a, 0x90, 0xda, - 0x28, 0x1b, 0xcb, 0x34, 0x4c, 0x71, 0x11, 0x98, 0x94, 0x7e, 0x3f, 0xcc, 0x59, 0x4a, 0x3c, 0xe8, 0x50, 0x77, 0x6a, - 0xa7, 0xbe, 0x10, 0x04, 0x78, 0x24, 0x75, 0x8e, 0x4d, 0xfe, 0x3e, 0x9e, 0x05, 0x6a, 0x20, 0x82, 0x28, 0x3b, 0xc6, - 0x47, 0x0c, 0x5c, 0x14, 0xe9, 0xb8, 0x9d, 0xae, 0x88, 0xcb, 0xdd, 0x63, 0x16, 0xe2, 0x24, 0x61, 0xae, 0x59, 0x36, - 0x64, 0x55, 0x84, 0x09, 0xba, 0x30, 0x30, 0xcb, 0x1b, 0xb2, 0xea, 0xf0, 0x08, 0x22, 0xb5, 0xda, 0x32, 0x56, 0x5d, - 0x65, 0x7c, 0x03, 0x40, 0xd6, 0x8c, 0xb1, 0xa3, 0xbf, 0x8d, 0x67, 0xea, 0x9b, 0x28, 0xe4, 0x27, 0x47, 0x7f, 0x83, - 0xe4, 0xe3, 0x6f, 0x90, 0x99, 0x83, 0xe4, 0x46, 0x41, 0x57, 0xcd, 0x59, 0xd7, 0x50, 0x9a, 0xb8, 0xf6, 0x4a, 0xbd, - 0xf6, 0xa4, 0x59, 0x7b, 0x05, 0xba, 0x53, 0x1b, 0xde, 0x43, 0xd9, 0xce, 0x82, 0x09, 0x3a, 0x9a, 0xdd, 0x81, 0x0e, - 0xde, 0x29, 0x82, 0x5e, 0x24, 0xa1, 0xf1, 0x08, 0x55, 0x46, 0xbd, 0x18, 0x0f, 0xaa, 0x93, 0x75, 0xc9, 0x3c, 0x03, - 0xe6, 0xd8, 0x9e, 0x43, 0x62, 0x98, 0xab, 0x83, 0x3a, 0x65, 0xe5, 0x30, 0xc7, 0x03, 0x78, 0xcd, 0xe4, 0x50, 0x0c, - 0x72, 0x8d, 0xf2, 0x7d, 0xc9, 0x8a, 0x61, 0x39, 0xc8, 0x35, 0x37, 0x33, 0x6d, 0xc6, 0xa6, 0x4d, 0x74, 0x78, 0xe6, - 0x15, 0x3b, 0x59, 0xf5, 0x80, 0x8f, 0x05, 0x4f, 0x66, 0xdf, 0xf3, 0xf1, 0x35, 0x70, 0x32, 0x9b, 0xbb, 0x68, 0x49, - 0xef, 0xa3, 0x94, 0xde, 0x46, 0x6b, 0xba, 0x8c, 0x2e, 0x8d, 0x89, 0x71, 0x52, 0xc3, 0x39, 0x00, 0xad, 0x02, 0x48, - 0x3c, 0xf5, 0xeb, 0x3d, 0x4f, 0xaa, 0x70, 0x49, 0x53, 0x70, 0x1b, 0xf6, 0xed, 0x33, 0xaf, 0x7d, 0x89, 0xd4, 0x06, - 0x31, 0xd6, 0xac, 0xa1, 0xe2, 0xc6, 0x5b, 0xf7, 0x91, 0xa8, 0x61, 0xe7, 0xba, 0xd8, 0x44, 0xd5, 0x70, 0x32, 0x2d, - 0x01, 0xb1, 0xb5, 0x1c, 0x0e, 0xdd, 0x11, 0xb2, 0x7b, 0xfc, 0xe8, 0x40, 0xcf, 0x3d, 0x69, 0xb1, 0x6d, 0x5b, 0xfe, - 0xc0, 0x10, 0xa6, 0xf4, 0xd3, 0x47, 0x3e, 0x20, 0x56, 0x5c, 0xc1, 0xd9, 0x08, 0xd4, 0xd1, 0x0a, 0x9d, 0x7e, 0xad, - 0xc2, 0x42, 0x1f, 0xe0, 0x9b, 0xbb, 0x28, 0xa1, 0xf7, 0x51, 0xee, 0x91, 0xb5, 0x65, 0xcd, 0xe4, 0xf4, 0x3c, 0x0b, - 0x79, 0xfb, 0x40, 0x2f, 0x17, 0x00, 0xa2, 0x35, 0x88, 0x7d, 0xa9, 0xeb, 0x11, 0x38, 0x0d, 0xa1, 0x49, 0x68, 0x04, - 0x57, 0x15, 0x84, 0x11, 0x70, 0x25, 0xe1, 0x6f, 0x30, 0x51, 0x81, 0x2f, 0xc0, 0x45, 0x26, 0x4d, 0x73, 0x1e, 0xd4, - 0xfe, 0x48, 0x9e, 0x15, 0x6d, 0x6f, 0x57, 0x18, 0x4d, 0x30, 0xf6, 0x44, 0xfb, 0x3c, 0x52, 0x8e, 0xe2, 0x22, 0x09, - 0xb3, 0xd1, 0x9d, 0x3a, 0xcf, 0x69, 0x36, 0xba, 0xd7, 0xbf, 0x2a, 0x3a, 0xa6, 0xbf, 0xe8, 0x80, 0x36, 0x4a, 0xfa, - 0xd6, 0x71, 0x36, 0xa0, 0xf5, 0x62, 0x69, 0xfc, 0xaf, 0xe5, 0xe8, 0x8e, 0xca, 0xd1, 0xbd, 0x6f, 0x49, 0x35, 0x99, - 0x16, 0xc7, 0x02, 0x0d, 0xa9, 0x3a, 0xbf, 0x2f, 0x80, 0x9f, 0x2b, 0x8d, 0xef, 0xb4, 0xf9, 0xde, 0x6b, 0xff, 0x45, - 0x27, 0x4f, 0xa0, 0x58, 0xa2, 0x82, 0x55, 0x23, 0xb0, 0x63, 0x5f, 0xe7, 0x71, 0x61, 0x46, 0x29, 0xa6, 0xd6, 0xa4, - 0x1f, 0x03, 0x57, 0x4c, 0x7b, 0x05, 0xb8, 0x5a, 0x82, 0x93, 0x00, 0xc4, 0xd0, 0x84, 0x3d, 0x3b, 0x86, 0xa8, 0xe7, - 0xc6, 0x31, 0x4a, 0x36, 0xdc, 0x03, 0x62, 0x2d, 0xf3, 0x56, 0x2e, 0x01, 0x09, 0xbc, 0xf5, 0x30, 0x29, 0x00, 0x63, - 0xb0, 0x5c, 0x12, 0x9d, 0xc7, 0x43, 0x9f, 0x50, 0x2f, 0x34, 0xea, 0x84, 0x6c, 0x6c, 0x09, 0x1c, 0x7f, 0x58, 0x1f, - 0x02, 0xc1, 0xab, 0x3c, 0xd7, 0x5f, 0x69, 0x5d, 0x7f, 0xa9, 0xf4, 0xdc, 0xb1, 0x5c, 0xd7, 0xcf, 0xdb, 0xd4, 0xe8, - 0x25, 0x58, 0xf8, 0x6e, 0x94, 0x79, 0x24, 0xb7, 0x08, 0xa9, 0x0a, 0xac, 0xd4, 0x2d, 0x24, 0x98, 0x7f, 0x25, 0x67, - 0xab, 0x32, 0x5f, 0x3d, 0xf2, 0xa0, 0x9c, 0x4d, 0x4f, 0x7f, 0x43, 0x82, 0x76, 0xdf, 0x91, 0xe6, 0xf1, 0x16, 0x1d, - 0x3e, 0xbb, 0xd6, 0x12, 0x73, 0x27, 0x51, 0xf1, 0x7c, 0x0a, 0xd8, 0xea, 0x79, 0x76, 0xad, 0x7c, 0xac, 0x76, 0x71, - 0xfc, 0xcc, 0xf9, 0x93, 0x54, 0xe1, 0x5a, 0x34, 0x94, 0x20, 0xe0, 0xcd, 0x61, 0xec, 0x0a, 0x55, 0x40, 0x43, 0x73, - 0x03, 0xc7, 0xb9, 0x1a, 0x56, 0x9a, 0x80, 0x69, 0x29, 0x8f, 0x0e, 0x70, 0x68, 0xf2, 0xa8, 0xdd, 0x34, 0xac, 0x0c, - 0x5d, 0x6b, 0xf4, 0xb9, 0xad, 0x74, 0xc6, 0x9b, 0x0d, 0x3f, 0x3c, 0x1a, 0x54, 0xf8, 0x93, 0x34, 0x47, 0xa3, 0x9d, - 0x1b, 0xee, 0x34, 0x02, 0x33, 0x57, 0x72, 0x45, 0x76, 0x47, 0xc9, 0xcb, 0xef, 0xe9, 0x85, 0x05, 0xf4, 0xe7, 0x3f, - 0x17, 0x13, 0x4e, 0x5a, 0x62, 0x42, 0xb4, 0x74, 0xd0, 0xa2, 0x83, 0x1d, 0xe5, 0x95, 0x7d, 0x89, 0x97, 0xce, 0xf1, - 0xbf, 0xaf, 0xc7, 0xda, 0x55, 0x20, 0xb4, 0x3a, 0x79, 0xd8, 0x9e, 0x2c, 0x10, 0x35, 0xa0, 0x9a, 0x5d, 0x95, 0xa3, - 0x4c, 0x3b, 0x2b, 0xb2, 0x69, 0xc8, 0x5c, 0x77, 0xb3, 0x34, 0x6c, 0x26, 0x3b, 0x16, 0x96, 0x19, 0x06, 0x6b, 0xa7, - 0x8a, 0x3e, 0x07, 0x2d, 0x3f, 0x82, 0xe7, 0x4d, 0xe5, 0x99, 0xcf, 0x66, 0x19, 0xf1, 0x02, 0x9d, 0x73, 0x2a, 0x16, - 0x4d, 0xe9, 0x58, 0xb9, 0xdd, 0x96, 0x68, 0x2c, 0x51, 0x46, 0x41, 0x50, 0xdb, 0x20, 0xec, 0xba, 0x74, 0x4f, 0xfa, - 0xb4, 0x8b, 0x4f, 0x2b, 0xd0, 0xf7, 0x78, 0x9f, 0x81, 0xc4, 0xd4, 0x93, 0x3c, 0x54, 0x8d, 0xe6, 0xe8, 0xe4, 0x59, - 0x9c, 0x6a, 0x7c, 0x7e, 0x25, 0x3b, 0x6b, 0xde, 0xad, 0xc6, 0x14, 0xff, 0x91, 0xba, 0x7d, 0xe7, 0x32, 0x34, 0xd1, - 0x5f, 0xcb, 0x83, 0x96, 0xc2, 0x82, 0xe3, 0xb6, 0xf1, 0xd7, 0x6f, 0x33, 0x87, 0x18, 0x96, 0x2e, 0x87, 0x37, 0xa1, - 0x43, 0x77, 0x57, 0xd9, 0x99, 0xeb, 0x23, 0xea, 0xd4, 0xc5, 0xba, 0x0d, 0x28, 0x59, 0xf2, 0x6e, 0x9d, 0x9e, 0x58, - 0xe9, 0x97, 0xc3, 0x70, 0x67, 0x1e, 0x35, 0xbb, 0xbb, 0xdd, 0x4e, 0x48, 0xdb, 0x3e, 0x18, 0xef, 0x4b, 0x58, 0x88, - 0xf3, 0x0e, 0x3b, 0xf8, 0x29, 0xac, 0x1e, 0xf3, 0xc1, 0x6f, 0x38, 0xce, 0x30, 0xfa, 0x99, 0x32, 0xf4, 0x79, 0x59, - 0xc8, 0x6b, 0xd5, 0x29, 0x5f, 0xe8, 0xd6, 0x32, 0xf5, 0x7e, 0x13, 0xbf, 0x69, 0x05, 0x88, 0xf1, 0xba, 0x62, 0xa5, - 0x78, 0x43, 0x2b, 0x8c, 0x6b, 0xe0, 0x36, 0x39, 0xd4, 0x52, 0x2d, 0x10, 0x75, 0xf9, 0xc9, 0x63, 0x1e, 0x19, 0x75, - 0x26, 0x7c, 0xf7, 0x98, 0xfb, 0xd2, 0xb5, 0xdd, 0x26, 0x7e, 0xaa, 0x69, 0x87, 0xbb, 0x03, 0xdd, 0xd1, 0xba, 0x87, - 0x9b, 0x67, 0xf3, 0xf3, 0xc8, 0x7c, 0x31, 0xc0, 0x66, 0xed, 0x32, 0x2e, 0x3b, 0x86, 0xfb, 0xde, 0xf4, 0x60, 0x2c, - 0x20, 0x90, 0x98, 0xa1, 0x97, 0x81, 0x0b, 0x5c, 0xe0, 0xae, 0x30, 0x60, 0x88, 0x6b, 0x5a, 0x72, 0xae, 0xad, 0x6c, - 0x7d, 0xe4, 0x6d, 0x54, 0x08, 0xd6, 0x75, 0xc7, 0x4d, 0x92, 0x43, 0x70, 0xc2, 0x96, 0x7b, 0x5f, 0x7b, 0xed, 0x0c, - 0xff, 0x39, 0x10, 0xce, 0x2d, 0xd1, 0x33, 0x6a, 0x7b, 0xac, 0xd5, 0xbd, 0x86, 0x57, 0xb9, 0x8f, 0x3c, 0xeb, 0x37, - 0xf3, 0xd2, 0xb0, 0x2f, 0x78, 0x2d, 0x05, 0x87, 0xc6, 0x76, 0x2b, 0xdc, 0x62, 0xf1, 0x8e, 0x56, 0x2b, 0x6b, 0x6d, - 0xb5, 0xd7, 0x4a, 0x45, 0xf7, 0xaf, 0x39, 0x4e, 0x9c, 0xa5, 0xb0, 0xfd, 0xf0, 0xe1, 0x82, 0x5d, 0x13, 0xc0, 0xa0, - 0xc5, 0x64, 0x81, 0x12, 0x54, 0xb2, 0x56, 0xb5, 0xdb, 0x29, 0xf1, 0xcb, 0xfd, 0xac, 0xcb, 0x6c, 0xe7, 0xf1, 0xeb, - 0x26, 0xed, 0x13, 0x9f, 0xa3, 0x1f, 0xe6, 0xb7, 0xd6, 0x49, 0xc9, 0x19, 0xc6, 0xb5, 0xfc, 0xff, 0x2a, 0x7a, 0x55, - 0x64, 0x69, 0xb4, 0x31, 0x3c, 0x98, 0x0d, 0xb5, 0xe9, 0x43, 0x63, 0x54, 0x6e, 0xd9, 0x28, 0x22, 0x5a, 0xdd, 0x81, - 0x60, 0x46, 0x71, 0x5f, 0xa2, 0xcd, 0x2b, 0x55, 0x16, 0xde, 0xe1, 0x13, 0x1b, 0xbd, 0x61, 0x7b, 0x42, 0x28, 0xdf, - 0x3d, 0x2d, 0xcc, 0xaa, 0xa5, 0xa2, 0xc1, 0x76, 0x09, 0xef, 0x62, 0x54, 0xe9, 0x27, 0x4c, 0xb6, 0x2c, 0x98, 0xea, - 0xff, 0x77, 0x45, 0x96, 0xb6, 0x29, 0x3a, 0x30, 0x9d, 0x4d, 0x9f, 0x4e, 0xba, 0xc1, 0x75, 0x06, 0x2c, 0x22, 0xd8, - 0x52, 0xe1, 0x78, 0x94, 0xda, 0x0d, 0x12, 0x26, 0x82, 0x9b, 0xa8, 0x97, 0x1d, 0x2d, 0x53, 0xb2, 0x2a, 0xe0, 0xf9, - 0x95, 0xab, 0x4c, 0xc7, 0xd1, 0xd0, 0xef, 0x5f, 0xa7, 0x26, 0xf4, 0x2b, 0xf5, 0x52, 0x15, 0xe7, 0x61, 0x54, 0x1d, - 0x2a, 0x8c, 0xd1, 0x92, 0xa6, 0x70, 0x0c, 0x66, 0x97, 0x61, 0x8a, 0x97, 0xb3, 0x4d, 0xc2, 0x3e, 0x63, 0x20, 0x97, - 0xda, 0xa0, 0x5e, 0x53, 0xa2, 0x35, 0x6b, 0x6f, 0xe6, 0x94, 0xd0, 0x4b, 0x56, 0xfa, 0x77, 0xa1, 0x35, 0x08, 0x14, - 0x65, 0x33, 0x65, 0x7a, 0xa1, 0xdb, 0x79, 0x49, 0x13, 0x5a, 0xd0, 0x15, 0xa9, 0x41, 0xdf, 0xeb, 0xe4, 0xec, 0xe8, - 0x64, 0x67, 0x66, 0x3d, 0x66, 0xc5, 0x70, 0x32, 0x8d, 0xe1, 0x9a, 0x16, 0xbb, 0x6b, 0xda, 0xb2, 0x79, 0xe3, 0x6a, - 0x6c, 0x9c, 0x06, 0xed, 0x02, 0x69, 0x9b, 0xe6, 0xf6, 0x53, 0x8f, 0xdb, 0x5f, 0xd7, 0x6c, 0x39, 0xed, 0xad, 0xb7, - 0xdb, 0x5e, 0x0a, 0x36, 0xa2, 0x1e, 0x1f, 0xbf, 0x56, 0xd2, 0x75, 0xcb, 0xe5, 0xa7, 0xf0, 0xec, 0xf1, 0xf5, 0x4b, - 0x1f, 0x5c, 0x8e, 0x56, 0x6d, 0xee, 0x7e, 0xb9, 0x8b, 0x2c, 0xf7, 0x59, 0x43, 0xcb, 0xf5, 0x0c, 0x35, 0xc9, 0xb3, - 0xd1, 0xde, 0xa1, 0x16, 0x2c, 0x67, 0xdd, 0x84, 0x27, 0x06, 0x3b, 0xf6, 0xaa, 0xb1, 0x39, 0x2a, 0x73, 0xc9, 0x6a, - 0x90, 0x40, 0x9f, 0xe4, 0x99, 0xa6, 0x7f, 0x90, 0x61, 0x3e, 0xba, 0xa3, 0x39, 0xe0, 0x8a, 0x55, 0xf6, 0x92, 0x41, - 0xea, 0xaa, 0xbd, 0xc4, 0x95, 0xaf, 0x70, 0x48, 0x36, 0xf8, 0x64, 0x98, 0xaa, 0x4f, 0x2e, 0x79, 0xf0, 0xff, 0xb6, - 0x6a, 0x95, 0x9e, 0x9b, 0xe4, 0x86, 0xe3, 0x5f, 0x27, 0x6d, 0x1f, 0x13, 0x83, 0x04, 0x3c, 0xb5, 0x8b, 0xa1, 0x1a, - 0x55, 0x45, 0x2c, 0xca, 0xdc, 0xc4, 0x1c, 0xdb, 0xdb, 0x35, 0x74, 0x50, 0x06, 0xbf, 0x6e, 0xf8, 0xc4, 0xdc, 0x81, - 0xad, 0x40, 0x47, 0x27, 0x9a, 0xcb, 0x30, 0x33, 0x97, 0x61, 0xda, 0xb5, 0x55, 0x60, 0x78, 0xd5, 0x56, 0x49, 0x94, - 0xab, 0x51, 0x8f, 0x9b, 0x59, 0x6a, 0xf6, 0x22, 0xef, 0x5e, 0x93, 0x9e, 0xc4, 0x9f, 0x2e, 0x3d, 0x79, 0x3d, 0x0c, - 0x88, 0xfc, 0x9c, 0xa5, 0xe1, 0x1a, 0x05, 0xc1, 0xa9, 0xd5, 0x0e, 0xa4, 0xf9, 0x08, 0x90, 0xf9, 0x71, 0x1a, 0xbe, - 0xd5, 0xe2, 0x1c, 0xb2, 0x51, 0x1a, 0x27, 0xb6, 0x34, 0xea, 0x21, 0xb8, 0xf3, 0x5e, 0xf3, 0x18, 0x02, 0x1f, 0x7e, - 0xc0, 0xcd, 0xa0, 0xa2, 0xdb, 0x12, 0x13, 0xa5, 0xcd, 0xa3, 0x6e, 0xf9, 0xa8, 0x21, 0x54, 0xb2, 0x32, 0xbc, 0x04, - 0xda, 0xbb, 0x23, 0x30, 0xaa, 0x9c, 0x40, 0x66, 0x58, 0x1c, 0x1e, 0x0d, 0x53, 0x25, 0x28, 0x1a, 0xca, 0xe1, 0x12, - 0xe5, 0x80, 0x98, 0x04, 0x02, 0xa3, 0x62, 0x90, 0xea, 0xca, 0xd4, 0x8b, 0x41, 0xaa, 0x6f, 0x55, 0xa4, 0x3e, 0xcf, - 0xc2, 0x8a, 0xea, 0x16, 0xd1, 0x31, 0x1d, 0x4a, 0xba, 0x34, 0x3b, 0x35, 0xd7, 0xd2, 0x0b, 0xb5, 0x1c, 0x9f, 0xe9, - 0x34, 0x18, 0xc5, 0x33, 0x97, 0xa2, 0xdf, 0xaa, 0xfd, 0xec, 0xbf, 0xc5, 0x94, 0x1a, 0xb1, 0xa9, 0xbd, 0x45, 0x0c, - 0xab, 0xf6, 0x43, 0x56, 0xe5, 0xa0, 0xdd, 0x05, 0x65, 0x63, 0x65, 0x9c, 0xe7, 0x1b, 0xc1, 0xcc, 0x41, 0xdb, 0x58, - 0x35, 0x7d, 0xe8, 0x8d, 0x18, 0xb5, 0x37, 0xa6, 0x1a, 0xf7, 0x04, 0x7e, 0xda, 0xa0, 0xe9, 0x5e, 0xe4, 0x39, 0xea, - 0x91, 0x77, 0xff, 0x33, 0x47, 0x76, 0x26, 0x9f, 0xc4, 0x32, 0xa9, 0xdb, 0xc7, 0x24, 0x58, 0xa8, 0x3a, 0x46, 0x17, - 0x6e, 0x64, 0x4a, 0xfb, 0xb9, 0x33, 0xfd, 0x88, 0x67, 0xf2, 0xb0, 0x1d, 0x1a, 0xf5, 0xa5, 0x61, 0x2d, 0x29, 0xa2, - 0xbe, 0xa0, 0xb7, 0xa6, 0x3a, 0x3a, 0xa2, 0x5e, 0x47, 0x60, 0x75, 0x45, 0x1b, 0xd4, 0x00, 0x4c, 0xc6, 0xb5, 0xad, - 0xcd, 0xe7, 0x60, 0x6a, 0xab, 0x2a, 0x78, 0x4a, 0x77, 0x85, 0xd2, 0xbd, 0x49, 0x5d, 0xb7, 0x86, 0xd8, 0x02, 0x06, - 0x04, 0x6e, 0xf4, 0xd4, 0xf4, 0x07, 0x4d, 0x54, 0x00, 0x1a, 0x34, 0x6e, 0x67, 0x3a, 0x47, 0xa2, 0xdf, 0xa9, 0x4d, - 0xdb, 0x4c, 0xf5, 0xaa, 0xf2, 0x01, 0x54, 0xfc, 0x59, 0x3a, 0xbf, 0x34, 0x23, 0x16, 0xc0, 0xb8, 0x07, 0xce, 0x54, - 0xef, 0x34, 0x03, 0xeb, 0x89, 0x3c, 0xcf, 0x4a, 0x9e, 0x48, 0x01, 0x33, 0x22, 0xaf, 0xaf, 0xa5, 0x80, 0x61, 0x50, - 0x03, 0x80, 0x16, 0xcd, 0x65, 0x34, 0xe1, 0x5f, 0xd5, 0x74, 0x5f, 0x1e, 0xfe, 0x95, 0xce, 0xf5, 0xf5, 0xb8, 0x06, - 0x43, 0xe5, 0x75, 0xc5, 0x77, 0x32, 0x7d, 0xcd, 0x9f, 0x78, 0x99, 0x96, 0x72, 0x5d, 0xec, 0x64, 0xf9, 0xea, 0x6b, - 0xfe, 0x54, 0xe7, 0x39, 0x7a, 0x52, 0xd3, 0x34, 0xbe, 0xdf, 0xc9, 0xf2, 0xf7, 0xaf, 0x9f, 0xd8, 0x3c, 0x5f, 0x8d, - 0x6b, 0x7a, 0xcb, 0xf9, 0x47, 0x97, 0x69, 0xa2, 0xab, 0x1a, 0x3f, 0xf9, 0xbb, 0xcd, 0xf5, 0xa4, 0xa6, 0xd7, 0x52, - 0x54, 0xcb, 0x9d, 0xa2, 0x8e, 0xbe, 0x3e, 0xfa, 0x3b, 0xff, 0xda, 0x74, 0xef, 0xa8, 0xa6, 0x7f, 0xae, 0xe3, 0xa2, - 0xe2, 0xc5, 0x4e, 0x71, 0x7f, 0xfb, 0xfb, 0xdf, 0x9f, 0xd8, 0x8c, 0x4f, 0x6a, 0x7a, 0xcf, 0xe3, 0x8e, 0xb6, 0x4f, - 0x9e, 0x3e, 0xe1, 0x7f, 0xab, 0x6b, 0xfa, 0x33, 0xf3, 0x83, 0xa3, 0x9e, 0x66, 0x9e, 0x1e, 0x3e, 0x91, 0x4d, 0xd4, - 0x80, 0xa1, 0x87, 0x06, 0x90, 0x4b, 0xab, 0xa6, 0xd9, 0xe3, 0x95, 0x0b, 0x6e, 0xdf, 0xe7, 0x71, 0x1a, 0xaf, 0xe0, - 0x20, 0xd8, 0xa0, 0x71, 0x56, 0x01, 0x9c, 0x2a, 0xf0, 0x9e, 0x51, 0x49, 0xb3, 0x52, 0xfe, 0x93, 0xf3, 0x8f, 0x30, - 0x68, 0x08, 0x69, 0xa3, 0x22, 0x03, 0xbd, 0x5d, 0xe9, 0xc8, 0x46, 0xe8, 0xbf, 0xd9, 0x8c, 0x83, 0xe3, 0xc3, 0xe8, - 0xf5, 0xfb, 0x61, 0xc1, 0x44, 0x58, 0x10, 0x42, 0xff, 0x08, 0x0b, 0x70, 0x28, 0x29, 0x98, 0x97, 0xcf, 0xf8, 0x9e, - 0x6b, 0xa3, 0xb0, 0x10, 0x44, 0x77, 0x91, 0x7d, 0x40, 0xd5, 0xa3, 0xef, 0xd0, 0x0d, 0xf1, 0xb2, 0xc2, 0x82, 0xa1, - 0x55, 0x0d, 0xcc, 0x10, 0x14, 0xff, 0x9a, 0x87, 0x12, 0x7c, 0xe2, 0x01, 0x3e, 0x7a, 0x4c, 0x66, 0x5c, 0x5d, 0x6b, - 0xdf, 0x5e, 0x86, 0x05, 0x0d, 0x74, 0xdb, 0x21, 0xe8, 0x40, 0xe4, 0xbf, 0x00, 0x4f, 0x81, 0x81, 0x0f, 0x0b, 0xbb, - 0x94, 0xbb, 0xfe, 0xea, 0x3f, 0x1b, 0xd6, 0xd1, 0x85, 0x1f, 0xfd, 0xd9, 0xba, 0xb0, 0x67, 0x64, 0x2a, 0x8f, 0xcb, - 0xe1, 0x64, 0x3a, 0x18, 0x48, 0x17, 0xc7, 0xed, 0x34, 0x9b, 0xff, 0x3c, 0x97, 0x8b, 0x05, 0xea, 0xbe, 0x71, 0x5e, - 0x67, 0xfa, 0x6f, 0xa4, 0x9d, 0x0f, 0x5e, 0x9f, 0xfe, 0x7a, 0x7e, 0x76, 0xfa, 0x12, 0x9c, 0x0f, 0x3e, 0xbc, 0xf8, - 0xee, 0xc5, 0x7b, 0x15, 0xdc, 0x5d, 0xcd, 0x79, 0xbf, 0xef, 0xa4, 0x3e, 0x21, 0x1f, 0x56, 0xe4, 0x30, 0x8c, 0x1f, - 0x17, 0xca, 0xe8, 0x81, 0x1c, 0x33, 0x0b, 0x85, 0x0c, 0x55, 0xd4, 0xf6, 0x77, 0x39, 0x9c, 0x78, 0x60, 0x16, 0xf7, - 0x0d, 0x11, 0xae, 0xdf, 0x72, 0x1b, 0x64, 0x4d, 0x9e, 0x78, 0xfd, 0xe0, 0x64, 0x2a, 0x1d, 0x5b, 0x58, 0x30, 0x28, - 0x1b, 0xda, 0x74, 0x9a, 0xcd, 0x8b, 0x85, 0x6d, 0x97, 0x5b, 0x20, 0xa3, 0x34, 0xbb, 0xbc, 0x0c, 0x15, 0x74, 0xf5, - 0x09, 0x68, 0x00, 0x4c, 0xa3, 0x0a, 0xd7, 0x22, 0x3e, 0xf3, 0xcb, 0x8f, 0xc6, 0x5e, 0xf3, 0xee, 0x50, 0xf7, 0x64, - 0x9a, 0x55, 0x35, 0x06, 0x74, 0x30, 0xa1, 0xdc, 0x0d, 0xba, 0x09, 0x26, 0xa3, 0xda, 0xf2, 0xf3, 0xbc, 0x5a, 0x98, - 0xe6, 0xb8, 0x61, 0xa8, 0xbc, 0x92, 0xd7, 0xb2, 0x81, 0xc8, 0x40, 0x32, 0x0c, 0x7b, 0x34, 0x46, 0x91, 0xfa, 0xc1, - 0xae, 0x77, 0xfc, 0x26, 0x97, 0x10, 0x4d, 0x31, 0x03, 0xe9, 0xfc, 0xa9, 0x50, 0xce, 0xe5, 0x92, 0xf1, 0xb9, 0x58, - 0x9c, 0x80, 0xdb, 0xf9, 0x5c, 0x2c, 0x22, 0x0c, 0xca, 0x97, 0x41, 0xac, 0x12, 0xb0, 0x7b, 0x71, 0x10, 0xbe, 0x9d, - 0xd0, 0x06, 0x76, 0x03, 0x49, 0x36, 0x28, 0xed, 0x4a, 0x43, 0x94, 0x3b, 0xe5, 0xd1, 0x06, 0x91, 0x87, 0x58, 0x35, - 0xaf, 0xda, 0x9e, 0x6c, 0xe6, 0x62, 0x82, 0xab, 0x2c, 0x66, 0x72, 0x1a, 0x1f, 0xb3, 0x62, 0x1a, 0x43, 0x29, 0x71, - 0x9a, 0x86, 0x31, 0x9d, 0x50, 0x41, 0x48, 0xc2, 0xf8, 0x3c, 0x5e, 0xd0, 0x04, 0xa5, 0x04, 0x21, 0x84, 0xfc, 0x18, - 0xa1, 0x6d, 0x0e, 0x2c, 0x79, 0xbb, 0xfd, 0x3c, 0xfd, 0xdc, 0x8e, 0xe1, 0x32, 0x2a, 0x42, 0x37, 0xe8, 0xac, 0xe1, - 0xdf, 0x88, 0x0a, 0x1a, 0x63, 0xc5, 0x10, 0x04, 0xbc, 0xc0, 0xa8, 0x84, 0x05, 0x89, 0x59, 0x05, 0x51, 0x04, 0xca, - 0x79, 0xbc, 0x60, 0x05, 0x6d, 0xda, 0x9c, 0xc6, 0xda, 0x24, 0xa8, 0xe7, 0xb0, 0xd4, 0x0e, 0xa4, 0x52, 0x21, 0xf6, - 0xf8, 0x4c, 0x44, 0x37, 0xda, 0xd0, 0x00, 0x50, 0xa0, 0x94, 0x5c, 0xfc, 0xf6, 0xf3, 0x3d, 0xdc, 0x14, 0xf4, 0x3f, - 0xdb, 0x98, 0x68, 0x67, 0xb9, 0x3a, 0xf4, 0xe6, 0x0b, 0x1a, 0xe7, 0x39, 0x84, 0x62, 0x33, 0x08, 0xe4, 0x22, 0xab, - 0x20, 0xa2, 0xc5, 0x7d, 0x60, 0x42, 0xc2, 0x41, 0x9b, 0x7e, 0x86, 0xd4, 0x86, 0x98, 0x5c, 0x79, 0x62, 0x60, 0xb7, - 0x55, 0x82, 0x80, 0x23, 0x3d, 0xcf, 0xfe, 0x6a, 0x62, 0xac, 0x69, 0x6a, 0x66, 0xe2, 0x6d, 0x28, 0x44, 0x83, 0x16, - 0x44, 0x33, 0x78, 0xff, 0x5c, 0x73, 0xbc, 0xea, 0xc0, 0x0f, 0x78, 0xe7, 0xe2, 0xcc, 0xab, 0x99, 0x47, 0xe4, 0xd4, - 0x47, 0x39, 0xa2, 0x5f, 0xf2, 0xb0, 0x1a, 0xe9, 0x64, 0x8c, 0x95, 0xc4, 0x41, 0x6f, 0x83, 0x05, 0x73, 0x42, 0x57, - 0x3c, 0xb4, 0x7c, 0xfc, 0x4b, 0x64, 0x32, 0x4a, 0x6a, 0xac, 0xe8, 0x4a, 0x8b, 0x11, 0xe7, 0x35, 0xcc, 0xd2, 0x64, - 0x45, 0x17, 0x0b, 0x4d, 0x9a, 0x85, 0x32, 0x0d, 0xf0, 0x09, 0xb4, 0x18, 0xb9, 0x87, 0x9a, 0x36, 0x10, 0x1a, 0x76, - 0x87, 0x80, 0x8f, 0xdc, 0x43, 0x87, 0xff, 0x9f, 0x67, 0x17, 0x88, 0xb4, 0x37, 0x37, 0x91, 0xf1, 0x48, 0xdd, 0xc0, - 0x41, 0x31, 0x3e, 0xf6, 0xcd, 0xc4, 0xcf, 0x9c, 0xd1, 0x87, 0xa4, 0xf2, 0x1d, 0x3e, 0x58, 0xfe, 0x78, 0x53, 0x33, - 0x2b, 0x23, 0x58, 0x0f, 0xdb, 0x2d, 0x2e, 0x88, 0xb6, 0x0b, 0x20, 0xf5, 0x8c, 0x57, 0x0b, 0xdf, 0x78, 0x35, 0xde, - 0x63, 0xbc, 0xea, 0xce, 0xd4, 0x30, 0x27, 0x1b, 0xd4, 0x67, 0x29, 0x79, 0x7e, 0x8e, 0x32, 0xc1, 0xa6, 0xcb, 0x59, - 0x49, 0x55, 0x2a, 0xa1, 0xbd, 0xd8, 0xcf, 0x18, 0xdf, 0x11, 0x8c, 0xb3, 0xe2, 0x30, 0x12, 0xa8, 0x4a, 0x25, 0x75, - 0xd8, 0x2b, 0x40, 0x3d, 0x06, 0xef, 0x0d, 0x86, 0xa8, 0x91, 0xb1, 0x9b, 0x36, 0x10, 0x1a, 0x1a, 0xeb, 0xd1, 0x9e, - 0xb5, 0x1e, 0xdd, 0x6e, 0x2b, 0xe3, 0x6f, 0x27, 0xd7, 0x45, 0x82, 0xa8, 0xc2, 0x6a, 0x34, 0x01, 0xde, 0x34, 0xb1, - 0xb7, 0x25, 0xa7, 0xb4, 0xc0, 0xf0, 0xd9, 0x7f, 0x84, 0xa5, 0x53, 0x49, 0x94, 0x64, 0x56, 0x46, 0x03, 0x77, 0x0e, - 0x3e, 0x8f, 0x2b, 0x58, 0x03, 0x10, 0xc9, 0x11, 0x3d, 0x5c, 0xff, 0x08, 0xa5, 0xcb, 0x2c, 0xc9, 0x4c, 0x42, 0x66, - 0x2e, 0xd2, 0x76, 0xd6, 0xc1, 0xc4, 0x99, 0xd4, 0x7a, 0x63, 0x21, 0x87, 0x06, 0xf9, 0x01, 0x94, 0x21, 0x0e, 0x9f, - 0x7c, 0x30, 0xa1, 0x52, 0x85, 0x52, 0x6d, 0x74, 0xb3, 0x1b, 0x78, 0xe5, 0x43, 0x76, 0xcd, 0xcb, 0x2a, 0xbe, 0x5e, - 0x19, 0x4b, 0x62, 0xce, 0xf6, 0xb9, 0xed, 0x51, 0x61, 0x5e, 0xbd, 0x79, 0xf1, 0xdd, 0x69, 0xe3, 0xd5, 0x2e, 0xe2, - 0x68, 0x08, 0xb6, 0x15, 0x63, 0x8c, 0xde, 0xe2, 0xd3, 0x60, 0xa2, 0x5c, 0x23, 0xd0, 0xbb, 0x14, 0xf4, 0xdb, 0x9f, - 0xeb, 0x09, 0x78, 0xcd, 0xf5, 0xf2, 0x4b, 0x3e, 0x02, 0x96, 0xa8, 0xd0, 0xb3, 0xc2, 0xdc, 0xac, 0xcc, 0xf6, 0x76, - 0x2b, 0x32, 0xd3, 0xae, 0x34, 0x32, 0x10, 0xaf, 0xb6, 0xc3, 0x58, 0xb8, 0x74, 0x4d, 0xb7, 0x83, 0x5d, 0x2d, 0x3d, - 0x4b, 0xe4, 0xed, 0xb6, 0x84, 0x0e, 0xd9, 0x01, 0xf7, 0x5e, 0xc6, 0x77, 0xf0, 0xb2, 0xf4, 0xba, 0xd9, 0x0c, 0x9e, - 0x00, 0x66, 0xc2, 0x85, 0xb3, 0x2c, 0x8e, 0x19, 0x4f, 0x42, 0x15, 0x9b, 0xab, 0x21, 0xf2, 0x56, 0x84, 0xd6, 0xec, - 0xaf, 0x50, 0x8c, 0xc0, 0xee, 0xe4, 0xec, 0x63, 0xb6, 0x9a, 0x2d, 0x01, 0x35, 0xff, 0x3a, 0x13, 0x40, 0x73, 0xed, - 0x5a, 0xb0, 0x4d, 0xa1, 0xcd, 0x75, 0xfd, 0x2c, 0x5e, 0xc5, 0x09, 0xa8, 0x6e, 0xc0, 0x5b, 0xe4, 0x5e, 0x8b, 0xae, - 0x0c, 0xba, 0x28, 0x7d, 0xa0, 0x1c, 0x4b, 0x0a, 0x1d, 0x7d, 0xef, 0x09, 0x75, 0xee, 0x19, 0xc0, 0x25, 0x8d, 0x9a, - 0xa7, 0x5a, 0xca, 0x58, 0x00, 0x2c, 0x74, 0x30, 0x53, 0x64, 0x2b, 0xba, 0x33, 0x98, 0x14, 0xf0, 0xd6, 0x00, 0x7f, - 0x88, 0xac, 0x52, 0x77, 0xc5, 0x32, 0x2c, 0x3d, 0xfb, 0xeb, 0x7e, 0x3f, 0xf6, 0xec, 0xaf, 0x2f, 0x35, 0xad, 0x8b, - 0xdb, 0x0d, 0x20, 0x35, 0x06, 0x10, 0x39, 0xd5, 0x03, 0x61, 0x22, 0x8a, 0x35, 0x7d, 0xff, 0x4e, 0x4d, 0x16, 0x05, - 0x42, 0xbf, 0x53, 0xaf, 0x27, 0x25, 0x01, 0x9d, 0x5a, 0xc5, 0x4e, 0x06, 0xda, 0xec, 0x03, 0x02, 0xa2, 0xfa, 0x19, - 0xd9, 0x7c, 0xa1, 0x9c, 0x8b, 0x55, 0xf8, 0xf0, 0x31, 0x85, 0x80, 0xc2, 0x1d, 0x35, 0x3a, 0x6f, 0x43, 0x24, 0x50, - 0x56, 0x28, 0x62, 0xcd, 0x8b, 0xb5, 0x24, 0x64, 0x3e, 0x5e, 0xa0, 0xe0, 0xca, 0x01, 0xbb, 0x72, 0x36, 0x19, 0x96, - 0x11, 0x67, 0xe1, 0xfe, 0x6f, 0x26, 0x0b, 0x82, 0x9a, 0x2b, 0x3f, 0x90, 0xe3, 0x4e, 0xa6, 0xc6, 0x9e, 0x6a, 0xd4, - 0x20, 0x98, 0x8c, 0x20, 0x30, 0xdc, 0xf0, 0x33, 0x3e, 0x3e, 0x5a, 0x10, 0x50, 0x91, 0x59, 0xb3, 0x10, 0xf3, 0xe2, - 0xf8, 0x2b, 0x40, 0x8d, 0x19, 0x1d, 0x3d, 0x9d, 0x72, 0x06, 0x87, 0x28, 0x1d, 0x83, 0x8c, 0x56, 0xc0, 0x6f, 0xa1, - 0x7e, 0xb7, 0x4e, 0x7c, 0x1f, 0xfa, 0x55, 0xd0, 0xcb, 0x18, 0x18, 0x8e, 0x68, 0x72, 0x18, 0xf2, 0xc1, 0x64, 0x00, - 0xda, 0x12, 0x6f, 0xf7, 0xb5, 0xb4, 0xe2, 0xe6, 0x74, 0xe9, 0x74, 0xff, 0xa4, 0x4d, 0x90, 0x44, 0x2a, 0x59, 0xa9, - 0x88, 0x01, 0x84, 0xb2, 0x54, 0xdb, 0x64, 0x09, 0x96, 0x15, 0x66, 0x49, 0x73, 0x83, 0x92, 0xb8, 0xbb, 0x19, 0x38, - 0x46, 0xcd, 0x3a, 0x0d, 0xcb, 0x96, 0x1b, 0x35, 0xc0, 0xe7, 0x24, 0xac, 0xb0, 0x37, 0x9c, 0x99, 0xf4, 0xce, 0x74, - 0xb8, 0x3a, 0xe6, 0xec, 0x35, 0x47, 0x30, 0x8e, 0x04, 0x6f, 0x3c, 0x74, 0xc9, 0x34, 0x54, 0x64, 0xca, 0x38, 0x98, - 0xf6, 0x00, 0xf7, 0x9e, 0x83, 0x71, 0x18, 0x1b, 0x54, 0x96, 0xd4, 0xa7, 0xde, 0x5d, 0x08, 0x04, 0x69, 0xad, 0x97, - 0xf9, 0x0c, 0x4f, 0xcf, 0x08, 0x65, 0x7f, 0xc8, 0xe1, 0x0b, 0xb0, 0xa3, 0x20, 0x27, 0x13, 0xfe, 0xf4, 0xf1, 0x6e, - 0xa0, 0x2a, 0x3e, 0x08, 0x0e, 0x62, 0x91, 0x1e, 0x04, 0x03, 0x01, 0xbf, 0x0a, 0x7e, 0x50, 0x49, 0x79, 0x70, 0x19, - 0x17, 0x07, 0xf1, 0x2a, 0x2e, 0xaa, 0x83, 0xdb, 0xac, 0x5a, 0x1e, 0x98, 0x0e, 0x01, 0x34, 0x6f, 0x30, 0x88, 0x07, - 0xc1, 0x41, 0x30, 0x28, 0xcc, 0xd4, 0xae, 0x58, 0xd9, 0x38, 0xce, 0x4c, 0x88, 0xb2, 0xa0, 0x19, 0x20, 0xac, 0x71, - 0x1a, 0x00, 0x9f, 0xba, 0x66, 0x29, 0xbd, 0xc4, 0x70, 0x03, 0x62, 0xba, 0x86, 0x3e, 0x00, 0x8f, 0xbc, 0xa6, 0x31, - 0x2c, 0x81, 0xcb, 0xc1, 0x80, 0xac, 0x21, 0x72, 0xc1, 0x9a, 0xda, 0x20, 0x0e, 0xe1, 0x5a, 0xd9, 0x69, 0xef, 0x02, - 0x33, 0x6d, 0xb7, 0x80, 0xa8, 0x3c, 0x21, 0xfd, 0xbe, 0xfd, 0x86, 0xfa, 0x17, 0xec, 0x25, 0xd8, 0x5f, 0x15, 0x55, - 0x98, 0x48, 0xa5, 0xf9, 0xbe, 0x62, 0x27, 0x03, 0x15, 0x71, 0x78, 0xc7, 0x91, 0xa2, 0x8d, 0xca, 0x65, 0xd9, 0x93, - 0x65, 0xc3, 0x57, 0xe2, 0x9a, 0x3b, 0x3f, 0xae, 0x4a, 0xca, 0xbc, 0xca, 0x56, 0x8a, 0xfd, 0x9b, 0x71, 0xcd, 0xfd, - 0x81, 0xf5, 0x67, 0xf3, 0x15, 0x5c, 0x5b, 0xbd, 0x77, 0x4d, 0xae, 0x11, 0x39, 0x4b, 0x28, 0x97, 0xd4, 0x36, 0x0f, - 0x6f, 0xe9, 0xfb, 0xfc, 0xea, 0xdb, 0x4c, 0xa7, 0xf1, 0x59, 0x85, 0x85, 0x0b, 0xd1, 0x8a, 0xe0, 0xd0, 0x90, 0x8b, - 0xe6, 0x11, 0x60, 0xae, 0x7d, 0xb6, 0x82, 0x82, 0xd4, 0xe7, 0x15, 0x7a, 0xb7, 0x42, 0xc2, 0x4b, 0xcd, 0x2e, 0x3d, - 0x0c, 0xa4, 0x8c, 0xdb, 0x43, 0x4b, 0x98, 0xb4, 0xbc, 0x08, 0xef, 0xbd, 0xe6, 0x26, 0xf7, 0x3c, 0xc4, 0xe8, 0x45, - 0x9e, 0x9d, 0x80, 0xb1, 0xee, 0x92, 0x9d, 0x0d, 0x4f, 0xfc, 0x86, 0xe7, 0xac, 0x45, 0xa3, 0xe9, 0x92, 0x25, 0xfd, - 0x7e, 0x0c, 0x26, 0xde, 0x29, 0xcb, 0xe1, 0x57, 0xbe, 0xa0, 0x6b, 0x06, 0x98, 0x62, 0xf4, 0x12, 0x12, 0x52, 0x44, - 0x22, 0x59, 0xab, 0x93, 0xe4, 0x13, 0xdd, 0x05, 0x60, 0xf4, 0xcb, 0x59, 0x1a, 0x2d, 0xf7, 0x9a, 0x59, 0x20, 0x79, - 0x86, 0xbe, 0xeb, 0x60, 0x7b, 0x63, 0x1f, 0xa4, 0x9c, 0x1f, 0x8b, 0xe9, 0x60, 0xc0, 0x89, 0x86, 0x1b, 0x2f, 0x95, - 0xb8, 0x56, 0xb7, 0xb8, 0x63, 0x18, 0x4b, 0x7d, 0x5b, 0xc4, 0xe0, 0x80, 0x5d, 0xb4, 0xb2, 0xdb, 0x07, 0xd8, 0x57, - 0x8e, 0x77, 0xa9, 0xb2, 0x3b, 0x3d, 0x66, 0x9a, 0xcb, 0x56, 0x93, 0x4e, 0x2a, 0xf6, 0x13, 0xf9, 0x26, 0x77, 0xd0, - 0xe5, 0x72, 0xac, 0x79, 0xcb, 0x01, 0xa8, 0xe8, 0x47, 0x8a, 0xea, 0x7e, 0x86, 0x23, 0xcc, 0x83, 0x75, 0x9b, 0x4f, - 0x0e, 0x4d, 0x81, 0x43, 0xe4, 0x49, 0x1b, 0x4d, 0x01, 0xdd, 0xbb, 0x78, 0xdc, 0xd5, 0x6f, 0x4b, 0x77, 0x81, 0x12, - 0xed, 0x54, 0xdc, 0xf0, 0x63, 0xa2, 0x4e, 0x67, 0xda, 0x10, 0xfa, 0x57, 0x46, 0xdc, 0x5f, 0x1a, 0x57, 0xf1, 0xa6, - 0x77, 0xf9, 0x8c, 0x43, 0x9d, 0xdd, 0x10, 0x0a, 0xc0, 0x55, 0x7b, 0x3a, 0x75, 0x63, 0x48, 0xaf, 0x94, 0xe8, 0x36, - 0x38, 0xd8, 0x5e, 0x9f, 0x71, 0x14, 0xfd, 0x18, 0x35, 0xf2, 0x6d, 0x24, 0x1e, 0xcb, 0x41, 0xfc, 0xb8, 0xa0, 0xcb, - 0x48, 0x3c, 0x2e, 0x06, 0xf1, 0x63, 0x59, 0xd7, 0xbb, 0xe7, 0xca, 0xfe, 0x3e, 0x22, 0xcf, 0xba, 0xb3, 0x97, 0x4a, - 0xd8, 0x18, 0x78, 0x76, 0x2d, 0x20, 0x9c, 0x82, 0x27, 0xb2, 0xb5, 0xf4, 0xa1, 0x73, 0xbb, 0x8f, 0x2d, 0x93, 0x04, - 0x41, 0xcf, 0xdb, 0x6c, 0x12, 0xc5, 0xce, 0x36, 0x8f, 0x3e, 0x9c, 0x02, 0x09, 0xdd, 0x6e, 0x9b, 0x75, 0xb5, 0x06, - 0x14, 0xd3, 0x70, 0xcc, 0x0f, 0x8b, 0xd1, 0xad, 0xef, 0xae, 0x7f, 0x58, 0x8c, 0x96, 0x64, 0x38, 0x31, 0x93, 0x1f, - 0x9f, 0x8c, 0x67, 0x71, 0x34, 0xa9, 0x3b, 0x4e, 0x0b, 0x8d, 0x7f, 0xea, 0xdd, 0x42, 0x11, 0x38, 0x15, 0x23, 0x38, - 0x72, 0x2a, 0x94, 0x93, 0x52, 0x03, 0xc3, 0xff, 0xa0, 0xda, 0xd1, 0xa6, 0xbd, 0x8e, 0xab, 0x64, 0x99, 0x89, 0x2b, - 0x1d, 0x3e, 0x5c, 0x47, 0x17, 0xb7, 0x01, 0xed, 0xbc, 0xcb, 0xb4, 0xe3, 0xd7, 0x49, 0x83, 0x9e, 0xb8, 0x9a, 0x19, - 0x70, 0xeb, 0x7e, 0x84, 0x66, 0x08, 0x8c, 0x96, 0xe7, 0xef, 0x10, 0x73, 0xfb, 0x17, 0x65, 0xf3, 0xab, 0x68, 0x9f, - 0x23, 0x23, 0x65, 0x9b, 0x8c, 0x54, 0x60, 0x84, 0x29, 0x45, 0x12, 0x57, 0x21, 0x04, 0xb2, 0xff, 0x9c, 0xe2, 0x5a, - 0x2c, 0xbd, 0xd7, 0x20, 0x4c, 0xb0, 0x5d, 0xd0, 0x7e, 0x75, 0x3b, 0xb7, 0x95, 0x16, 0x7b, 0xa4, 0xbe, 0xcf, 0x9d, - 0xed, 0x8a, 0x26, 0x7f, 0x9f, 0x37, 0xa0, 0x0d, 0x20, 0xca, 0x7d, 0x7d, 0x54, 0x02, 0x27, 0x23, 0x6e, 0x28, 0x31, - 0x7a, 0x41, 0x57, 0x27, 0x72, 0xcf, 0x4e, 0xcd, 0x9b, 0x8a, 0x99, 0x8a, 0x2b, 0xdf, 0xec, 0x99, 0xff, 0x60, 0x28, - 0xa8, 0x00, 0x03, 0x6f, 0x73, 0xc6, 0xa3, 0x03, 0xdd, 0xad, 0xd1, 0x69, 0xc1, 0x66, 0x41, 0x5d, 0xd6, 0x6d, 0x1b, - 0x0f, 0x1a, 0x71, 0x50, 0x14, 0xab, 0x42, 0x8d, 0x84, 0x27, 0x02, 0x01, 0x53, 0x76, 0xcd, 0x23, 0x23, 0xa8, 0xe9, - 0x4d, 0x28, 0x6c, 0x28, 0xf8, 0xab, 0x44, 0x35, 0xbd, 0x09, 0x6d, 0x32, 0x71, 0x9a, 0x41, 0x04, 0x33, 0x62, 0xbb, - 0xdf, 0x02, 0xda, 0xdc, 0x9a, 0xd1, 0xa6, 0xae, 0xad, 0xb6, 0x0a, 0xb9, 0xa4, 0x48, 0x59, 0xfe, 0x3b, 0x35, 0x15, - 0x94, 0xd4, 0x72, 0xd1, 0x9b, 0x34, 0x5d, 0xf4, 0x78, 0x66, 0x24, 0x81, 0xca, 0x2d, 0x77, 0x8c, 0xfe, 0x10, 0x16, - 0x78, 0xc4, 0xc4, 0x89, 0x05, 0x73, 0xab, 0x13, 0x96, 0xcd, 0xc5, 0x62, 0xb4, 0x92, 0x10, 0x36, 0xf8, 0x98, 0x65, - 0xf3, 0x52, 0x3f, 0x84, 0xbe, 0xb0, 0xf4, 0x2d, 0xd8, 0xc5, 0x06, 0x2b, 0x59, 0x06, 0xe0, 0x7b, 0x41, 0x37, 0x2b, - 0x59, 0x46, 0x52, 0x75, 0x3f, 0xae, 0xb1, 0x04, 0x95, 0x56, 0xa8, 0xb4, 0xa4, 0xc6, 0x82, 0xc0, 0x57, 0x55, 0x97, - 0x0f, 0xc9, 0xae, 0x02, 0xf5, 0xd4, 0x51, 0x03, 0x4e, 0x81, 0xaa, 0x02, 0x0b, 0x92, 0xa0, 0x32, 0x74, 0x55, 0x60, - 0x5a, 0x81, 0x69, 0xa6, 0x0a, 0x17, 0x65, 0x76, 0x28, 0xcd, 0x7a, 0xc9, 0x67, 0xf1, 0x20, 0x4c, 0x86, 0x31, 0x79, - 0x8c, 0x50, 0xfb, 0x87, 0x79, 0x14, 0x6b, 0xb9, 0xe4, 0xca, 0xf9, 0xc5, 0xdf, 0x7e, 0xc2, 0x5e, 0xf7, 0x1c, 0x83, - 0x05, 0x38, 0x4b, 0xdb, 0xeb, 0x4c, 0xbc, 0x93, 0xad, 0xe0, 0x38, 0x98, 0x45, 0x39, 0xac, 0x7a, 0x72, 0x44, 0x73, - 0x91, 0x6b, 0xef, 0x22, 0x44, 0x0e, 0x32, 0x7b, 0x0c, 0xb0, 0x1b, 0xe1, 0xeb, 0xd0, 0xda, 0xdc, 0xea, 0x0a, 0xf1, - 0x37, 0x4a, 0x24, 0x7e, 0x94, 0xf2, 0xe3, 0x7a, 0xa5, 0x72, 0x55, 0x06, 0x8f, 0x55, 0x37, 0x83, 0x67, 0xda, 0xf7, - 0x58, 0xfb, 0xb7, 0xb6, 0x9b, 0xe3, 0xbd, 0x07, 0x0f, 0x5a, 0xff, 0x5b, 0x4f, 0x42, 0x68, 0xaf, 0x9c, 0xa4, 0xee, - 0xa8, 0xd1, 0x33, 0x93, 0x35, 0xa2, 0x12, 0xa6, 0x76, 0xa7, 0x72, 0x0c, 0xd4, 0x74, 0x00, 0xd7, 0x12, 0x35, 0x41, - 0x4f, 0x0a, 0x36, 0x86, 0x23, 0xce, 0xe2, 0xa0, 0x1d, 0xc7, 0x28, 0x5e, 0xce, 0x95, 0x78, 0x39, 0x3f, 0x61, 0x1c, - 0xa0, 0xb5, 0x00, 0xa9, 0x5e, 0xc3, 0x7e, 0xe6, 0x0a, 0x16, 0xd8, 0xdc, 0xf9, 0x8e, 0x2c, 0x90, 0x21, 0x4e, 0x36, - 0xc7, 0xc9, 0x1e, 0xd7, 0x7a, 0xee, 0x05, 0x3e, 0x4e, 0xea, 0x85, 0x57, 0x57, 0xd9, 0xae, 0x6b, 0xc9, 0xca, 0x79, - 0x31, 0x98, 0x40, 0x50, 0x96, 0x72, 0x5e, 0x0c, 0x27, 0x0b, 0x9a, 0xc3, 0x8f, 0x45, 0x03, 0x1d, 0x62, 0x39, 0x48, - 0xe0, 0xd2, 0xd9, 0x63, 0xc0, 0x1b, 0x4a, 0x2d, 0xee, 0xc6, 0x3a, 0x72, 0xac, 0xa3, 0x38, 0x0c, 0x63, 0xc0, 0x95, - 0x75, 0x02, 0xef, 0xfd, 0xd7, 0xc7, 0x26, 0x20, 0xab, 0x76, 0x85, 0x57, 0xa3, 0xdc, 0x75, 0xa5, 0xd1, 0x97, 0x94, - 0x9e, 0xf0, 0x82, 0xa7, 0x92, 0xed, 0xb6, 0x67, 0xe0, 0x6c, 0x89, 0x87, 0xc4, 0x3b, 0x46, 0xf4, 0x62, 0xda, 0xc8, - 0xcc, 0x09, 0x9c, 0xd9, 0xee, 0xb2, 0x8d, 0xf9, 0xb1, 0x03, 0x1c, 0x2c, 0x82, 0x90, 0xb8, 0x21, 0x0c, 0x13, 0x3b, - 0x29, 0x87, 0x5a, 0x08, 0xd7, 0xb5, 0xf0, 0x3a, 0x4e, 0xcb, 0x18, 0x5c, 0xa4, 0xb5, 0x6d, 0xe2, 0x1e, 0xba, 0xee, - 0xf9, 0x31, 0xb7, 0x3a, 0x46, 0x5b, 0x48, 0xbf, 0x1d, 0x9d, 0x3e, 0x70, 0x18, 0x80, 0xa6, 0x07, 0xb3, 0xaa, 0x7d, - 0x26, 0x71, 0x73, 0xda, 0x09, 0x42, 0x22, 0x10, 0x45, 0xe9, 0x8c, 0x30, 0xfd, 0x3b, 0xcd, 0x65, 0x15, 0xad, 0x1e, - 0xe4, 0x99, 0x43, 0x9e, 0x85, 0xde, 0xf6, 0xa0, 0x55, 0x73, 0x37, 0x18, 0x27, 0x6e, 0xb7, 0x77, 0xfe, 0xdf, 0xb2, - 0xae, 0xad, 0xd6, 0x88, 0xc7, 0xed, 0xea, 0x07, 0x8d, 0xbd, 0xda, 0x53, 0x31, 0x60, 0x56, 0xd2, 0x3b, 0xa3, 0x4a, - 0x5e, 0x64, 0xbc, 0xc4, 0x93, 0x6a, 0xd5, 0xf0, 0xf1, 0xbe, 0xcd, 0x46, 0xe6, 0x81, 0x4c, 0x01, 0xf1, 0xfc, 0x36, - 0x35, 0xea, 0xe3, 0x14, 0x25, 0xe0, 0xef, 0x74, 0x7c, 0x23, 0xfa, 0xd1, 0xbe, 0xb8, 0xe2, 0xd5, 0xdb, 0x5b, 0x61, - 0x5e, 0x3c, 0xb7, 0x3a, 0x7f, 0xfa, 0xba, 0xf0, 0xa1, 0xc3, 0x51, 0x7b, 0x07, 0x45, 0x96, 0x4c, 0x9c, 0x4c, 0x8c, - 0xac, 0x4d, 0xcc, 0x3e, 0x2a, 0xb8, 0x98, 0xa8, 0x42, 0xcf, 0x3a, 0x7b, 0xc2, 0x14, 0xa0, 0x6f, 0x1c, 0xa3, 0x92, - 0x31, 0x2c, 0x18, 0xa8, 0xd3, 0x94, 0x10, 0x3d, 0x14, 0x33, 0x8c, 0x57, 0x0c, 0xa0, 0x30, 0x85, 0x02, 0x51, 0x74, - 0xf6, 0xe1, 0x40, 0x13, 0xfa, 0xfd, 0xdb, 0x54, 0x67, 0xa0, 0x65, 0x3d, 0x2d, 0x40, 0x54, 0x07, 0xd1, 0x56, 0x79, - 0x11, 0xfe, 0xb0, 0xa4, 0x65, 0x46, 0x97, 0x82, 0xa6, 0x82, 0x26, 0x19, 0xbd, 0xe4, 0x4a, 0x54, 0x7c, 0x29, 0x98, - 0xa2, 0xed, 0x86, 0xb0, 0xff, 0xd0, 0xa0, 0xeb, 0xad, 0x58, 0x6b, 0x68, 0x77, 0x82, 0x8c, 0xd0, 0x7c, 0xa1, 0x83, - 0x90, 0xa1, 0x72, 0x12, 0xba, 0x56, 0x69, 0xbc, 0x02, 0x97, 0x4c, 0xb3, 0xd1, 0x32, 0x2e, 0xc3, 0xc0, 0x7e, 0x15, - 0x58, 0x4c, 0x0e, 0x4c, 0x3a, 0x5b, 0x5f, 0x3c, 0x93, 0xd7, 0x2b, 0x29, 0xb8, 0xa8, 0x14, 0x44, 0xbf, 0xc1, 0x7d, - 0x37, 0x71, 0xd5, 0x59, 0xb3, 0x56, 0xfa, 0xd0, 0xb7, 0x3e, 0x6b, 0xe3, 0xbe, 0x30, 0x38, 0x06, 0x3b, 0x1f, 0x11, - 0x03, 0x69, 0x50, 0xe9, 0x16, 0x87, 0x26, 0x40, 0x97, 0x0e, 0x29, 0x64, 0xc9, 0x54, 0xa6, 0x4a, 0x50, 0xf1, 0x8d, - 0xdf, 0x4b, 0x59, 0x8d, 0xfe, 0x5c, 0xf3, 0xe2, 0xfe, 0x8c, 0xe7, 0x1c, 0xc7, 0x28, 0x48, 0x62, 0x71, 0x13, 0x97, - 0x01, 0xf1, 0x2d, 0xaf, 0x82, 0xa3, 0xd4, 0x84, 0x8d, 0xd9, 0xa9, 0x1a, 0xb5, 0x5e, 0x05, 0xfa, 0xca, 0x28, 0xdf, - 0x18, 0x0c, 0x4d, 0x44, 0x15, 0xf4, 0xbd, 0x56, 0xf7, 0xb4, 0xba, 0x61, 0x01, 0xf1, 0xe7, 0x4a, 0x2f, 0xd4, 0x7a, - 0xdd, 0x8c, 0xb9, 0x61, 0x22, 0x04, 0x8d, 0xbe, 0xaa, 0x17, 0x0e, 0x3f, 0x7f, 0xa3, 0x2c, 0x89, 0xe0, 0xc5, 0x26, - 0x5d, 0x17, 0x26, 0x96, 0x06, 0xd5, 0x01, 0x73, 0xa3, 0x4d, 0xce, 0xaf, 0x40, 0xf4, 0xe7, 0xac, 0x88, 0x26, 0x75, - 0x4d, 0x15, 0x82, 0x61, 0xb4, 0xb9, 0x6b, 0xa4, 0xd3, 0x7b, 0xf0, 0x72, 0x33, 0xd6, 0x48, 0xda, 0xd3, 0xb1, 0xa6, - 0x05, 0x2f, 0x57, 0x52, 0x94, 0x10, 0xdd, 0xb9, 0x37, 0xa6, 0xd7, 0x71, 0x26, 0xaa, 0x38, 0x13, 0xa7, 0xe5, 0x8a, - 0x27, 0xd5, 0x7b, 0xa8, 0x50, 0x1b, 0xe3, 0x60, 0xeb, 0xd5, 0xa8, 0xab, 0x70, 0xc8, 0xaf, 0x2e, 0x5f, 0xdc, 0xad, - 0x62, 0x91, 0xc2, 0xa8, 0xd7, 0xfb, 0x5e, 0x34, 0xa7, 0x63, 0x15, 0x17, 0x5c, 0x98, 0xa8, 0xc5, 0xb4, 0x62, 0x01, - 0xd7, 0x19, 0x03, 0xca, 0x55, 0xec, 0xce, 0x4c, 0xc5, 0x32, 0x8c, 0xcb, 0xf2, 0xc7, 0xac, 0xc4, 0x3b, 0x00, 0xb4, - 0x06, 0x4e, 0x8b, 0x99, 0x01, 0x01, 0xb9, 0xcf, 0x0d, 0x2e, 0x02, 0x0b, 0x8e, 0x9e, 0x8c, 0x57, 0x77, 0x01, 0xf5, - 0xde, 0x48, 0x75, 0x3d, 0x64, 0xc1, 0x78, 0xf4, 0x34, 0x70, 0xc8, 0x21, 0xfe, 0x47, 0x4f, 0x8e, 0xf6, 0x7f, 0x33, - 0x09, 0x48, 0x3d, 0x05, 0x55, 0x85, 0x51, 0x88, 0xc2, 0xb4, 0xbf, 0x5e, 0xab, 0x5b, 0xee, 0xdb, 0x8b, 0x92, 0x17, - 0x37, 0x10, 0xad, 0x9d, 0x4c, 0x33, 0x20, 0xe7, 0x52, 0x25, 0xc0, 0xa2, 0x88, 0xab, 0xaa, 0xc8, 0x2e, 0xc0, 0x44, - 0x09, 0x0d, 0xc0, 0xcc, 0xd3, 0x4b, 0x74, 0xf8, 0x88, 0xe6, 0x01, 0xf6, 0x29, 0x58, 0xd4, 0xa4, 0x2e, 0xa1, 0xb0, - 0xe4, 0x00, 0x83, 0xd5, 0xa9, 0xb8, 0xd2, 0x8e, 0x53, 0xaf, 0x7e, 0x8f, 0x96, 0x12, 0x63, 0xcd, 0xea, 0x79, 0x8a, - 0x2f, 0x4a, 0x99, 0xaf, 0x2b, 0xd0, 0x9e, 0x5f, 0x56, 0xd1, 0xd1, 0x93, 0xd5, 0xdd, 0x54, 0x75, 0x23, 0x82, 0x5e, - 0x4c, 0x15, 0xce, 0x5b, 0x12, 0xe7, 0x49, 0x38, 0x19, 0x8f, 0xbf, 0x38, 0x18, 0x1e, 0x40, 0x32, 0x99, 0xfe, 0x35, - 0x54, 0x8e, 0x5c, 0xc3, 0xc9, 0x78, 0x5c, 0xff, 0x5e, 0x9b, 0x30, 0xdf, 0xa6, 0x9e, 0x67, 0xbf, 0x1f, 0xab, 0xf5, - 0x7f, 0x72, 0x7c, 0xa8, 0x7f, 0xfc, 0x5e, 0xd7, 0xd3, 0xd7, 0x45, 0x38, 0xff, 0x57, 0xa8, 0xd6, 0xf7, 0x69, 0x51, - 0xc4, 0xf7, 0x35, 0x44, 0x36, 0x15, 0xce, 0xbb, 0x86, 0x7a, 0x64, 0x81, 0x1e, 0x91, 0xe9, 0xa5, 0x60, 0xf0, 0xcd, - 0xfb, 0x2a, 0x0c, 0x78, 0xb9, 0x1a, 0x72, 0x51, 0x65, 0xd5, 0xfd, 0x10, 0xf3, 0x04, 0xf8, 0xa9, 0x19, 0xc7, 0x67, - 0x85, 0x21, 0xbe, 0x97, 0x05, 0xe7, 0x7f, 0xf1, 0x50, 0x19, 0x8b, 0x8f, 0xd1, 0x58, 0x7c, 0x4c, 0x55, 0x37, 0x26, - 0x5f, 0x53, 0xdd, 0xb7, 0xc9, 0xd7, 0x60, 0x92, 0x95, 0xb5, 0xbf, 0x51, 0xc6, 0x9a, 0xd1, 0x98, 0xde, 0xbc, 0xcc, - 0xb3, 0x15, 0x5c, 0x0a, 0x96, 0xfa, 0x47, 0x4d, 0xe8, 0x7b, 0xde, 0xce, 0x3e, 0x1a, 0x8d, 0x9e, 0x15, 0x74, 0x34, - 0x1a, 0x7d, 0xcc, 0x6a, 0x42, 0x57, 0xa2, 0xe3, 0xfd, 0x7b, 0x4e, 0x2f, 0x64, 0x7a, 0x1f, 0x05, 0x01, 0x5d, 0x66, - 0x69, 0xca, 0x85, 0x2a, 0xeb, 0x2c, 0x6d, 0xe7, 0x55, 0x2d, 0x44, 0x20, 0x24, 0xdd, 0x46, 0x84, 0x64, 0x22, 0xf4, - 0xed, 0x4e, 0xcf, 0x46, 0xa3, 0xd1, 0x59, 0x6a, 0xaa, 0x75, 0x17, 0x94, 0xd7, 0x68, 0x4e, 0xe1, 0xfc, 0x14, 0xc0, - 0x1a, 0xc9, 0x44, 0x7f, 0x39, 0xfc, 0xef, 0xe1, 0x6c, 0x3e, 0x1e, 0x7e, 0x33, 0x5a, 0x3c, 0x3e, 0xa4, 0x41, 0xe0, - 0x83, 0x78, 0x87, 0xda, 0xba, 0x65, 0x5a, 0x1e, 0x8f, 0xa7, 0xa4, 0x1c, 0xb0, 0x27, 0xd6, 0xb7, 0xe8, 0x8b, 0x27, - 0x80, 0xcc, 0x8a, 0x22, 0xe5, 0xc0, 0x49, 0x43, 0xf1, 0x6a, 0xf6, 0x4a, 0x00, 0x5e, 0x9c, 0x8d, 0xec, 0x60, 0xb4, - 0xa2, 0xe3, 0x08, 0xca, 0xab, 0xad, 0xa9, 0x48, 0x8f, 0xb1, 0xcc, 0x44, 0x49, 0x1d, 0x4f, 0xcb, 0xdb, 0xac, 0x4a, - 0x96, 0x18, 0xe8, 0x29, 0x2e, 0x79, 0xf0, 0x45, 0x10, 0x95, 0xec, 0xe8, 0xe9, 0x54, 0xc1, 0x1d, 0x63, 0x52, 0xca, - 0xaf, 0x20, 0xf1, 0x9b, 0x31, 0x42, 0xc2, 0x12, 0xed, 0xc1, 0x89, 0x35, 0xbe, 0xcc, 0x65, 0x0c, 0x1e, 0xad, 0xa5, - 0xe6, 0xe1, 0xec, 0xc9, 0x68, 0xed, 0x51, 0x5a, 0xcd, 0x91, 0xd0, 0x9c, 0x50, 0x32, 0x79, 0x58, 0x52, 0xf9, 0xc5, - 0x04, 0xbd, 0xa4, 0xc0, 0xcd, 0x3c, 0x82, 0xe3, 0xdf, 0x5a, 0x7a, 0xe8, 0xe5, 0x93, 0xb2, 0xc3, 0xf9, 0xff, 0x2e, - 0xe9, 0x62, 0x70, 0xe8, 0x86, 0xe6, 0xad, 0x76, 0xe7, 0xad, 0x90, 0x71, 0xac, 0xc2, 0x67, 0x29, 0xb1, 0xc6, 0xb8, - 0x9c, 0x9d, 0x6c, 0x4c, 0x77, 0x46, 0x55, 0x91, 0x5d, 0x87, 0x44, 0xf7, 0xca, 0x81, 0x84, 0x06, 0x51, 0x36, 0xc2, - 0xf5, 0x03, 0xd6, 0x33, 0x5e, 0x27, 0x6f, 0x78, 0x51, 0x65, 0x89, 0x7a, 0x7f, 0xd3, 0x78, 0x5f, 0xd7, 0x26, 0xa0, - 0xea, 0xbb, 0x82, 0xc1, 0x3c, 0xbf, 0x2d, 0x00, 0xc4, 0x14, 0x69, 0x80, 0x4f, 0x30, 0x83, 0xa0, 0x76, 0xcd, 0xbc, - 0x6a, 0x04, 0xdf, 0x80, 0xaf, 0xde, 0x15, 0x80, 0x41, 0x12, 0x82, 0x14, 0x19, 0x42, 0x03, 0x81, 0x40, 0xc3, 0x90, - 0x0b, 0x0c, 0x7e, 0xe2, 0xc5, 0x91, 0x54, 0x4e, 0x89, 0x3c, 0x0c, 0xf0, 0x47, 0x40, 0x55, 0x00, 0x12, 0xe3, 0x71, - 0x08, 0x2f, 0xd4, 0x2f, 0xf7, 0x46, 0xed, 0x11, 0xf6, 0x3a, 0x0d, 0x21, 0xd8, 0x10, 0x3e, 0x04, 0xb0, 0xa4, 0x08, - 0x7d, 0x8b, 0x5c, 0x46, 0x18, 0x5c, 0xe6, 0xd9, 0x4a, 0x27, 0x55, 0xa3, 0x8e, 0xe6, 0x43, 0xa9, 0x1d, 0xc9, 0x01, - 0xf5, 0xd2, 0x63, 0x4c, 0x2f, 0x54, 0xba, 0x2a, 0xca, 0x19, 0xe5, 0xbc, 0xd3, 0x13, 0xe3, 0xc2, 0x16, 0x72, 0x88, - 0x84, 0xf3, 0xae, 0x50, 0xa1, 0x70, 0xf8, 0x02, 0xc0, 0xc0, 0x40, 0xda, 0xb1, 0x1b, 0xef, 0x46, 0x65, 0x3f, 0xe7, - 0xec, 0xf0, 0xbf, 0xe7, 0xf1, 0xf0, 0xaf, 0xf1, 0xf0, 0x9b, 0xc5, 0x20, 0x1c, 0xda, 0x9f, 0xe4, 0xf1, 0xa3, 0x43, - 0xfa, 0x92, 0x5b, 0x2e, 0x0d, 0x16, 0x7e, 0x23, 0xd8, 0x8f, 0x5a, 0x09, 0x41, 0x14, 0xe0, 0x0d, 0xcb, 0xad, 0xc6, - 0x09, 0x00, 0x1e, 0x06, 0xff, 0x15, 0xa0, 0xd1, 0x94, 0xbb, 0x78, 0x81, 0xbe, 0x44, 0xfd, 0x3e, 0xf9, 0xaa, 0x61, - 0x30, 0x08, 0xe2, 0x1a, 0x15, 0x13, 0x86, 0xe8, 0x32, 0x26, 0x0a, 0x06, 0xd9, 0x66, 0xdf, 0x6e, 0x7b, 0x6d, 0x49, - 0x18, 0x7e, 0xe9, 0x67, 0x9a, 0x98, 0x79, 0x87, 0x1b, 0xdb, 0x4a, 0xae, 0x42, 0xc4, 0x0a, 0xd4, 0xbf, 0x72, 0x06, - 0xb1, 0x37, 0x6f, 0x32, 0xf0, 0xe9, 0xb0, 0x5f, 0x8c, 0x67, 0xc0, 0x46, 0xc1, 0x9d, 0xaf, 0xe0, 0x97, 0x19, 0xb8, - 0x79, 0x8b, 0x18, 0x05, 0x0e, 0x76, 0x49, 0xf4, 0xfb, 0xbd, 0x3c, 0x0b, 0x73, 0x8d, 0x3b, 0x9d, 0xd7, 0x46, 0x0d, - 0x81, 0x3a, 0x72, 0x50, 0x3f, 0xe8, 0x21, 0x18, 0xaa, 0x21, 0x28, 0x3a, 0xda, 0xe2, 0xea, 0xb5, 0xf5, 0x14, 0xa6, - 0xb7, 0xaa, 0xbe, 0x62, 0xf4, 0x87, 0xcc, 0x04, 0x16, 0xd2, 0xae, 0x39, 0xd6, 0x35, 0xc7, 0x48, 0x7b, 0xfa, 0x7d, - 0xd1, 0x20, 0x3f, 0x9d, 0x85, 0x07, 0x81, 0x2a, 0x55, 0xee, 0x94, 0x45, 0xb9, 0x2d, 0xcd, 0x1b, 0xc3, 0x9a, 0xe6, - 0x99, 0x8d, 0x73, 0x33, 0xeb, 0xf5, 0xc2, 0x10, 0x1d, 0x3c, 0xb1, 0x54, 0xac, 0x0d, 0xc2, 0x1d, 0x99, 0x84, 0xd1, - 0x35, 0xc8, 0x2e, 0xc3, 0x73, 0x4e, 0x90, 0x4f, 0x05, 0xf6, 0x41, 0x55, 0xeb, 0xe5, 0x84, 0xc7, 0x46, 0xbe, 0x6c, - 0x04, 0x0d, 0xf2, 0x92, 0xa2, 0xde, 0xc4, 0xed, 0xd8, 0x47, 0x2d, 0xe4, 0xca, 0x4d, 0x3d, 0xed, 0x69, 0x52, 0xd1, - 0x63, 0xbd, 0x4a, 0xfd, 0x02, 0x4b, 0x0b, 0x4b, 0x3e, 0x08, 0xed, 0x69, 0x5a, 0x81, 0x19, 0x6e, 0x6c, 0x06, 0x43, - 0x3f, 0x1c, 0x3f, 0x01, 0x9d, 0x51, 0xdb, 0x12, 0xc2, 0xd8, 0x0d, 0xc2, 0xca, 0x7b, 0x22, 0x5f, 0x3c, 0xf1, 0x2e, - 0x06, 0x21, 0x37, 0x9b, 0x59, 0x34, 0x30, 0xdd, 0xaf, 0x65, 0xb3, 0x79, 0xba, 0xb9, 0x5e, 0x94, 0x50, 0x01, 0xdb, - 0x6d, 0x25, 0x08, 0xfe, 0xfd, 0x98, 0xcd, 0xf0, 0x6f, 0xd6, 0xef, 0xf7, 0x42, 0xfc, 0xc5, 0x31, 0x98, 0xd1, 0x5c, - 0x2c, 0xd8, 0x47, 0x90, 0x31, 0x91, 0x08, 0x53, 0x95, 0x31, 0x20, 0xab, 0xc0, 0x22, 0xd0, 0x7c, 0xa0, 0x72, 0x61, - 0x26, 0x7b, 0x99, 0x73, 0x0d, 0x39, 0x6d, 0x8d, 0x53, 0x36, 0xca, 0x12, 0xe5, 0xca, 0x91, 0x8d, 0xe2, 0x3c, 0x8b, - 0x4b, 0x5e, 0x6e, 0xb7, 0xfa, 0x70, 0x4c, 0x0a, 0x0e, 0xec, 0xba, 0xa2, 0x52, 0x25, 0xeb, 0x48, 0x75, 0xe3, 0x2f, - 0xc3, 0x02, 0xf7, 0x29, 0x9f, 0x17, 0x86, 0x46, 0x1c, 0x80, 0x30, 0x83, 0xa9, 0x5b, 0x7a, 0x2f, 0x2c, 0xa0, 0x79, - 0x25, 0x21, 0x1b, 0x4c, 0xf5, 0x2c, 0x7c, 0x63, 0x26, 0xe6, 0xc5, 0x02, 0xc2, 0xea, 0x14, 0x0b, 0xcd, 0x6c, 0xd2, - 0x84, 0xc5, 0x00, 0x9b, 0x17, 0x93, 0x29, 0xc4, 0x77, 0x57, 0xe5, 0xc4, 0x0b, 0x73, 0xdf, 0x4e, 0x1c, 0x72, 0x08, - 0xbc, 0xaa, 0x0d, 0xba, 0x9a, 0x6d, 0x38, 0xea, 0x48, 0x39, 0x31, 0xf9, 0xfd, 0x54, 0x41, 0x88, 0x3b, 0x71, 0x24, - 0x5c, 0xde, 0x6c, 0x17, 0x5e, 0x74, 0x20, 0xe8, 0xa8, 0xc1, 0x29, 0x3f, 0x31, 0x38, 0x1a, 0x93, 0x74, 0xe3, 0x9d, - 0x20, 0x45, 0x18, 0x93, 0x8d, 0x64, 0xd7, 0x32, 0x14, 0xf3, 0x78, 0x01, 0xca, 0xcb, 0x78, 0x01, 0x96, 0x46, 0xc6, - 0x20, 0x15, 0xe4, 0x77, 0xdc, 0x0b, 0x85, 0x45, 0x71, 0x85, 0x48, 0xcf, 0xea, 0xf7, 0x51, 0xd1, 0x0e, 0x05, 0x82, - 0xe2, 0x0e, 0x65, 0x9e, 0x9c, 0xf5, 0x58, 0x20, 0xb1, 0x21, 0x60, 0x7c, 0xa5, 0xd3, 0x54, 0x6b, 0xdd, 0x1b, 0x33, - 0x0f, 0x7c, 0x9a, 0x8d, 0x84, 0xac, 0xce, 0x2f, 0x41, 0xa4, 0xe4, 0xa3, 0xe3, 0x23, 0xbf, 0x88, 0x3b, 0xcb, 0xbc, - 0xb5, 0x2d, 0x2a, 0xd9, 0xc9, 0x06, 0x40, 0x0b, 0x75, 0xf4, 0x2c, 0x25, 0xb7, 0x29, 0x49, 0xed, 0x36, 0x05, 0xac, - 0x24, 0x7f, 0x01, 0x43, 0xf0, 0xb5, 0x03, 0xe1, 0x74, 0xac, 0x10, 0xaf, 0x69, 0x8a, 0x48, 0x93, 0x61, 0x49, 0x71, - 0x6c, 0x4b, 0x44, 0x41, 0xb5, 0x65, 0xd9, 0xc1, 0x30, 0x51, 0x82, 0x9f, 0xa7, 0x1e, 0x25, 0x0a, 0x02, 0xaa, 0x87, - 0x1c, 0x24, 0xd8, 0xb6, 0x81, 0xf0, 0x80, 0x3c, 0xa2, 0x37, 0xd6, 0xdf, 0x65, 0x9d, 0x67, 0x17, 0x9a, 0xe7, 0x72, - 0xbd, 0x2b, 0xcc, 0x18, 0xe1, 0x49, 0x66, 0xc2, 0x06, 0x78, 0xe7, 0x99, 0x51, 0xdb, 0xf4, 0x3c, 0xbc, 0xb6, 0x53, - 0x8c, 0xd0, 0xb7, 0x67, 0xd0, 0x4d, 0x30, 0xaf, 0x0e, 0x9b, 0xf5, 0x4a, 0x41, 0x6a, 0x98, 0x5a, 0x34, 0x31, 0xeb, - 0x59, 0x83, 0xf2, 0xed, 0xb6, 0xa7, 0xe7, 0x6a, 0xff, 0xdc, 0x6d, 0xb7, 0x3d, 0xec, 0xd6, 0xf3, 0xb4, 0xdb, 0x2a, - 0xbe, 0x52, 0x1f, 0xb4, 0xc7, 0x9f, 0xbb, 0xf1, 0xe7, 0x06, 0xd9, 0xa4, 0x74, 0x34, 0xd3, 0xd6, 0x07, 0xe1, 0x81, - 0xd3, 0xfb, 0x46, 0x93, 0xbe, 0xcb, 0x42, 0x49, 0x57, 0xa2, 0x51, 0x5d, 0xed, 0x4c, 0x4c, 0x1f, 0x5c, 0xff, 0x0f, - 0xaf, 0x02, 0x3c, 0xe2, 0xd4, 0xce, 0xde, 0xdb, 0xa0, 0xa2, 0xd1, 0x16, 0x8e, 0x14, 0xa1, 0x07, 0x24, 0x61, 0x5f, - 0xcb, 0x5a, 0xdc, 0xe6, 0x59, 0xf6, 0x30, 0x7d, 0x7a, 0x95, 0xfa, 0x5e, 0x08, 0x6e, 0x99, 0x65, 0xe6, 0xc0, 0xab, - 0x28, 0x0e, 0x68, 0xd4, 0x45, 0xfb, 0xae, 0xb3, 0xb2, 0x04, 0xaf, 0x17, 0xb8, 0x57, 0x9e, 0x71, 0x1f, 0x7e, 0xef, - 0xaa, 0x6a, 0x6e, 0xd2, 0xb3, 0x6c, 0x9e, 0x2d, 0xb6, 0xdb, 0x10, 0xff, 0x76, 0xb5, 0xc8, 0xd1, 0xe4, 0x39, 0xe8, - 0x34, 0x31, 0x92, 0x11, 0xd3, 0x8d, 0xf3, 0x36, 0xff, 0x1b, 0xd1, 0x70, 0x9a, 0x38, 0x05, 0x7a, 0x31, 0x7b, 0x04, - 0x32, 0x29, 0x03, 0x72, 0x20, 0x66, 0x7a, 0xcd, 0x40, 0x34, 0x32, 0x11, 0x01, 0xae, 0x30, 0x36, 0x12, 0x8d, 0x4e, - 0x38, 0xa9, 0x09, 0x58, 0xb0, 0xda, 0xf2, 0x3e, 0x58, 0xda, 0x56, 0x15, 0xf7, 0xde, 0x92, 0xe6, 0xb8, 0x0e, 0x9c, - 0xaf, 0x83, 0x19, 0x62, 0x53, 0x76, 0xb5, 0x40, 0xee, 0x97, 0xd7, 0xb4, 0x37, 0xae, 0x13, 0x98, 0xb5, 0x4d, 0x6d, - 0x19, 0x3f, 0x5b, 0xfa, 0x8f, 0x7a, 0x70, 0x95, 0x31, 0xd8, 0xdc, 0x58, 0x69, 0xd8, 0x7d, 0xe3, 0xf9, 0x52, 0x40, - 0x78, 0x3a, 0x9f, 0x1e, 0x9f, 0x65, 0x1e, 0x3d, 0x06, 0xa2, 0x63, 0x3e, 0x2a, 0xdd, 0x47, 0x76, 0xf7, 0xfa, 0x01, - 0x01, 0xe7, 0x55, 0xbb, 0xa0, 0x79, 0xb9, 0x80, 0xc0, 0xaa, 0x5e, 0x79, 0x85, 0xe5, 0x33, 0x63, 0x76, 0x05, 0x64, - 0xa8, 0x20, 0x10, 0xb8, 0xbb, 0xeb, 0x5c, 0x88, 0x55, 0x87, 0x95, 0x39, 0x4d, 0xc2, 0x4e, 0x42, 0x34, 0x6f, 0x0d, - 0x66, 0xc1, 0x7f, 0x05, 0x83, 0x72, 0x10, 0x44, 0x41, 0x14, 0x04, 0x64, 0x50, 0xc0, 0x2f, 0xc4, 0x5d, 0x23, 0x18, - 0xb3, 0x05, 0x3a, 0xfc, 0x8e, 0x33, 0x9f, 0x11, 0x79, 0xd1, 0x08, 0xeb, 0xe9, 0x06, 0xe0, 0x42, 0xca, 0x9c, 0xc7, - 0xe8, 0x73, 0xf2, 0x8e, 0xb3, 0x8c, 0xd0, 0x77, 0xde, 0xa9, 0xfc, 0x88, 0x37, 0x82, 0xfd, 0xed, 0x0e, 0xdb, 0x4b, - 0x90, 0x57, 0xf4, 0xc6, 0xf4, 0x1d, 0x27, 0x51, 0xd6, 0x70, 0xa6, 0xe6, 0xd0, 0xb3, 0xca, 0xb2, 0x56, 0xd4, 0x90, - 0x1b, 0x14, 0x73, 0x23, 0xcb, 0xe4, 0x64, 0xda, 0x6a, 0x4e, 0x05, 0xae, 0x3b, 0xbb, 0x5e, 0x40, 0x72, 0x28, 0x34, - 0x4b, 0x67, 0xc3, 0x79, 0xdb, 0x96, 0x3d, 0x6f, 0x9d, 0x42, 0x5e, 0x43, 0x54, 0x34, 0x48, 0x47, 0x40, 0x0d, 0xad, - 0xb8, 0xaa, 0xc0, 0x85, 0xd9, 0xb4, 0x87, 0x9b, 0xf6, 0x98, 0x66, 0x7c, 0x80, 0x98, 0x79, 0x1c, 0x5b, 0x06, 0x76, - 0x24, 0x0e, 0xdf, 0xc7, 0xf9, 0x02, 0xed, 0xd2, 0x5b, 0x57, 0x8b, 0x47, 0x58, 0x7b, 0xde, 0x0a, 0x09, 0x01, 0xe2, - 0xd3, 0x54, 0xba, 0xdd, 0x06, 0x01, 0x0c, 0x70, 0xbf, 0xdf, 0x03, 0xae, 0xd5, 0xb0, 0x93, 0xe6, 0xd6, 0x6c, 0x89, - 0xbd, 0xa2, 0xf0, 0x18, 0x98, 0x53, 0xf3, 0x9f, 0x41, 0x40, 0xf1, 0xdc, 0x0d, 0xc1, 0xde, 0x94, 0x9d, 0x6c, 0x8a, - 0x7e, 0xff, 0x79, 0x81, 0x0f, 0x28, 0x17, 0x06, 0x31, 0xb7, 0x8e, 0xe3, 0x61, 0xd8, 0x27, 0xf5, 0x21, 0x8e, 0x45, - 0x9e, 0x85, 0x8e, 0xb0, 0x54, 0x86, 0xb0, 0x70, 0xc5, 0x48, 0x07, 0x71, 0x50, 0x93, 0xce, 0xc1, 0xaa, 0x5c, 0xf0, - 0xe5, 0x5e, 0xef, 0x0d, 0xc0, 0xa4, 0x67, 0xde, 0xb0, 0xbc, 0xf7, 0x00, 0xd1, 0x7a, 0x3d, 0x5c, 0x28, 0xee, 0xe5, - 0xcb, 0x06, 0x1a, 0x27, 0xbe, 0xb4, 0xec, 0xfa, 0x4c, 0xcb, 0x4a, 0x46, 0xa3, 0x51, 0x55, 0x2b, 0xc9, 0x87, 0x23, - 0x2f, 0x2d, 0x14, 0x4f, 0x19, 0xa7, 0x3c, 0x05, 0xcb, 0x77, 0x43, 0xe9, 0xe6, 0x0b, 0xba, 0xe2, 0x22, 0x55, 0x3f, - 0x3d, 0xf4, 0xcd, 0x06, 0x71, 0xcd, 0x9a, 0x3a, 0x1c, 0x3b, 0xfc, 0x10, 0x00, 0xd3, 0x3e, 0xcc, 0x5c, 0xba, 0x86, - 0xe9, 0x05, 0xf1, 0x6c, 0x5c, 0xf0, 0xd0, 0xe5, 0x01, 0xec, 0x43, 0x73, 0x48, 0xe2, 0xa7, 0xf0, 0x73, 0x66, 0xd2, - 0x3a, 0x3e, 0xc3, 0xd9, 0x8c, 0x4a, 0x75, 0x23, 0x68, 0xbf, 0x86, 0x44, 0x62, 0x90, 0x9e, 0x1b, 0x0c, 0x45, 0xeb, - 0x6e, 0x03, 0x57, 0x7e, 0x4b, 0xef, 0x7c, 0x1a, 0x04, 0x58, 0xdf, 0x58, 0x0c, 0x00, 0xa8, 0xe2, 0x0f, 0x54, 0x5d, - 0x99, 0x2b, 0x8a, 0x69, 0x98, 0x4a, 0xb4, 0x77, 0x1c, 0xd7, 0x51, 0xe3, 0x3a, 0x2c, 0x58, 0x69, 0x6d, 0x9b, 0xdd, - 0x5b, 0x5a, 0xd8, 0x12, 0x50, 0x2d, 0x88, 0x3b, 0x01, 0x7c, 0x68, 0xa4, 0x3a, 0x10, 0x64, 0xf7, 0xc1, 0x01, 0x00, - 0x6f, 0x78, 0x1e, 0x86, 0xf0, 0x07, 0x16, 0x0e, 0x2c, 0x4b, 0xd5, 0xcf, 0xe5, 0x34, 0x86, 0x73, 0x37, 0x57, 0x3b, - 0x7c, 0xb6, 0x04, 0xc5, 0xa6, 0x9a, 0x53, 0x73, 0xf9, 0xca, 0x1b, 0xfb, 0x3d, 0x26, 0x98, 0xc7, 0xcc, 0x36, 0xfc, - 0xd6, 0xd3, 0x6d, 0x7d, 0x83, 0xdd, 0xc0, 0x49, 0x7b, 0xe1, 0xb4, 0x17, 0xdb, 0xa5, 0x81, 0xfc, 0xab, 0x1b, 0x42, - 0x84, 0x57, 0x9a, 0x58, 0x64, 0x0d, 0x99, 0x8e, 0xc5, 0x0a, 0x51, 0x6d, 0x2a, 0x9e, 0x69, 0x03, 0x81, 0x72, 0xaa, - 0x2e, 0x4c, 0xad, 0x54, 0x26, 0x0c, 0xe2, 0x4e, 0x09, 0x8b, 0x2a, 0x03, 0x0c, 0x83, 0x0a, 0x29, 0xae, 0xad, 0xe7, - 0x2f, 0x5c, 0xbe, 0x99, 0x69, 0xb3, 0xfd, 0xf4, 0x65, 0x1e, 0x5f, 0x6d, 0xb7, 0x61, 0xf7, 0x0b, 0x30, 0x47, 0x2d, - 0x95, 0x86, 0x11, 0x9c, 0x40, 0x94, 0xe4, 0x7a, 0x4f, 0xce, 0x89, 0xe3, 0xe4, 0xda, 0xcd, 0x9b, 0xed, 0xa4, 0x18, - 0x81, 0x05, 0x9c, 0xb8, 0x48, 0x07, 0x5a, 0x2a, 0x49, 0xed, 0x29, 0xe0, 0x6d, 0x7a, 0x47, 0xa9, 0xf0, 0x6a, 0xa1, - 0x49, 0x48, 0xe5, 0xee, 0x25, 0x76, 0xd4, 0x80, 0x73, 0x52, 0x77, 0x10, 0x70, 0xda, 0xd3, 0x8d, 0xb5, 0x8a, 0x64, - 0x93, 0xe0, 0xbd, 0xd2, 0x43, 0x97, 0x68, 0xa7, 0x76, 0xb7, 0xad, 0xca, 0x16, 0x0a, 0xe6, 0x41, 0xce, 0x12, 0x75, - 0x3c, 0xa0, 0xd0, 0x45, 0x1d, 0x0d, 0xf9, 0x82, 0x14, 0x7a, 0xe5, 0x68, 0x55, 0xf3, 0xae, 0x64, 0xa0, 0x54, 0xab, - 0x20, 0xaf, 0x89, 0x75, 0x5f, 0xcb, 0x1a, 0x8b, 0x2b, 0x27, 0xa4, 0xb0, 0x09, 0x9f, 0x5b, 0x8a, 0x85, 0x59, 0xec, - 0x8d, 0xa9, 0x2f, 0x5c, 0x22, 0xb4, 0xdd, 0x6d, 0x88, 0xd1, 0x06, 0xeb, 0x66, 0xbb, 0x7d, 0x55, 0x84, 0xf3, 0x6c, - 0x41, 0xe5, 0x28, 0x4b, 0x11, 0x52, 0xcd, 0x78, 0x2c, 0xdb, 0x2e, 0x98, 0x89, 0xa1, 0xae, 0x3d, 0x5e, 0x92, 0x29, - 0xd6, 0x26, 0xc9, 0x51, 0x7c, 0x21, 0x0b, 0xb5, 0xd6, 0x08, 0xc1, 0xc3, 0xfd, 0x8f, 0x14, 0x62, 0xda, 0x99, 0x75, - 0xf7, 0xed, 0xce, 0x0d, 0xf1, 0x0f, 0x08, 0xac, 0x50, 0xb2, 0x57, 0xc5, 0xe8, 0x22, 0x13, 0x29, 0xee, 0x54, 0x15, - 0x25, 0x58, 0xad, 0x83, 0x66, 0xcb, 0xed, 0xbd, 0xd8, 0x12, 0x05, 0x88, 0xf3, 0x2c, 0x34, 0xe3, 0x59, 0x39, 0xcb, - 0x99, 0x8c, 0x62, 0x43, 0xa2, 0xd2, 0x8b, 0x12, 0xef, 0xf3, 0x34, 0xa6, 0x87, 0x6e, 0x0d, 0x82, 0xeb, 0xea, 0xce, - 0x46, 0x9a, 0x2f, 0x08, 0x51, 0x13, 0x20, 0x61, 0xa3, 0x9a, 0x53, 0xeb, 0x4a, 0x3c, 0xcc, 0x2a, 0x9f, 0xeb, 0x83, - 0xf8, 0x4a, 0x00, 0x0f, 0xeb, 0x6d, 0xef, 0x6b, 0xe1, 0xb1, 0x36, 0xf8, 0x76, 0xbb, 0xbd, 0x12, 0xf3, 0x20, 0xf0, - 0x18, 0xcd, 0x5f, 0x94, 0xc4, 0xbc, 0x37, 0xa6, 0xb0, 0xe2, 0x7d, 0x17, 0xbf, 0x6e, 0x52, 0x6b, 0x2d, 0x72, 0x77, - 0xb8, 0x3e, 0xe0, 0x79, 0x4a, 0x1c, 0xed, 0xa8, 0x9c, 0x4a, 0x6b, 0x3b, 0x80, 0x5d, 0x11, 0x18, 0x28, 0xfb, 0xfb, - 0x94, 0x6d, 0xc0, 0x3c, 0x11, 0xac, 0x8f, 0xd0, 0x6f, 0x4b, 0xe9, 0x4f, 0xc6, 0x68, 0xdc, 0x23, 0xd7, 0x55, 0x74, - 0xc4, 0x75, 0x34, 0x7b, 0x1e, 0xfd, 0xed, 0xe9, 0x98, 0x16, 0xb1, 0x48, 0xe5, 0x35, 0xa8, 0x20, 0x40, 0x19, 0x82, - 0x8e, 0x10, 0x9a, 0x1a, 0x80, 0x06, 0xc1, 0x0d, 0xc0, 0x3f, 0x3b, 0x9d, 0x28, 0x6d, 0x4d, 0x3e, 0x46, 0xab, 0x2a, - 0x72, 0xd6, 0x86, 0x76, 0x53, 0xc9, 0x21, 0x79, 0x5c, 0x02, 0xbe, 0x25, 0x36, 0x4b, 0xd9, 0xa0, 0xa8, 0xcd, 0xa6, - 0x5e, 0x2b, 0x76, 0xe4, 0xae, 0x51, 0xb4, 0x59, 0x8b, 0xda, 0x6e, 0x64, 0xbe, 0x98, 0xde, 0x59, 0x61, 0xe0, 0xd4, - 0xb4, 0xe6, 0x76, 0x07, 0x4a, 0xce, 0xd6, 0x67, 0x72, 0x13, 0x20, 0x0e, 0x30, 0x5c, 0x77, 0xf3, 0xdb, 0x05, 0xa1, - 0x77, 0xec, 0xce, 0x8a, 0x55, 0x6f, 0xad, 0x5c, 0xc4, 0xa4, 0xdd, 0x0e, 0x26, 0x70, 0x19, 0x67, 0x85, 0x7d, 0xa1, - 0xd5, 0x0d, 0x45, 0x47, 0xdb, 0xa4, 0xfd, 0xbc, 0xa3, 0xdd, 0x70, 0xc1, 0xb7, 0x62, 0x1d, 0xe7, 0x96, 0x35, 0x55, - 0x68, 0xda, 0x81, 0xde, 0x0e, 0x01, 0xcd, 0xd9, 0x98, 0x2e, 0x69, 0x8a, 0x17, 0x68, 0xba, 0x06, 0x33, 0x9d, 0x4b, - 0xe8, 0x6b, 0xb7, 0x8f, 0xf6, 0xa5, 0xea, 0x89, 0xf0, 0x96, 0x28, 0xf8, 0xb6, 0xa4, 0xe0, 0xa5, 0x96, 0xf3, 0xd8, - 0xcc, 0x21, 0xe0, 0xd3, 0xa8, 0x12, 0xbd, 0x93, 0xe2, 0x0a, 0xb4, 0x99, 0x70, 0x04, 0x9a, 0xaa, 0x11, 0x5b, 0x39, - 0xc0, 0xed, 0xc5, 0xd3, 0x80, 0x50, 0x90, 0xea, 0xae, 0xed, 0x8a, 0xbc, 0x63, 0x27, 0x9b, 0x3b, 0x30, 0x13, 0xae, - 0xd6, 0x65, 0xeb, 0x2b, 0x9b, 0xec, 0x3e, 0xae, 0x09, 0xb6, 0xdd, 0xdb, 0x20, 0xe1, 0x1d, 0xbd, 0x25, 0x9b, 0xdb, - 0x7e, 0x3f, 0x84, 0xfe, 0x10, 0xaa, 0x3b, 0x74, 0xd7, 0xd9, 0xa1, 0x3b, 0x9f, 0xf9, 0xb5, 0x7a, 0x3e, 0xe5, 0x1d, - 0xf2, 0x01, 0x4d, 0xd6, 0xe8, 0x2a, 0xbe, 0x87, 0x4d, 0x1d, 0x55, 0x54, 0x55, 0x1e, 0x25, 0x14, 0x54, 0xe2, 0x19, - 0x2f, 0xcf, 0x38, 0xc6, 0x7a, 0xd5, 0x4f, 0xef, 0x34, 0xaf, 0xb6, 0x36, 0x6b, 0xb3, 0x5c, 0x5f, 0x80, 0x85, 0xc4, - 0x05, 0x8f, 0xae, 0x35, 0x2d, 0xb9, 0xf2, 0x98, 0xfa, 0x73, 0x1c, 0x95, 0xe0, 0x32, 0xce, 0x72, 0x50, 0xe3, 0x5e, - 0x36, 0xfb, 0x1f, 0x6a, 0xdb, 0xb1, 0x65, 0xe3, 0xcc, 0xbd, 0x09, 0xc9, 0xe6, 0x7f, 0x6c, 0xa0, 0x5e, 0x87, 0x18, - 0x21, 0xd6, 0x2c, 0xe8, 0xb7, 0x0c, 0x62, 0x85, 0x06, 0xe5, 0x3a, 0x49, 0x78, 0x59, 0x06, 0x46, 0xa9, 0xb5, 0x66, - 0x6b, 0x73, 0x9e, 0x3d, 0x62, 0x27, 0x8f, 0x7a, 0x8c, 0xdd, 0x11, 0x9a, 0x68, 0x9d, 0x90, 0xa9, 0x31, 0xf2, 0xb4, - 0x40, 0xba, 0x43, 0x51, 0x76, 0x19, 0xbe, 0x45, 0x21, 0x4b, 0x7b, 0x9f, 0x9b, 0x13, 0x59, 0x7d, 0xa3, 0x8d, 0x50, - 0x22, 0x95, 0x08, 0xb2, 0xf1, 0x5b, 0x04, 0x30, 0x86, 0x66, 0x07, 0x64, 0xb3, 0x64, 0x67, 0xf4, 0xdc, 0x9a, 0x04, - 0xc1, 0xeb, 0xb7, 0x2a, 0xd1, 0x8c, 0xb2, 0x22, 0xba, 0xca, 0xe8, 0xe7, 0x3e, 0x24, 0xd1, 0x79, 0x48, 0xfc, 0xdc, - 0xb0, 0xb4, 0x6e, 0x42, 0x14, 0x33, 0x9b, 0x0d, 0xaf, 0xba, 0xfb, 0xa8, 0xb1, 0xad, 0x8c, 0x8f, 0xf9, 0x9d, 0x4d, - 0x23, 0x53, 0xe8, 0xeb, 0x70, 0xd2, 0xef, 0xc3, 0x5f, 0x4d, 0x3f, 0xf0, 0x96, 0x82, 0xbf, 0xd8, 0x23, 0x52, 0x27, - 0x2c, 0x00, 0x78, 0xc6, 0x9c, 0x57, 0xcd, 0x09, 0x7c, 0xc4, 0x4e, 0x36, 0x8f, 0xc2, 0xb3, 0xc6, 0xcc, 0xdd, 0x87, - 0x78, 0xa9, 0x4a, 0x7a, 0xde, 0x3c, 0x99, 0x81, 0x58, 0x59, 0xad, 0xf9, 0x1d, 0xb3, 0xfa, 0x04, 0x20, 0x52, 0x77, - 0xd6, 0xc1, 0x16, 0x3f, 0x36, 0x5d, 0x26, 0x9b, 0x94, 0xb5, 0x99, 0x28, 0xa5, 0x22, 0x69, 0x2e, 0x02, 0xe8, 0x37, - 0x0c, 0x47, 0x0d, 0x70, 0xe7, 0x7a, 0xec, 0xcd, 0xd0, 0x78, 0x63, 0x6a, 0xe8, 0xd9, 0x46, 0x2f, 0x6f, 0x47, 0x21, - 0xcc, 0x58, 0x44, 0x77, 0xee, 0x58, 0x0c, 0xcf, 0xe8, 0x5b, 0xa8, 0xf0, 0x75, 0x88, 0xd1, 0x85, 0x49, 0x5d, 0x4f, - 0xd7, 0x6a, 0x2b, 0xdd, 0x12, 0x9a, 0x63, 0x54, 0x23, 0xaf, 0x6d, 0xf7, 0xd4, 0x08, 0xed, 0x09, 0xe5, 0xe1, 0x1d, - 0xad, 0xe8, 0xad, 0x65, 0x11, 0x9c, 0xfc, 0xd8, 0xcb, 0x4f, 0xe8, 0x85, 0x27, 0x30, 0x29, 0xda, 0x1a, 0xc0, 0xef, - 0x51, 0x3f, 0x9c, 0xd5, 0x53, 0x2b, 0xe5, 0xf0, 0x14, 0xbe, 0x64, 0x03, 0x72, 0x05, 0xbd, 0x58, 0x63, 0x76, 0x12, - 0x83, 0x0e, 0x6a, 0x67, 0x77, 0x78, 0x93, 0x52, 0x86, 0x68, 0x8d, 0xe8, 0x20, 0xaf, 0xfe, 0x09, 0x9a, 0x3e, 0x48, - 0x0b, 0x53, 0xba, 0x46, 0x01, 0x0f, 0xe8, 0x9b, 0xfa, 0xfd, 0x1c, 0x9f, 0x6b, 0xcf, 0x32, 0x0d, 0x7b, 0xbc, 0x24, - 0x74, 0xe9, 0xc5, 0xd1, 0x02, 0x69, 0xb3, 0x63, 0x15, 0x80, 0x15, 0x49, 0xa0, 0x11, 0x09, 0x58, 0x2e, 0x79, 0xe2, - 0xb2, 0x0d, 0x1a, 0xd4, 0x44, 0x25, 0x85, 0x2c, 0x91, 0x04, 0x7e, 0x18, 0x41, 0x99, 0xa2, 0x18, 0xc4, 0xbd, 0x7a, - 0x79, 0xc5, 0x35, 0x35, 0x60, 0x4d, 0x11, 0x4c, 0xb0, 0x4e, 0xa7, 0x40, 0x6c, 0xc5, 0x7a, 0x05, 0x9e, 0xa8, 0x8e, - 0x13, 0x47, 0x96, 0x00, 0x0d, 0xf4, 0x7c, 0xe9, 0xb4, 0x5b, 0xde, 0x9e, 0x68, 0xa9, 0x62, 0x73, 0xef, 0xc5, 0xc2, - 0x72, 0x8f, 0x95, 0xbf, 0x1d, 0x68, 0x2f, 0xac, 0x76, 0x44, 0xd4, 0x60, 0x75, 0xd8, 0xb6, 0xf3, 0x43, 0x69, 0xa8, - 0xee, 0x95, 0x63, 0x02, 0x2a, 0xba, 0x8a, 0xab, 0x65, 0x94, 0x8d, 0xe0, 0xcf, 0x76, 0x1b, 0x1c, 0x06, 0x60, 0x11, - 0xfa, 0xf3, 0xfb, 0x1f, 0x23, 0x0c, 0x57, 0xf5, 0xf3, 0xfb, 0x1f, 0xb7, 0xdb, 0xa7, 0xe3, 0xb1, 0xe1, 0x0a, 0x9c, - 0x5a, 0x07, 0xf8, 0x03, 0xc3, 0x36, 0xd8, 0x25, 0xbb, 0xdd, 0x3e, 0x05, 0x0e, 0x42, 0xb1, 0x0d, 0x66, 0x17, 0x2b, - 0xc7, 0x36, 0xc5, 0x6a, 0xe8, 0x1d, 0x09, 0xd8, 0x7d, 0x3b, 0x2c, 0xc5, 0x2e, 0xf5, 0x51, 0x21, 0x29, 0xf5, 0xa2, - 0x7f, 0xd1, 0x29, 0xb0, 0xa4, 0x60, 0xca, 0x1b, 0x2c, 0xab, 0x6a, 0x55, 0x46, 0x87, 0x87, 0xf1, 0x2a, 0x1b, 0x95, - 0x19, 0x6c, 0xf3, 0xf2, 0xe6, 0x0a, 0x00, 0x26, 0x02, 0xda, 0x78, 0xb7, 0x16, 0x99, 0x79, 0xb1, 0xa0, 0xcb, 0x0c, - 0xd7, 0x24, 0x98, 0x1d, 0xe4, 0xdc, 0xea, 0x26, 0xa7, 0xc4, 0x3e, 0x80, 0x0d, 0xe6, 0x76, 0xdb, 0xe0, 0x17, 0x4e, - 0x46, 0x4f, 0x67, 0xcb, 0x4c, 0x1b, 0xb8, 0x72, 0xb3, 0xff, 0x49, 0xe4, 0xa5, 0xa1, 0xe2, 0x93, 0x4c, 0x5f, 0x64, - 0xc0, 0xe7, 0xb1, 0xbf, 0x44, 0xe8, 0xb3, 0x5c, 0x8d, 0xd6, 0x00, 0x1b, 0x9b, 0x5d, 0xde, 0x8f, 0x52, 0x0e, 0x11, - 0x3a, 0x02, 0xab, 0xae, 0x59, 0x66, 0xc4, 0xb7, 0xa9, 0xb8, 0x6f, 0xa9, 0xc2, 0xfe, 0x12, 0x9e, 0xf3, 0x0e, 0x37, - 0x8e, 0x43, 0xbd, 0x49, 0x14, 0xbe, 0x40, 0x21, 0x2a, 0x47, 0xe3, 0x42, 0x27, 0x90, 0xca, 0x3c, 0x26, 0x14, 0x73, - 0xb8, 0x77, 0x3f, 0xa7, 0xce, 0x5c, 0xc6, 0x17, 0xee, 0xbd, 0xf0, 0x65, 0x26, 0x77, 0x12, 0x40, 0xa2, 0x54, 0xed, - 0x3f, 0x7d, 0x42, 0x6a, 0xfc, 0xaf, 0x54, 0x6b, 0x00, 0x7a, 0x3f, 0x41, 0x4d, 0x8e, 0x20, 0x60, 0x2b, 0xa6, 0x7e, - 0x74, 0x01, 0x2b, 0x99, 0xff, 0x80, 0xba, 0x1d, 0xc1, 0x36, 0x2a, 0x9e, 0x50, 0x54, 0xd1, 0x82, 0xa7, 0x6b, 0x91, - 0xc6, 0x22, 0xb9, 0x8f, 0x78, 0x3d, 0xc5, 0x92, 0x98, 0x8d, 0x18, 0xf6, 0x53, 0xb3, 0x0b, 0x3f, 0x16, 0x0d, 0x93, - 0x78, 0x5a, 0xfa, 0xdb, 0xca, 0xdb, 0x4c, 0x96, 0x71, 0x46, 0xa6, 0x5c, 0x21, 0x98, 0x5b, 0x7d, 0x8f, 0x39, 0xc1, - 0x9f, 0x1c, 0x3d, 0x21, 0xf4, 0x4e, 0x4e, 0x4b, 0x04, 0xe9, 0x13, 0xa9, 0x75, 0x5d, 0xc5, 0x7e, 0x4d, 0x21, 0xaa, - 0x85, 0x60, 0x10, 0xca, 0xd4, 0xb4, 0x4f, 0xf1, 0x7d, 0xb6, 0xec, 0xbf, 0x4c, 0xd9, 0x92, 0x6c, 0x04, 0x74, 0x4c, - 0x3a, 0xef, 0x57, 0x6f, 0xcf, 0xce, 0xbc, 0xdf, 0xa0, 0x09, 0x07, 0xd5, 0x0d, 0xb4, 0xab, 0x20, 0xd3, 0x18, 0xc5, - 0x66, 0x31, 0xd6, 0x6e, 0x4d, 0x44, 0x10, 0x84, 0xbb, 0x9c, 0x85, 0xed, 0x76, 0x42, 0xbc, 0x0d, 0x24, 0x50, 0xe0, - 0xda, 0x46, 0x39, 0x09, 0x89, 0xba, 0x90, 0xe9, 0xc9, 0xba, 0x91, 0x2c, 0xd0, 0x6b, 0xec, 0x28, 0xa0, 0xa7, 0xdc, - 0x3e, 0x05, 0xf4, 0x7d, 0xc1, 0x4e, 0xf9, 0x20, 0x18, 0x62, 0xbc, 0xd9, 0x80, 0xde, 0x4a, 0xf5, 0x08, 0x1e, 0xd3, - 0xc0, 0x72, 0xd1, 0x97, 0x05, 0x43, 0x98, 0xa5, 0x3f, 0x53, 0x36, 0xf9, 0xfa, 0xef, 0x6e, 0x7e, 0x2f, 0xb4, 0x98, - 0x1d, 0x84, 0xe2, 0xf6, 0x7a, 0x02, 0xc4, 0xaf, 0xe2, 0xd7, 0x60, 0x6d, 0xae, 0x25, 0xde, 0x6e, 0x7a, 0xfe, 0x10, - 0xbe, 0x1c, 0xdd, 0x7e, 0x52, 0x9a, 0x4f, 0x20, 0x68, 0x8f, 0x93, 0x94, 0xbb, 0xef, 0x3e, 0x4a, 0x57, 0x11, 0x8c, - 0x16, 0x20, 0xf8, 0xed, 0xad, 0xe4, 0xbc, 0x29, 0xfc, 0xc7, 0x3a, 0xdf, 0x63, 0x2c, 0x15, 0x79, 0x86, 0xd3, 0xdf, - 0x00, 0x07, 0xbf, 0xf7, 0x6f, 0x65, 0xd6, 0x90, 0xe8, 0x42, 0x7d, 0x04, 0xf4, 0x7f, 0xac, 0xc7, 0xef, 0x14, 0x25, - 0x7d, 0x49, 0x9c, 0x23, 0x7c, 0x13, 0x2f, 0xd1, 0x74, 0xb1, 0x37, 0xae, 0xe9, 0x9b, 0xc2, 0xbc, 0xd0, 0x0a, 0x0e, - 0xfb, 0xd6, 0x28, 0x3c, 0xf0, 0xcc, 0xfb, 0x56, 0x34, 0x04, 0xdd, 0xbf, 0xe2, 0xde, 0xf8, 0x56, 0xb0, 0x0c, 0x6f, - 0xca, 0x59, 0x66, 0xee, 0x70, 0xb7, 0x99, 0x48, 0xe5, 0x2d, 0x63, 0xc1, 0x5a, 0x28, 0x73, 0xde, 0x34, 0x98, 0x6d, - 0xea, 0x48, 0x25, 0xbb, 0xef, 0xff, 0x6a, 0x9c, 0xb0, 0xd9, 0x20, 0x38, 0xab, 0x64, 0x11, 0x5f, 0xf1, 0x60, 0xaa, - 0x55, 0x14, 0x19, 0xd8, 0x15, 0x02, 0x52, 0x8e, 0xd3, 0xde, 0xc1, 0x93, 0xa5, 0x66, 0x26, 0xe4, 0xb7, 0xd5, 0x59, - 0xc0, 0x5b, 0x33, 0x9a, 0xa7, 0x15, 0xec, 0x32, 0x5f, 0x49, 0xf1, 0x47, 0x4b, 0x92, 0x8d, 0xf5, 0x37, 0x64, 0xd8, - 0x56, 0x3e, 0x73, 0x01, 0x98, 0x3b, 0xb7, 0x52, 0x05, 0xfd, 0xeb, 0x01, 0x23, 0x84, 0x44, 0x40, 0x38, 0x8b, 0x89, - 0x7b, 0x61, 0xc2, 0x61, 0xba, 0x40, 0x41, 0x31, 0x06, 0x0a, 0xfa, 0x28, 0x43, 0x4e, 0x4f, 0xf9, 0x20, 0x69, 0xcc, - 0xd6, 0x1f, 0xaa, 0x44, 0x7a, 0x23, 0x09, 0x3d, 0x87, 0xdf, 0xe3, 0x16, 0x0f, 0xd4, 0x08, 0xd6, 0xe9, 0x6e, 0x4e, - 0x87, 0x2f, 0x0b, 0x32, 0xfc, 0x13, 0xbc, 0xdd, 0x62, 0x7b, 0x59, 0x4e, 0x60, 0x71, 0xc7, 0x5e, 0xf1, 0x34, 0x57, - 0x2d, 0x4e, 0x88, 0x47, 0x2c, 0x72, 0x9f, 0x58, 0xc0, 0x88, 0x1a, 0x46, 0xe3, 0x87, 0xb3, 0xb7, 0x6f, 0x34, 0x86, - 0x55, 0xee, 0x7f, 0x00, 0x23, 0xaa, 0xa5, 0xed, 0x76, 0xc0, 0x97, 0x23, 0x34, 0x60, 0x4f, 0xdd, 0x60, 0xf7, 0xfb, - 0x26, 0xed, 0xa4, 0xf4, 0xb2, 0x39, 0x31, 0xe8, 0x8e, 0xd2, 0x66, 0xa9, 0x0c, 0x8c, 0xbb, 0x0a, 0x47, 0x73, 0x62, - 0x23, 0x56, 0xf5, 0x3e, 0x0c, 0x97, 0x34, 0xb6, 0xb2, 0x72, 0xbb, 0x9b, 0x70, 0x64, 0x13, 0xe0, 0xfa, 0x14, 0xb4, - 0x57, 0x73, 0x0e, 0x5a, 0x50, 0xa2, 0xc0, 0x11, 0x6d, 0xb7, 0x21, 0x44, 0x24, 0x29, 0x86, 0x93, 0x59, 0x58, 0x0c, - 0x87, 0x6a, 0xe0, 0x0b, 0x42, 0xa2, 0x37, 0xc5, 0x3c, 0x5b, 0x28, 0x04, 0x23, 0x7f, 0x27, 0x7d, 0x5b, 0x28, 0x4e, - 0xb9, 0xf7, 0xad, 0x20, 0x9b, 0x5f, 0x53, 0x8c, 0xc1, 0xe8, 0x34, 0x9b, 0x19, 0x48, 0x58, 0x4f, 0x2b, 0xa2, 0xd6, - 0x91, 0x9d, 0x0d, 0x50, 0xc5, 0xa2, 0x69, 0x30, 0xa8, 0x5b, 0x3c, 0xb1, 0x9e, 0xd1, 0x7b, 0x50, 0x09, 0xa2, 0x5a, - 0xb0, 0x1b, 0xc3, 0xb5, 0xf6, 0x46, 0x84, 0x92, 0x72, 0xd2, 0x64, 0x66, 0xac, 0x68, 0xb0, 0x00, 0x21, 0x69, 0x5c, - 0x56, 0xaf, 0x65, 0x9a, 0x5d, 0x66, 0x80, 0x20, 0xe1, 0xfc, 0x09, 0x65, 0xe3, 0xcd, 0x33, 0x35, 0x2f, 0x5d, 0x89, - 0x33, 0x0b, 0x7b, 0xd2, 0xf5, 0x96, 0x16, 0x24, 0x2a, 0x80, 0x46, 0xf9, 0x5a, 0x9e, 0x7f, 0xec, 0x58, 0x85, 0xec, - 0x7e, 0x38, 0x55, 0xb6, 0x43, 0xfc, 0x84, 0x55, 0xc4, 0x3b, 0xad, 0x2b, 0x25, 0xd2, 0xe8, 0x68, 0x1b, 0x10, 0xc3, - 0x96, 0x7d, 0x8b, 0x1a, 0x3e, 0x08, 0xbb, 0xe8, 0x24, 0x3f, 0xe8, 0x29, 0x1e, 0x5b, 0x03, 0x49, 0x5f, 0x8b, 0xe0, - 0x6b, 0x74, 0xa4, 0x13, 0x65, 0x1a, 0x89, 0x29, 0x24, 0xfa, 0xf5, 0x42, 0x6b, 0x2c, 0xa3, 0xec, 0x2b, 0xf2, 0xbf, - 0xd3, 0xdd, 0xfb, 0x56, 0x6c, 0xb7, 0x30, 0xc9, 0x9e, 0x07, 0x1a, 0x6c, 0x6a, 0xd4, 0x0a, 0xe1, 0xec, 0x9c, 0x56, - 0xa8, 0x1d, 0xeb, 0x85, 0x25, 0x90, 0x07, 0xb0, 0x15, 0x69, 0x50, 0x06, 0xc9, 0xde, 0x14, 0x73, 0xb1, 0x70, 0xa2, - 0x1c, 0xa9, 0xf0, 0xcf, 0xe4, 0x28, 0xe5, 0x70, 0x15, 0x0b, 0x0b, 0x86, 0xfc, 0xea, 0xe8, 0xb2, 0x90, 0xd7, 0x20, - 0x29, 0x31, 0x0c, 0x95, 0xe5, 0x75, 0x71, 0xd5, 0x96, 0x84, 0xf6, 0xce, 0x01, 0x94, 0xa6, 0x00, 0xc1, 0x4b, 0xa3, - 0x86, 0x98, 0x6d, 0xd4, 0xee, 0x8a, 0xf6, 0x92, 0x03, 0xea, 0x74, 0xd7, 0x6e, 0xbd, 0x29, 0x5b, 0x75, 0x2b, 0x2e, - 0xfc, 0x03, 0x4a, 0x3f, 0xe5, 0x83, 0xc2, 0xa7, 0x12, 0xb8, 0xf1, 0xd5, 0x26, 0xcb, 0x2e, 0xef, 0x71, 0xe9, 0x57, - 0x8d, 0xf1, 0xeb, 0xf7, 0x7b, 0x6a, 0x21, 0x34, 0x52, 0x81, 0xf9, 0xf6, 0x99, 0xa9, 0xca, 0x68, 0x4a, 0xed, 0x25, - 0xb8, 0x72, 0xf6, 0x23, 0xa8, 0x88, 0xeb, 0x8a, 0xd4, 0xa6, 0x06, 0xe8, 0xc0, 0xcb, 0x0a, 0xb7, 0xb2, 0x00, 0x8f, - 0x9d, 0x80, 0x6c, 0xb7, 0x3c, 0x0c, 0xf4, 0xa1, 0x13, 0xf8, 0x5b, 0xf2, 0x0c, 0x99, 0x35, 0xfb, 0xf8, 0x93, 0x16, - 0xfc, 0x63, 0x0b, 0x7e, 0x44, 0x71, 0xa7, 0x95, 0xf9, 0xb7, 0xd2, 0xba, 0xc5, 0xfd, 0x3b, 0x99, 0x26, 0x14, 0x95, - 0x09, 0xb5, 0x5f, 0xe9, 0x6f, 0x26, 0x78, 0x94, 0xca, 0xfe, 0x41, 0xc2, 0x07, 0xb3, 0xc6, 0x13, 0x6b, 0x3c, 0x19, - 0x4e, 0xb7, 0xd2, 0xb0, 0x0c, 0x28, 0xf4, 0xf3, 0x32, 0x57, 0x54, 0x3f, 0xff, 0xb4, 0xe6, 0x6b, 0xde, 0x6c, 0xb1, - 0x4d, 0x7a, 0xa0, 0xc1, 0x5e, 0x1e, 0x4d, 0x29, 0x9c, 0x44, 0x9d, 0x1b, 0x89, 0xba, 0xa8, 0x59, 0x86, 0xea, 0x04, - 0xaf, 0xe6, 0xa9, 0x1e, 0xf6, 0x66, 0x22, 0x5a, 0x2b, 0x29, 0x4b, 0x0c, 0x58, 0xeb, 0xc8, 0x43, 0x72, 0xb7, 0xd6, - 0x71, 0xa7, 0xa1, 0x2e, 0x4d, 0xa1, 0x26, 0x58, 0xe1, 0x02, 0x1c, 0x41, 0x3f, 0x16, 0x21, 0x87, 0x6b, 0xaa, 0xd2, - 0x2f, 0x68, 0x4a, 0x9e, 0x78, 0x8a, 0x5a, 0xad, 0x48, 0xb7, 0x1f, 0xe5, 0xd8, 0x0d, 0xdf, 0x38, 0x21, 0x27, 0x46, - 0xe8, 0xef, 0x8e, 0xa5, 0x9c, 0xa1, 0xc5, 0x83, 0x3a, 0xc1, 0x7a, 0x79, 0x4b, 0x81, 0x62, 0x8e, 0x2e, 0xab, 0xae, - 0x79, 0x85, 0xb6, 0x2f, 0xcb, 0x7e, 0x3f, 0xb7, 0xf5, 0xa4, 0xec, 0x64, 0xb3, 0x34, 0xfb, 0x10, 0x15, 0x53, 0xb8, - 0xeb, 0x13, 0xcd, 0x5f, 0x85, 0xfa, 0xaa, 0x2d, 0x73, 0x3e, 0xe2, 0x88, 0x13, 0x92, 0x93, 0xfa, 0x27, 0x35, 0xf5, - 0x4a, 0xdc, 0xaf, 0x2a, 0xf9, 0x45, 0x18, 0x2b, 0x46, 0x17, 0xb8, 0x20, 0x55, 0x2a, 0xef, 0x17, 0x05, 0xc0, 0x5f, - 0x09, 0xf6, 0x26, 0x0d, 0xb5, 0xf2, 0x5b, 0xb4, 0x05, 0xfc, 0x1b, 0xc5, 0x0d, 0x58, 0x05, 0x06, 0x18, 0x4d, 0xb6, - 0xe7, 0x34, 0x81, 0x03, 0x4e, 0x68, 0x15, 0x05, 0x15, 0x66, 0x68, 0xa8, 0x2d, 0x8c, 0x9e, 0xa1, 0x8c, 0x5b, 0x65, - 0xf6, 0x6e, 0x8c, 0x9d, 0x16, 0x78, 0x0d, 0xff, 0x46, 0x2f, 0x14, 0xb3, 0x51, 0x07, 0xe9, 0xd1, 0x49, 0x4c, 0x7f, - 0xdc, 0xc2, 0xc9, 0xcd, 0xc2, 0x59, 0xd6, 0x2c, 0x81, 0xee, 0xc0, 0x05, 0x31, 0xee, 0xf7, 0x73, 0x38, 0x32, 0xcd, - 0xc8, 0x17, 0x2c, 0xa7, 0x31, 0x5b, 0x52, 0xed, 0x79, 0x78, 0x55, 0x85, 0x39, 0x5d, 0x5a, 0x19, 0x6f, 0xca, 0x40, - 0x65, 0xb4, 0xdd, 0x86, 0xf0, 0xa7, 0xdb, 0xda, 0x25, 0x9d, 0x2f, 0x21, 0x03, 0xfc, 0x01, 0x89, 0x28, 0x62, 0x81, - 0xff, 0x5b, 0x8d, 0x53, 0x7a, 0xa2, 0xb4, 0x66, 0x09, 0x5d, 0x33, 0x5d, 0x3f, 0xbd, 0x64, 0xeb, 0xc6, 0x52, 0xd8, - 0x6e, 0xc3, 0x66, 0x02, 0xd3, 0x9c, 0x2b, 0x99, 0x5e, 0xa2, 0x4e, 0x0a, 0xa8, 0x58, 0x78, 0x89, 0xcb, 0x2f, 0x25, - 0x14, 0x9a, 0x3b, 0x5f, 0x2e, 0x8c, 0x12, 0x13, 0x5a, 0x25, 0x3f, 0x7f, 0xa8, 0xcc, 0xd7, 0xc6, 0x43, 0xf0, 0xb7, - 0x34, 0x4c, 0x4c, 0x91, 0xa8, 0x10, 0x9d, 0x7d, 0x0b, 0xb2, 0x1c, 0x01, 0xb8, 0x9e, 0x67, 0xb2, 0xa6, 0x3f, 0xa4, - 0x10, 0x17, 0x1e, 0x1a, 0xf4, 0xae, 0x90, 0xd7, 0x59, 0xc9, 0x43, 0xbc, 0x27, 0x78, 0x9a, 0xd1, 0xfd, 0x06, 0x1f, - 0xda, 0xda, 0xa3, 0x27, 0xc8, 0xc6, 0x53, 0xee, 0xd7, 0xbf, 0x88, 0x70, 0x0e, 0xd1, 0x3b, 0x17, 0x54, 0xab, 0xab, - 0x1d, 0x20, 0x97, 0x67, 0x7b, 0xf5, 0x08, 0x4e, 0x37, 0x7d, 0x7d, 0xab, 0x42, 0x67, 0x0e, 0x20, 0xed, 0x21, 0x59, - 0xd7, 0x5c, 0xef, 0x00, 0x77, 0x24, 0x56, 0x6b, 0xa0, 0xb1, 0x6e, 0x6b, 0x76, 0xda, 0xa3, 0x78, 0x4c, 0x64, 0x66, - 0x2c, 0x52, 0x8c, 0xb9, 0x5b, 0xa7, 0x45, 0xd1, 0x06, 0xcd, 0x10, 0x76, 0xef, 0x3a, 0x7c, 0xdd, 0x8a, 0x38, 0xbf, - 0xdf, 0xf6, 0x05, 0x46, 0xc3, 0x98, 0x6b, 0xf7, 0x3c, 0x43, 0x37, 0x6c, 0xb0, 0x8d, 0x24, 0x88, 0x48, 0x90, 0x99, - 0x3a, 0x10, 0x65, 0x6d, 0x0d, 0xd8, 0x1e, 0x71, 0xbd, 0x69, 0x15, 0x3f, 0xaf, 0x62, 0xf0, 0xf6, 0xac, 0x71, 0x4a, - 0xeb, 0x6b, 0x5c, 0x73, 0x5c, 0x15, 0x22, 0x6a, 0x8b, 0x14, 0x00, 0xc3, 0xce, 0x17, 0xb8, 0x33, 0x2b, 0x0c, 0xe6, - 0x84, 0xa5, 0x92, 0x9d, 0xca, 0xf5, 0xe7, 0xb0, 0xc5, 0x41, 0x2a, 0x5f, 0x7a, 0xfd, 0xfd, 0xcd, 0x17, 0x5f, 0xa0, - 0xdb, 0x9e, 0xf3, 0x23, 0x08, 0x32, 0x81, 0x0e, 0x6a, 0x4a, 0xf5, 0xf8, 0x4b, 0x01, 0xd4, 0x1e, 0xe6, 0xe1, 0x97, - 0x82, 0x89, 0xf8, 0x26, 0xbb, 0x8a, 0x2b, 0x59, 0x8c, 0x6e, 0xb8, 0x48, 0x65, 0x61, 0xa5, 0xc6, 0xc1, 0xe9, 0x6a, - 0x95, 0xf3, 0x00, 0x4c, 0xe5, 0x2d, 0xa3, 0x6c, 0x2b, 0xcb, 0xf4, 0xe0, 0x6a, 0x79, 0x7a, 0xa5, 0x45, 0xe7, 0xe5, - 0xcd, 0x55, 0x10, 0xe1, 0xaf, 0x0b, 0xf3, 0xe3, 0x3a, 0x2e, 0x3f, 0x06, 0x91, 0xb5, 0xa9, 0x33, 0x3f, 0x50, 0x2a, - 0x0f, 0xfe, 0x4e, 0x20, 0xd3, 0xfd, 0xa5, 0x00, 0xcb, 0x6c, 0x5b, 0xf1, 0x71, 0x8c, 0xb5, 0x0e, 0x27, 0x64, 0xa6, - 0x4a, 0xf4, 0xde, 0x25, 0xeb, 0x02, 0xac, 0xfd, 0x14, 0xb6, 0xb3, 0xca, 0x35, 0xc3, 0xca, 0x54, 0x45, 0x66, 0x56, - 0xd6, 0xec, 0x30, 0xb4, 0x4e, 0x34, 0x73, 0xf4, 0x16, 0xd0, 0x0f, 0xe4, 0xf0, 0x8a, 0x96, 0x6b, 0xe6, 0xf9, 0xd8, - 0x34, 0x5e, 0x3f, 0x3a, 0xbc, 0x72, 0x0b, 0xf6, 0xce, 0xde, 0xc9, 0x51, 0x98, 0x08, 0x9e, 0xc6, 0x66, 0x7c, 0x91, - 0x67, 0x05, 0xec, 0xa0, 0xc9, 0x78, 0x4c, 0xbd, 0xa5, 0xd5, 0xba, 0x39, 0x3a, 0x64, 0xdb, 0xec, 0x71, 0xf5, 0x98, - 0x93, 0x43, 0xde, 0x32, 0xb5, 0x6d, 0x5b, 0xc7, 0x79, 0x9a, 0x7c, 0x65, 0xba, 0x2f, 0xd6, 0x36, 0x42, 0xbc, 0x72, - 0x76, 0x74, 0x5e, 0xd2, 0xad, 0x6f, 0x4a, 0x43, 0xaf, 0x25, 0x00, 0xf3, 0x69, 0x03, 0xfe, 0x82, 0x95, 0xeb, 0x51, - 0xc5, 0xcb, 0x0a, 0x24, 0x2c, 0x28, 0xc2, 0x9b, 0x62, 0x6f, 0x0a, 0x77, 0xe3, 0xf4, 0x1c, 0x76, 0xe0, 0x62, 0x8a, - 0xee, 0x38, 0x31, 0x99, 0x95, 0x46, 0x2b, 0x1a, 0xe9, 0x5f, 0xae, 0x2f, 0xb1, 0xee, 0x8b, 0x56, 0xe6, 0xd9, 0x9c, - 0x0a, 0x9b, 0xde, 0x55, 0x2e, 0x9d, 0xa8, 0xdf, 0x32, 0xe1, 0xca, 0x95, 0x20, 0x20, 0xd3, 0x82, 0xf5, 0x0a, 0xb3, - 0x8b, 0xe4, 0x1a, 0x08, 0x19, 0x18, 0xbe, 0x06, 0x6b, 0x51, 0x72, 0x63, 0x05, 0xeb, 0xdd, 0xf3, 0x75, 0x82, 0x90, - 0x82, 0x07, 0x6e, 0x82, 0xbe, 0x6f, 0xdd, 0xbc, 0x1d, 0x25, 0xca, 0x20, 0x3e, 0xb9, 0x76, 0xca, 0x41, 0x02, 0x01, - 0x38, 0xb0, 0x2a, 0x24, 0x89, 0x02, 0x9d, 0x07, 0x57, 0x33, 0x8e, 0x60, 0xf3, 0xca, 0x99, 0x8b, 0x1b, 0xc0, 0x79, - 0xe5, 0xcf, 0x65, 0x83, 0x2d, 0xeb, 0x11, 0x55, 0xe6, 0x8c, 0x53, 0x0c, 0xea, 0x64, 0x09, 0xfa, 0xca, 0x52, 0xda, - 0x2b, 0xd0, 0x34, 0x5e, 0xb3, 0x95, 0xf2, 0x01, 0xa0, 0x17, 0x6c, 0xa5, 0x8c, 0xfd, 0xf1, 0xeb, 0x73, 0xb6, 0xd2, - 0xd2, 0xe0, 0xe9, 0xf5, 0xec, 0x62, 0x76, 0x3e, 0x60, 0x47, 0x51, 0xa8, 0x0d, 0x18, 0x02, 0x17, 0x99, 0x20, 0x18, - 0x84, 0x1a, 0xff, 0x65, 0xa0, 0x02, 0x84, 0x11, 0x8f, 0xc7, 0x46, 0x1c, 0xb1, 0x70, 0x3c, 0xc4, 0x60, 0x60, 0xcd, - 0x17, 0x24, 0x20, 0xd4, 0x94, 0x86, 0xbe, 0x9e, 0xe1, 0x70, 0x72, 0x30, 0x81, 0x54, 0xcc, 0xcc, 0x54, 0x61, 0x6c, - 0x4c, 0x22, 0x88, 0xff, 0xda, 0x59, 0x2f, 0x94, 0xdb, 0x5d, 0xa3, 0x81, 0xa0, 0x19, 0x7c, 0x56, 0xc5, 0x93, 0x83, - 0x61, 0x57, 0xc5, 0x38, 0x0a, 0x37, 0x46, 0xf9, 0x76, 0x7e, 0x0c, 0x60, 0xbe, 0xe7, 0x43, 0x5f, 0x2e, 0x71, 0x7e, - 0xf8, 0x84, 0x3c, 0x7e, 0x42, 0xe8, 0x39, 0x3b, 0xff, 0xe2, 0x09, 0x3d, 0x57, 0xe4, 0xe4, 0x60, 0x12, 0xdd, 0x30, - 0x8b, 0x81, 0x73, 0xa4, 0x9a, 0x40, 0xaf, 0x46, 0x6b, 0xa1, 0x16, 0x98, 0x76, 0x68, 0x0a, 0xbf, 0x19, 0x1f, 0x04, - 0x83, 0x9b, 0x76, 0xd3, 0x6f, 0xda, 0x6d, 0xf5, 0xbc, 0xba, 0x0e, 0x8e, 0xa2, 0xdd, 0x62, 0x26, 0x7f, 0x1f, 0x1f, - 0xb8, 0x39, 0xc0, 0xfa, 0x1e, 0x1e, 0x13, 0xd3, 0xa4, 0x9d, 0x51, 0xf1, 0x6b, 0xfa, 0x0a, 0xfb, 0xd0, 0x2c, 0xb2, - 0xa3, 0x0f, 0xc3, 0x7f, 0xab, 0x13, 0xf5, 0xf9, 0x17, 0x47, 0x40, 0x8e, 0x40, 0x06, 0x8a, 0x25, 0x82, 0x19, 0x0e, - 0x34, 0x05, 0x14, 0x64, 0x7a, 0xdc, 0xa9, 0x1e, 0x7e, 0x35, 0x6a, 0x6a, 0x46, 0x6e, 0x60, 0x6a, 0xb0, 0x2d, 0xf8, - 0x81, 0xea, 0x86, 0xfe, 0x46, 0xa3, 0x3d, 0x69, 0x27, 0x33, 0xf3, 0x92, 0xda, 0x38, 0x77, 0x37, 0x10, 0xd0, 0xd9, - 0xc1, 0x2d, 0x4a, 0xf6, 0xe5, 0xf1, 0xd5, 0x01, 0xae, 0x22, 0x40, 0x0d, 0x63, 0xc1, 0x97, 0x83, 0x2b, 0xbd, 0xb9, - 0x0f, 0x02, 0x32, 0xf8, 0x32, 0x38, 0xf9, 0x72, 0x20, 0x07, 0xc1, 0xf1, 0xe1, 0xd5, 0x49, 0xe0, 0x8c, 0xfb, 0x21, - 0xe4, 0xa5, 0xaa, 0x28, 0x66, 0xc2, 0x54, 0x91, 0xd8, 0xda, 0x73, 0x5b, 0xaf, 0x32, 0x3e, 0xa3, 0xe9, 0xd4, 0x22, - 0xa1, 0x87, 0x29, 0x8b, 0xcd, 0xef, 0x60, 0xc2, 0xaf, 0x83, 0xc8, 0x05, 0x85, 0x9d, 0xe5, 0x51, 0x4c, 0x97, 0xec, - 0x4e, 0x84, 0x29, 0x4d, 0x0e, 0x73, 0x42, 0xa2, 0x70, 0xa9, 0xc0, 0x04, 0xd5, 0xeb, 0x04, 0xe2, 0xda, 0xba, 0xcf, - 0xef, 0x44, 0xb8, 0xa4, 0xf9, 0x61, 0x42, 0x5a, 0x45, 0xb8, 0x08, 0x35, 0x9b, 0x9a, 0x5e, 0xb2, 0x70, 0x45, 0xaf, - 0x80, 0x99, 0x92, 0xeb, 0xf0, 0x0a, 0xb8, 0xbc, 0xf5, 0x7c, 0xb5, 0x60, 0x57, 0x0d, 0xe9, 0x9b, 0xe1, 0x8b, 0x2f, - 0xad, 0x4f, 0x1e, 0xf0, 0x90, 0xce, 0x0f, 0x2f, 0x05, 0x1b, 0x80, 0x9b, 0x8c, 0xdf, 0x7e, 0x2b, 0xef, 0xf4, 0xbc, - 0xb4, 0xa7, 0x18, 0x67, 0xa6, 0x9d, 0x98, 0xb4, 0x13, 0x72, 0xff, 0xbe, 0xed, 0xbb, 0x17, 0xaf, 0x95, 0xcb, 0xaa, - 0x65, 0x48, 0x8a, 0xb5, 0x72, 0x9d, 0x46, 0xc9, 0xa9, 0x15, 0x78, 0xb2, 0x4b, 0x5e, 0x25, 0x4b, 0xff, 0xa0, 0xb2, - 0x56, 0x03, 0xf6, 0x18, 0xb1, 0x2c, 0x14, 0x8e, 0xfd, 0xeb, 0x8c, 0x15, 0x6b, 0x5f, 0xa0, 0x11, 0x23, 0xf7, 0xf6, - 0x3a, 0x63, 0x5e, 0x0c, 0xda, 0x64, 0xed, 0x85, 0xee, 0xf3, 0xd2, 0xf3, 0x16, 0xef, 0xe5, 0x94, 0x1a, 0x46, 0x22, - 0x7a, 0x30, 0x56, 0x66, 0x94, 0x2a, 0x51, 0x6b, 0xd0, 0x88, 0x60, 0x63, 0x17, 0x0c, 0x14, 0x9c, 0x50, 0xb9, 0xa7, - 0xce, 0xf6, 0xed, 0x94, 0x4a, 0x0f, 0x68, 0x97, 0x1a, 0x55, 0xb9, 0x5b, 0x66, 0x92, 0x55, 0x83, 0x60, 0xf4, 0x47, - 0x29, 0xc5, 0x0c, 0xef, 0x8c, 0x2c, 0x98, 0x82, 0x95, 0xa0, 0xaa, 0x65, 0x58, 0x0e, 0x39, 0x6a, 0xf1, 0x8c, 0x4f, - 0xaa, 0xd4, 0x3f, 0x3a, 0x82, 0x06, 0x2f, 0xd7, 0xad, 0xa0, 0xc1, 0x4f, 0xc6, 0x4f, 0xf4, 0x40, 0xa7, 0x6b, 0xed, - 0x78, 0xe8, 0xf3, 0xdb, 0x88, 0x37, 0xae, 0x7b, 0x4f, 0xb5, 0x56, 0xa1, 0x0c, 0xb4, 0x58, 0x51, 0xb9, 0x52, 0x4b, - 0xba, 0xdf, 0x45, 0x00, 0x2c, 0x62, 0x63, 0x36, 0xde, 0xb5, 0xcd, 0x0a, 0x41, 0xa3, 0xcb, 0x4e, 0x36, 0xf1, 0x80, - 0x25, 0xba, 0xb5, 0x83, 0x09, 0x8d, 0x4f, 0x58, 0xd9, 0xef, 0xe7, 0x27, 0x40, 0x4f, 0xb5, 0x11, 0x53, 0x01, 0x47, - 0xfe, 0xe7, 0x56, 0x64, 0x8a, 0x02, 0x9b, 0x35, 0x75, 0xb7, 0xc6, 0x32, 0x12, 0x7d, 0x99, 0xd2, 0xe5, 0x09, 0xcf, - 0x80, 0x69, 0xbd, 0x6e, 0x39, 0xae, 0xec, 0x2a, 0x8e, 0x3c, 0x15, 0x96, 0x15, 0xe7, 0x55, 0x38, 0xde, 0x7a, 0x7c, - 0x83, 0x43, 0xc3, 0xa6, 0x5d, 0xfa, 0x43, 0x08, 0x0b, 0xe1, 0x75, 0x06, 0xb7, 0x11, 0x6d, 0x27, 0x81, 0xca, 0x1b, - 0x73, 0x9d, 0x50, 0x36, 0xb7, 0xeb, 0xb5, 0x67, 0x90, 0x4e, 0xcc, 0x81, 0x52, 0x8d, 0xa0, 0x35, 0x9a, 0x05, 0x55, - 0x23, 0x1e, 0x39, 0x1e, 0xde, 0x19, 0xc4, 0x6a, 0xf9, 0x92, 0xa6, 0x52, 0x34, 0x00, 0xe3, 0x02, 0xb8, 0x3c, 0xfd, - 0xfc, 0xfe, 0xc7, 0x33, 0x1e, 0x17, 0xc9, 0xf2, 0x5d, 0x5c, 0xc4, 0xd7, 0x65, 0xb8, 0x51, 0x63, 0x14, 0xd7, 0x64, - 0x2a, 0x06, 0x4c, 0x9a, 0x95, 0xd4, 0xdc, 0x95, 0x9a, 0x10, 0x63, 0x9d, 0xc9, 0xba, 0xac, 0xe4, 0x75, 0xa3, 0xd2, - 0x75, 0x91, 0xe1, 0xc7, 0x2d, 0x9f, 0xd3, 0x43, 0x00, 0x36, 0x35, 0x2e, 0xa4, 0x91, 0xd4, 0x85, 0x18, 0x73, 0x11, - 0xaf, 0xeb, 0xe3, 0x71, 0xa3, 0xeb, 0x25, 0x7b, 0x3a, 0xfe, 0x6a, 0xfa, 0x3a, 0x0b, 0xb3, 0x81, 0x20, 0xa3, 0x6a, - 0xc9, 0x45, 0xcb, 0x94, 0x53, 0x99, 0x04, 0xa0, 0x8f, 0x67, 0x8f, 0xb1, 0xa3, 0xf1, 0x98, 0x6c, 0xda, 0xe2, 0x01, - 0x1e, 0x2e, 0xd7, 0x61, 0x41, 0x66, 0xba, 0x8e, 0x28, 0x10, 0xfc, 0xae, 0x0a, 0x00, 0xd9, 0xd2, 0x56, 0x65, 0xb8, - 0x34, 0xf6, 0x74, 0x3c, 0xa1, 0x12, 0xbb, 0x1d, 0x92, 0xda, 0xab, 0xd0, 0xcd, 0xbc, 0xf4, 0x3d, 0x8a, 0xa4, 0x71, - 0x59, 0xda, 0xa9, 0x54, 0xaa, 0x3d, 0x33, 0x73, 0x5d, 0x83, 0x98, 0x14, 0xa1, 0xae, 0xbb, 0xf4, 0xea, 0xde, 0x6d, - 0xae, 0x35, 0xdb, 0x01, 0xef, 0x35, 0x68, 0x86, 0x92, 0xb7, 0x98, 0xb7, 0xae, 0x88, 0x9a, 0xae, 0xd6, 0x60, 0x56, - 0x8c, 0xb2, 0xa5, 0x28, 0x5d, 0x53, 0x50, 0x0a, 0x46, 0x97, 0x6b, 0x6f, 0xe1, 0xbe, 0x96, 0x8d, 0x0b, 0x4b, 0xa6, - 0x57, 0x8b, 0x92, 0x12, 0xaa, 0x9b, 0x8a, 0x91, 0x12, 0x46, 0x4a, 0xc3, 0x53, 0xf9, 0x5e, 0xe0, 0x71, 0x9e, 0x07, - 0x51, 0xcb, 0x0b, 0xec, 0xb4, 0x22, 0xa7, 0xe0, 0xe8, 0x65, 0x72, 0x1a, 0x0a, 0xfc, 0x43, 0xa6, 0x40, 0x5d, 0x87, - 0xea, 0x7e, 0x83, 0x9b, 0xff, 0x9f, 0x05, 0x0b, 0x3c, 0xbe, 0xf5, 0x0a, 0xb7, 0xd1, 0x3f, 0x0b, 0x9f, 0x96, 0x3e, - 0x93, 0xbe, 0xab, 0x8b, 0x27, 0xed, 0xcd, 0x46, 0xc9, 0x32, 0xcb, 0xd3, 0x37, 0x32, 0xe5, 0x20, 0x32, 0x43, 0x6b, - 0x50, 0x76, 0x22, 0x1a, 0x37, 0x3c, 0x30, 0x62, 0x6c, 0xdc, 0xf8, 0x7e, 0xc8, 0x40, 0x36, 0x0c, 0x56, 0xdf, 0x2c, - 0x95, 0xc9, 0x1a, 0x10, 0x36, 0xb4, 0xfc, 0x44, 0xe3, 0x6d, 0x84, 0xfa, 0xfa, 0x05, 0x6e, 0x73, 0xa5, 0xef, 0x73, - 0xfe, 0x43, 0x46, 0x7f, 0x40, 0xe0, 0x97, 0x78, 0x05, 0x72, 0x8f, 0x67, 0x50, 0x37, 0xc2, 0xf6, 0x72, 0x0c, 0x96, - 0x84, 0xe8, 0x28, 0xa2, 0x62, 0x81, 0x82, 0xa6, 0x30, 0x88, 0x22, 0xea, 0x82, 0x39, 0xbc, 0xc8, 0x65, 0xf2, 0x71, - 0x6a, 0x7c, 0xe6, 0x87, 0x31, 0xc6, 0x90, 0x0e, 0x06, 0x61, 0x35, 0x0b, 0x86, 0xe3, 0xd1, 0xe4, 0xe8, 0x29, 0x9c, - 0xdb, 0xc1, 0x38, 0x20, 0x83, 0xa0, 0x2e, 0x57, 0xb1, 0xa0, 0xe5, 0xcd, 0x95, 0x2d, 0x03, 0x3f, 0xae, 0x83, 0xc1, - 0x3f, 0x0b, 0x4f, 0xf1, 0x0e, 0x9a, 0x93, 0x73, 0x19, 0x82, 0x8d, 0xfd, 0x9a, 0x80, 0xa4, 0xac, 0xa7, 0xf9, 0x49, - 0x7d, 0xb8, 0x31, 0xa5, 0xfd, 0x33, 0x87, 0x17, 0x1c, 0x76, 0x48, 0xa0, 0x40, 0x1a, 0x4f, 0xb3, 0xd1, 0x2b, 0xa5, - 0xc8, 0x7d, 0x57, 0x70, 0xb8, 0x33, 0xf7, 0x9c, 0xe9, 0x91, 0x53, 0x48, 0x34, 0xb3, 0x80, 0x1b, 0xf9, 0x2b, 0x71, - 0x13, 0xe7, 0x59, 0x7a, 0xd0, 0x7c, 0x73, 0x50, 0xde, 0x8b, 0x2a, 0xbe, 0x1b, 0x05, 0xc6, 0x9a, 0x90, 0xfb, 0xaa, - 0x27, 0x40, 0x4f, 0x80, 0x2d, 0x00, 0x06, 0xc4, 0x3b, 0x66, 0x26, 0x33, 0x1e, 0x81, 0x47, 0x60, 0xd3, 0x07, 0xb2, - 0xb8, 0x77, 0x2e, 0x49, 0xfe, 0x66, 0x2a, 0xed, 0x55, 0xaf, 0xdc, 0x29, 0xc8, 0x7a, 0xb5, 0x95, 0xbb, 0x6e, 0x7d, - 0xf6, 0x4d, 0x87, 0x57, 0xe0, 0x85, 0x04, 0xb7, 0xc8, 0x7e, 0xbf, 0x29, 0xa8, 0x14, 0x46, 0x45, 0xbc, 0x93, 0x5c, - 0xa3, 0x7f, 0xbb, 0x37, 0x36, 0x8a, 0xe4, 0x96, 0x0f, 0x0f, 0xa0, 0xce, 0xe4, 0x5d, 0x71, 0x3b, 0x87, 0xa8, 0xad, - 0xbb, 0xf1, 0xc0, 0x7b, 0x83, 0x76, 0x59, 0x73, 0x04, 0x5b, 0x5e, 0x1c, 0x64, 0x30, 0x16, 0x38, 0x2b, 0x23, 0xa5, - 0xc6, 0x35, 0xa4, 0x16, 0x7c, 0x92, 0xa7, 0x7b, 0xc8, 0x52, 0x4f, 0x82, 0x22, 0xc7, 0xb3, 0x18, 0x32, 0x8d, 0xb7, - 0x81, 0xd8, 0xef, 0x65, 0x08, 0xd2, 0xb4, 0xed, 0xb6, 0x39, 0x02, 0x65, 0xf7, 0xc0, 0x94, 0xa4, 0xae, 0x8d, 0xa9, - 0x81, 0x86, 0x1e, 0x44, 0x8d, 0x54, 0xc4, 0xd9, 0xc9, 0x6b, 0xd0, 0x21, 0x82, 0xef, 0x77, 0x9a, 0x95, 0x1d, 0x2f, - 0x26, 0x04, 0x4f, 0xde, 0x17, 0x77, 0x59, 0x59, 0x95, 0xd1, 0xfb, 0x14, 0x0d, 0xa1, 0x12, 0x29, 0xa2, 0x97, 0x10, - 0x5f, 0xb0, 0xc4, 0xdf, 0x65, 0xf4, 0x63, 0x4a, 0xe3, 0x34, 0xc5, 0xf4, 0xe7, 0x05, 0xfc, 0x7c, 0x06, 0x28, 0x97, - 0xb8, 0x13, 0xa2, 0x0b, 0x09, 0xf6, 0x6a, 0x10, 0xdd, 0xab, 0xe2, 0x80, 0x29, 0x1a, 0xdd, 0x09, 0x8a, 0x98, 0x75, - 0x98, 0xfd, 0xfb, 0x02, 0x85, 0x42, 0xaa, 0x98, 0x5f, 0x84, 0x7d, 0x88, 0x7e, 0xc0, 0x22, 0x4f, 0xdf, 0xbd, 0x32, - 0x43, 0x1a, 0xdd, 0x4b, 0xaa, 0xb7, 0x36, 0x1e, 0x5b, 0x88, 0xd2, 0x13, 0x5d, 0xad, 0xe9, 0x79, 0xbc, 0xca, 0xa2, - 0x0d, 0xe0, 0x4f, 0xbc, 0x7b, 0xf5, 0x4c, 0x59, 0x98, 0x3c, 0xcf, 0x40, 0x71, 0x70, 0xfa, 0xee, 0xd5, 0x6b, 0x99, - 0xae, 0x73, 0x1e, 0x9d, 0x4b, 0x24, 0xad, 0xa7, 0xef, 0x5e, 0xfd, 0x84, 0xe6, 0x5e, 0x3f, 0x16, 0xf0, 0xfe, 0x25, - 0xf0, 0x96, 0x51, 0xbc, 0x86, 0x3e, 0xa9, 0xdf, 0xc9, 0x1a, 0x3b, 0xe5, 0xd5, 0x5a, 0x46, 0x3f, 0xa7, 0xb5, 0x27, - 0xad, 0xfa, 0x57, 0xe1, 0x53, 0x3b, 0x4f, 0xc0, 0x73, 0x97, 0x67, 0xe2, 0x63, 0x64, 0x45, 0x3b, 0x41, 0xf4, 0xe5, - 0xc1, 0xdd, 0x75, 0x2e, 0xca, 0x08, 0x5f, 0x30, 0xb4, 0x0b, 0x8a, 0x0e, 0x0f, 0x6f, 0x6f, 0x6f, 0x47, 0xb7, 0x5f, - 0x8d, 0x64, 0x71, 0x75, 0x38, 0xf9, 0xe6, 0x9b, 0x6f, 0x0e, 0xf1, 0x6d, 0xf0, 0x65, 0xdb, 0xed, 0xbd, 0x22, 0x7c, - 0xc0, 0x02, 0x44, 0xec, 0xfe, 0x12, 0xae, 0x28, 0xa0, 0x85, 0x1b, 0x7c, 0x19, 0x7c, 0xa9, 0x0f, 0x9d, 0x2f, 0x8f, - 0xcb, 0x9b, 0x2b, 0x55, 0x7e, 0x57, 0xc9, 0x47, 0xe3, 0xf1, 0xf8, 0x10, 0x24, 0x50, 0x5f, 0x0e, 0xf8, 0x20, 0x38, - 0x09, 0x06, 0x19, 0x5c, 0x68, 0xca, 0x9b, 0xab, 0x93, 0xc0, 0x33, 0xcd, 0x6d, 0xb0, 0x88, 0x0e, 0xc4, 0x25, 0x38, - 0xbc, 0xa2, 0xc1, 0x97, 0x01, 0x71, 0x29, 0x5f, 0x40, 0xca, 0x17, 0x47, 0x4f, 0xfd, 0xb4, 0xff, 0xa5, 0xd2, 0xbe, - 0xf2, 0xd3, 0x8e, 0x31, 0xed, 0xab, 0x67, 0x7e, 0xda, 0x89, 0x4a, 0x7b, 0xe1, 0xa7, 0xfd, 0xef, 0x72, 0x00, 0xa9, - 0x07, 0xbe, 0xf5, 0xdf, 0x85, 0xd7, 0x1a, 0x3c, 0x85, 0xa2, 0xec, 0x3a, 0xbe, 0xe2, 0xd0, 0xe8, 0xc1, 0xdd, 0x75, - 0x4e, 0x83, 0x01, 0xb6, 0xd7, 0x33, 0x09, 0xf1, 0x3e, 0xf8, 0x72, 0x5d, 0xe4, 0x61, 0xf0, 0xe5, 0x00, 0x0b, 0x19, - 0x7c, 0x19, 0x90, 0x2f, 0x8d, 0x81, 0x8c, 0x60, 0x9b, 0xc0, 0x85, 0x66, 0x1d, 0xda, 0x80, 0x69, 0xbe, 0x34, 0xae, - 0xa6, 0x7f, 0x16, 0xdd, 0xd9, 0xf0, 0x96, 0xa8, 0xdc, 0x74, 0x83, 0x9a, 0xbe, 0x05, 0xef, 0x04, 0x68, 0x54, 0x14, - 0xdc, 0xc4, 0x45, 0x38, 0x1c, 0x96, 0x37, 0x57, 0x04, 0xec, 0x32, 0x57, 0x3c, 0xae, 0xa2, 0x40, 0xc8, 0xa1, 0xfa, - 0x19, 0xa8, 0x48, 0x60, 0x01, 0x42, 0x19, 0xc1, 0x7f, 0x41, 0x4d, 0xdf, 0x49, 0xb6, 0x09, 0x86, 0xb7, 0xfc, 0xe2, - 0x63, 0x56, 0x0d, 0x95, 0x68, 0xf1, 0x46, 0x50, 0xf8, 0x01, 0x7f, 0x5d, 0xd5, 0xd1, 0x9f, 0xe0, 0xc6, 0xdd, 0xd4, - 0xb0, 0xbf, 0x93, 0x8e, 0x45, 0x7d, 0x27, 0xe7, 0xd9, 0x62, 0xda, 0x3a, 0xd0, 0xdf, 0x4a, 0x52, 0xcd, 0xb3, 0x41, - 0x30, 0x0c, 0x06, 0x7c, 0xc1, 0xde, 0xca, 0x39, 0xf7, 0xcc, 0xa7, 0x1e, 0x49, 0x7f, 0x9a, 0x67, 0xd9, 0x00, 0x7c, - 0x53, 0x90, 0x1f, 0x39, 0xfc, 0xef, 0xf9, 0x10, 0x85, 0x87, 0x83, 0x47, 0x87, 0x64, 0x16, 0xac, 0xee, 0xd0, 0xa3, - 0x33, 0x0a, 0x32, 0xb1, 0xe4, 0x45, 0x56, 0x79, 0x4b, 0xe5, 0x7e, 0xdd, 0xf6, 0xf2, 0xd8, 0x7b, 0x36, 0xaf, 0x62, - 0x11, 0xa8, 0x73, 0x0e, 0x14, 0x6f, 0x28, 0x7b, 0x2a, 0x9b, 0x12, 0x52, 0x6d, 0xc8, 0x1b, 0x96, 0x03, 0x16, 0x1c, - 0xf7, 0x86, 0xc3, 0x83, 0x60, 0xe0, 0xd4, 0xb9, 0x83, 0xe0, 0x60, 0x38, 0x3c, 0x09, 0xdc, 0x7d, 0x28, 0x1b, 0xb9, - 0x3b, 0x23, 0x2d, 0xd8, 0xbf, 0x8a, 0xb0, 0xa4, 0x20, 0x1e, 0x93, 0x5a, 0xfc, 0xa5, 0xc1, 0x65, 0x06, 0x00, 0x7d, - 0xa4, 0x24, 0x60, 0x06, 0x56, 0x66, 0x00, 0xa1, 0xca, 0x69, 0xcc, 0xce, 0x81, 0x79, 0x04, 0x8e, 0x59, 0xc1, 0x64, - 0x01, 0x62, 0x49, 0x80, 0x73, 0x17, 0x44, 0xb1, 0x2e, 0xe4, 0x11, 0x04, 0x01, 0xc0, 0x9f, 0xc4, 0x94, 0x82, 0x49, - 0x3a, 0x76, 0x23, 0x08, 0xe2, 0xf8, 0xec, 0x46, 0xb4, 0x26, 0x67, 0x89, 0x0e, 0x66, 0x24, 0x01, 0x36, 0xc4, 0xc0, - 0xf0, 0xc1, 0xfd, 0x1c, 0x94, 0x1e, 0x56, 0xef, 0x84, 0x5c, 0xf0, 0x3d, 0xf7, 0x64, 0xb3, 0x70, 0xf5, 0x84, 0x83, - 0xe0, 0x9e, 0x6b, 0x16, 0x60, 0x54, 0x15, 0xeb, 0xb2, 0xe2, 0xe9, 0x87, 0xfb, 0x15, 0xc4, 0x02, 0xc4, 0x01, 0x7d, - 0x27, 0xf3, 0x2c, 0xb9, 0x0f, 0x9d, 0x3d, 0xd7, 0x46, 0xa5, 0x7f, 0xff, 0xe1, 0xf5, 0x8f, 0x11, 0x88, 0x1c, 0x6b, - 0x43, 0xe9, 0xef, 0x39, 0x9e, 0x4d, 0x7e, 0xc4, 0x2b, 0x7f, 0x63, 0xdf, 0x73, 0x7b, 0x7a, 0xf4, 0xfb, 0x50, 0x37, - 0xbd, 0xe7, 0xb3, 0x7b, 0x3e, 0x72, 0xc5, 0xa1, 0xba, 0xc2, 0x7d, 0x7d, 0xbb, 0xf6, 0x8d, 0x90, 0x1e, 0x9e, 0x67, - 0xca, 0x1b, 0xf3, 0xa3, 0x1d, 0x0c, 0x83, 0x60, 0xaa, 0x85, 0x92, 0x10, 0x85, 0x84, 0x29, 0x01, 0x43, 0x74, 0xa0, - 0x97, 0xd5, 0x14, 0x39, 0x37, 0x35, 0xb2, 0xf0, 0x7e, 0xc0, 0xb4, 0xd0, 0xa1, 0x91, 0x43, 0xf9, 0xc1, 0xe1, 0x84, - 0x31, 0x0b, 0xbf, 0x55, 0xc2, 0xf4, 0xab, 0x45, 0xe5, 0x1c, 0x44, 0x0f, 0xc0, 0x18, 0x57, 0xf0, 0x02, 0xba, 0xc2, - 0x6e, 0xd6, 0x2a, 0x4a, 0x08, 0x82, 0xe9, 0x21, 0x07, 0xe8, 0x61, 0x17, 0xb4, 0xac, 0x2c, 0xd5, 0xad, 0xca, 0x59, - 0xaa, 0xa8, 0xcb, 0x50, 0x56, 0xc6, 0x0a, 0x03, 0xbf, 0x64, 0xdf, 0x17, 0xe8, 0x59, 0x3e, 0x15, 0x5d, 0xf0, 0x42, - 0x28, 0xc1, 0x72, 0x5d, 0xef, 0x44, 0x20, 0xea, 0xfc, 0xd0, 0xbb, 0xea, 0x6b, 0x5c, 0x3f, 0x9e, 0xbe, 0x96, 0x29, - 0xd7, 0x26, 0x14, 0x9a, 0xcf, 0x97, 0xbe, 0x62, 0xa2, 0x60, 0xb7, 0xd0, 0xaf, 0xb6, 0x8d, 0x3e, 0xbb, 0x5f, 0xeb, - 0xcd, 0xa0, 0x44, 0xc7, 0xbc, 0x46, 0xc1, 0xb5, 0x52, 0x28, 0x18, 0xed, 0x6d, 0xfc, 0x09, 0x8e, 0xdc, 0xea, 0xf6, - 0xd0, 0xfb, 0xad, 0x8a, 0xaf, 0xde, 0xa0, 0x6f, 0xa7, 0xfd, 0x39, 0xaa, 0xe4, 0xcf, 0xab, 0x15, 0xf8, 0x50, 0x41, - 0xa4, 0x15, 0x8b, 0xd3, 0x0b, 0xf5, 0x9c, 0xbd, 0x3b, 0x7d, 0x03, 0x7e, 0x94, 0xf8, 0xfb, 0x97, 0xef, 0x82, 0x9a, - 0x4c, 0xe3, 0x59, 0x61, 0x3e, 0xb4, 0x39, 0x20, 0x54, 0x8b, 0x4b, 0xb3, 0xef, 0x67, 0x71, 0x93, 0x7d, 0xd7, 0x6c, - 0x3d, 0x2d, 0x9a, 0x48, 0x52, 0x86, 0xdb, 0x07, 0x03, 0x02, 0x7d, 0x80, 0x28, 0xce, 0xbe, 0xa0, 0x31, 0xa4, 0xf9, - 0xcc, 0xbe, 0x1f, 0x21, 0xf0, 0xc5, 0x4e, 0x48, 0x35, 0xae, 0xb0, 0x68, 0xf4, 0x90, 0xcf, 0x78, 0xa4, 0x0c, 0x8b, - 0xde, 0x63, 0x02, 0x71, 0x86, 0xd3, 0xea, 0x3d, 0x62, 0x40, 0xe3, 0xdd, 0x40, 0xcb, 0x1e, 0xa2, 0x8c, 0xba, 0xec, - 0x0d, 0x8b, 0xef, 0x8f, 0xeb, 0x30, 0xb3, 0x96, 0x97, 0x43, 0xf8, 0x1b, 0x68, 0x03, 0x70, 0xca, 0x91, 0xe5, 0xab, - 0xcc, 0x46, 0x57, 0x4b, 0x4c, 0x6f, 0x22, 0x88, 0x4d, 0xa4, 0xd3, 0x61, 0xed, 0xea, 0x54, 0xbd, 0xab, 0x9d, 0xcf, - 0x44, 0xaf, 0x02, 0xad, 0x5c, 0xdb, 0x1e, 0x0f, 0xe1, 0x3f, 0xb5, 0xb4, 0xc2, 0x46, 0xd8, 0x73, 0xf1, 0x85, 0xe7, - 0xd8, 0x9c, 0x80, 0x06, 0xd7, 0x32, 0x05, 0xe0, 0x2c, 0xad, 0x46, 0xa3, 0x46, 0xd8, 0x67, 0xe5, 0x7c, 0x0e, 0x5b, - 0x0b, 0xf1, 0xb4, 0x00, 0x1c, 0xb8, 0x89, 0xc9, 0xc9, 0xbb, 0x31, 0x39, 0xa7, 0x1f, 0x15, 0xdc, 0x77, 0x70, 0x5e, - 0x2e, 0xe3, 0x54, 0xde, 0x02, 0x36, 0x65, 0xe0, 0xa7, 0x62, 0xa9, 0x5e, 0x42, 0xb2, 0xe4, 0xc9, 0x47, 0xb4, 0xda, - 0x48, 0x03, 0xe0, 0x2a, 0xa7, 0xc6, 0x72, 0x4f, 0x81, 0xa6, 0xba, 0x52, 0x54, 0x42, 0x5c, 0x55, 0x71, 0xb2, 0x3c, - 0xc3, 0xd4, 0x70, 0x03, 0xbd, 0x88, 0x02, 0xb9, 0xe2, 0x02, 0x48, 0x7a, 0xce, 0x7e, 0xcb, 0x34, 0xf6, 0xfa, 0x33, - 0x89, 0x02, 0x26, 0x8d, 0xa2, 0x8c, 0x95, 0xb2, 0x17, 0xd2, 0x44, 0xbf, 0x0b, 0x82, 0xda, 0xbd, 0xfc, 0x13, 0xea, - 0x7e, 0x06, 0xad, 0x08, 0x1b, 0xe0, 0x85, 0x1a, 0xfc, 0x30, 0xb5, 0x4b, 0xce, 0x03, 0x32, 0x74, 0xde, 0x67, 0xb5, - 0xdd, 0xea, 0xcf, 0x96, 0x80, 0xf5, 0x9a, 0x1a, 0x9f, 0xc2, 0x30, 0x21, 0x26, 0x56, 0xb2, 0x55, 0x56, 0xda, 0x0d, - 0x65, 0xda, 0x49, 0x97, 0xcc, 0x6b, 0xe1, 0x34, 0xef, 0x31, 0xb6, 0x1c, 0xa9, 0xdc, 0xfd, 0x7e, 0x68, 0x7e, 0xb2, - 0x9c, 0x3e, 0xd3, 0x21, 0xac, 0xbd, 0xf1, 0xa0, 0x39, 0xd1, 0xea, 0xaa, 0x8e, 0x7e, 0x40, 0x07, 0x60, 0xa6, 0x2d, - 0x42, 0xa5, 0x0b, 0xbe, 0xed, 0x2b, 0x51, 0x71, 0x49, 0xc2, 0x52, 0x49, 0x60, 0x67, 0x37, 0x25, 0x3b, 0x9b, 0x80, - 0x78, 0x86, 0xbb, 0x9e, 0x16, 0x3b, 0x21, 0x4d, 0x78, 0x8b, 0x83, 0x04, 0x44, 0x1d, 0xaa, 0xba, 0x84, 0x6c, 0x8c, - 0xa1, 0x8b, 0x7f, 0x51, 0x0a, 0x13, 0xd6, 0x32, 0xa9, 0x4a, 0x4c, 0x50, 0xa8, 0x72, 0xb7, 0x45, 0x60, 0x89, 0x82, - 0x1d, 0xc0, 0xde, 0xbb, 0x51, 0x37, 0xa3, 0xa6, 0xaa, 0x53, 0x2f, 0xc1, 0xc7, 0x69, 0xd6, 0x55, 0x90, 0x59, 0xd8, - 0x55, 0xb1, 0xe6, 0x81, 0x8e, 0xd5, 0xa5, 0x8c, 0x89, 0xbb, 0xb4, 0xc8, 0x10, 0x1f, 0x19, 0x63, 0x0b, 0x6b, 0x38, - 0xd2, 0xf6, 0xb8, 0xe9, 0x09, 0x42, 0x3f, 0x61, 0x43, 0x09, 0xdc, 0x74, 0xb6, 0xa7, 0xa6, 0x99, 0x0f, 0x88, 0x38, - 0x0c, 0x28, 0x90, 0x6c, 0x1c, 0xd2, 0x1c, 0xe9, 0x0b, 0x92, 0x26, 0x0c, 0x94, 0xad, 0x78, 0x4e, 0x90, 0x15, 0x85, - 0x9e, 0xad, 0xab, 0x36, 0xce, 0x95, 0x61, 0x8e, 0x96, 0x9c, 0x0a, 0x4f, 0x13, 0x64, 0x62, 0x7b, 0xda, 0x66, 0x26, - 0xc3, 0x51, 0xb2, 0xc0, 0xfc, 0x0a, 0xa2, 0xc4, 0x9d, 0x69, 0x56, 0xe5, 0x60, 0x5c, 0xc0, 0x02, 0xad, 0x7c, 0x0f, - 0xea, 0xc6, 0x1a, 0xda, 0x68, 0x58, 0x66, 0xb7, 0x3f, 0xc1, 0x7e, 0xad, 0x9d, 0xd6, 0x65, 0x8a, 0xe5, 0x65, 0x0a, - 0xd1, 0x5e, 0xc8, 0xfc, 0x46, 0x91, 0xe8, 0x4e, 0x11, 0x86, 0x84, 0x75, 0x94, 0x3d, 0x69, 0x53, 0x03, 0xe8, 0xa9, - 0x17, 0x00, 0xbe, 0x73, 0x2d, 0xc3, 0x2e, 0xd2, 0xfd, 0x55, 0xc1, 0xb8, 0x74, 0x83, 0x20, 0x45, 0x6f, 0x52, 0x30, - 0xe7, 0xf5, 0x28, 0xa9, 0x37, 0xa7, 0x2d, 0x33, 0xaa, 0x8e, 0x8a, 0x90, 0x72, 0x82, 0xff, 0xe4, 0x95, 0xd4, 0xc4, - 0x26, 0x4c, 0xf0, 0xc0, 0x87, 0x79, 0x86, 0x0d, 0xbc, 0xdd, 0xbe, 0x4b, 0xc3, 0xa4, 0xcd, 0x36, 0xa4, 0x20, 0xad, - 0x30, 0x71, 0x42, 0xa0, 0xb2, 0x57, 0xb8, 0x5f, 0xb0, 0x9d, 0x34, 0x05, 0x0f, 0xc2, 0x46, 0x03, 0x13, 0xb7, 0xba, - 0xf8, 0x3a, 0x4c, 0x68, 0xb8, 0xa4, 0xda, 0xd9, 0x49, 0x4b, 0x9a, 0xdb, 0xeb, 0xf2, 0xd2, 0xf6, 0x41, 0xc7, 0x52, - 0xeb, 0x1a, 0x1e, 0x68, 0x5e, 0xb3, 0x8b, 0x2b, 0xa6, 0x69, 0xa2, 0xb1, 0x1e, 0x52, 0x96, 0x1c, 0xeb, 0x7a, 0xba, - 0xc2, 0xd5, 0x32, 0xd3, 0x40, 0xf7, 0x12, 0x2f, 0xf4, 0x80, 0x0f, 0x1e, 0xae, 0x48, 0x74, 0x89, 0xcd, 0x66, 0xab, - 0x9a, 0x4c, 0xf3, 0x7d, 0xd9, 0x72, 0x13, 0x20, 0xcf, 0x52, 0xdf, 0xdc, 0x27, 0xc7, 0x9a, 0xb6, 0xf9, 0x49, 0x80, - 0x6b, 0xee, 0x15, 0x90, 0x74, 0x2c, 0x41, 0x17, 0xef, 0xd3, 0x1f, 0x44, 0x6a, 0xa6, 0x82, 0xee, 0x9d, 0x2f, 0x52, - 0x37, 0xbf, 0x00, 0xdb, 0xa8, 0x8d, 0x31, 0xcd, 0xca, 0xd6, 0x61, 0xa2, 0x2c, 0xac, 0x91, 0x85, 0x5c, 0x82, 0x0f, - 0xe6, 0x6e, 0x53, 0xa7, 0xa7, 0x1d, 0x44, 0xd8, 0xef, 0xa2, 0xc7, 0x23, 0x8c, 0x15, 0x6b, 0x90, 0x18, 0x56, 0x61, - 0x4d, 0x9b, 0xcb, 0x21, 0xca, 0xa9, 0x59, 0x32, 0xd1, 0x92, 0xfa, 0x94, 0x22, 0x4a, 0xc1, 0xdc, 0x78, 0x5a, 0x36, - 0x4c, 0x09, 0x11, 0xb2, 0x42, 0x3a, 0xa0, 0x5a, 0x0b, 0x2d, 0xd5, 0x04, 0x01, 0x0f, 0xbd, 0x2c, 0x34, 0xa6, 0x20, - 0xfa, 0x88, 0x0c, 0x37, 0xe2, 0xc8, 0xe8, 0xee, 0x18, 0xc5, 0x04, 0x42, 0x77, 0x7b, 0x79, 0x61, 0xf5, 0x69, 0xd9, - 0x56, 0x07, 0x71, 0x8d, 0x69, 0xb2, 0x87, 0xa0, 0xc6, 0x28, 0x68, 0x73, 0xba, 0xd1, 0x9f, 0x8b, 0xd0, 0xb7, 0x0b, - 0xc7, 0x6e, 0x14, 0x44, 0x42, 0x44, 0x5a, 0xaf, 0xa9, 0x18, 0xa0, 0x76, 0x1e, 0xbb, 0x88, 0x55, 0xba, 0x5b, 0x88, - 0xf2, 0x46, 0x65, 0xfd, 0x71, 0x1d, 0x92, 0xed, 0x16, 0xcb, 0x02, 0x5f, 0xf6, 0xb3, 0xf5, 0x1e, 0x08, 0xf4, 0xd7, - 0xeb, 0x4f, 0x42, 0xa0, 0xbf, 0xca, 0x3e, 0x07, 0x02, 0xfd, 0xf5, 0xfa, 0x7f, 0x1a, 0x02, 0xfd, 0x6c, 0xed, 0x41, - 0xa0, 0xab, 0xc1, 0xf8, 0xb5, 0x60, 0xc1, 0xdb, 0x37, 0x01, 0x7d, 0x2e, 0x59, 0xf0, 0xf6, 0xe5, 0x4b, 0xdf, 0x08, - 0x44, 0x68, 0x24, 0x7f, 0x23, 0x0b, 0x46, 0xdc, 0x16, 0x78, 0x85, 0x5a, 0x27, 0x1f, 0xa8, 0x28, 0x03, 0x20, 0xfa, - 0xf2, 0x9f, 0x59, 0xb5, 0x0c, 0x83, 0xc3, 0x80, 0xcc, 0x1c, 0x24, 0xe8, 0x70, 0x02, 0xb7, 0x37, 0x28, 0xe5, 0xbb, - 0xcf, 0x42, 0x53, 0x1f, 0x8d, 0x46, 0x71, 0x71, 0x85, 0x77, 0x3a, 0xb3, 0x8f, 0x10, 0xef, 0x38, 0xe3, 0xa5, 0x8d, - 0x98, 0xb1, 0x8c, 0xcb, 0x73, 0x1d, 0xaa, 0xa6, 0xb4, 0x3b, 0xb1, 0x5c, 0xca, 0xdb, 0x73, 0x80, 0xed, 0xb7, 0x5b, - 0x33, 0xc6, 0x6e, 0x28, 0x86, 0x58, 0xc7, 0xd3, 0x7d, 0xb6, 0xd6, 0xef, 0x2e, 0xe2, 0x92, 0xbf, 0x8b, 0xab, 0x25, - 0x83, 0x4e, 0xea, 0xed, 0x5a, 0xc8, 0xf5, 0xca, 0x55, 0x72, 0xbe, 0x16, 0x1f, 0x85, 0xbc, 0x15, 0x6a, 0x53, 0x9d, - 0xf3, 0x1b, 0x68, 0x11, 0xdb, 0xa0, 0x32, 0x42, 0xf0, 0xa4, 0xf2, 0x58, 0x2c, 0x05, 0xf2, 0x9e, 0x51, 0x03, 0xf3, - 0xde, 0x91, 0x83, 0x86, 0x76, 0x10, 0xb5, 0xc7, 0xb0, 0x91, 0x45, 0x67, 0x60, 0xe2, 0xf8, 0x02, 0x4a, 0x07, 0x28, - 0x6e, 0x88, 0x03, 0x01, 0x77, 0x0a, 0xe4, 0x79, 0x1b, 0x50, 0x2c, 0xb4, 0xf4, 0xfd, 0x40, 0xd4, 0x19, 0x6a, 0x60, - 0x0c, 0x1b, 0xc3, 0x84, 0xf7, 0x26, 0xf4, 0x05, 0x05, 0x8d, 0x6e, 0x01, 0x2e, 0x87, 0x7f, 0xae, 0xf9, 0x79, 0x96, - 0x22, 0xe0, 0x4d, 0x96, 0x2a, 0x6b, 0xa2, 0x1e, 0x0a, 0x39, 0xf0, 0xd9, 0x53, 0x3e, 0xe9, 0x78, 0x61, 0x9e, 0xbd, - 0xd5, 0x46, 0xa9, 0x58, 0xe7, 0x60, 0xeb, 0xe3, 0xd7, 0x32, 0x97, 0x3a, 0xe0, 0xf4, 0xb9, 0x58, 0x5f, 0xf3, 0x22, - 0x4b, 0xce, 0x97, 0x59, 0x59, 0xc9, 0xe2, 0x7e, 0x61, 0x70, 0x0c, 0x74, 0x59, 0xad, 0x49, 0xdc, 0xfb, 0x1d, 0x38, - 0x33, 0xab, 0xc8, 0x14, 0xc3, 0xa7, 0x63, 0x52, 0x6b, 0x33, 0x68, 0x68, 0x20, 0xb5, 0xbf, 0x53, 0x09, 0xc0, 0xe9, - 0xee, 0xd9, 0x76, 0x8d, 0x36, 0x0d, 0xd8, 0xdb, 0x35, 0x52, 0xb3, 0x94, 0x0a, 0xfe, 0xe7, 0x9a, 0x1b, 0x18, 0xfb, - 0xd0, 0x41, 0x34, 0x97, 0x3d, 0xad, 0x63, 0x50, 0xd8, 0x3e, 0x44, 0xf1, 0xf8, 0x69, 0xfa, 0x02, 0xa1, 0xb6, 0xe1, - 0x6e, 0x8b, 0xda, 0x73, 0x1b, 0xa9, 0xa9, 0x6b, 0x6d, 0xcc, 0xa1, 0xad, 0x8b, 0xd9, 0xa7, 0x32, 0x0c, 0x06, 0xd1, - 0xa7, 0xb2, 0xb0, 0xc9, 0x03, 0x4b, 0x50, 0x65, 0x39, 0x36, 0x16, 0x73, 0x5a, 0x05, 0x0e, 0x89, 0x1e, 0x26, 0x2d, - 0x60, 0xcf, 0x00, 0x52, 0x6d, 0x02, 0xa3, 0xaa, 0xb5, 0xa2, 0x0e, 0x6c, 0x76, 0x8a, 0x46, 0x0b, 0xe1, 0xef, 0x8f, - 0x36, 0xcd, 0xcd, 0x50, 0x1f, 0x3e, 0xda, 0xc4, 0xf0, 0x5f, 0x52, 0xcf, 0x52, 0x5e, 0xc5, 0x59, 0xce, 0xe2, 0x3c, - 0xff, 0x9d, 0x6e, 0xae, 0x79, 0xb5, 0x94, 0x69, 0x14, 0x7c, 0xf7, 0xe2, 0x43, 0x60, 0xb4, 0x96, 0xb9, 0xc6, 0xab, - 0xd1, 0x82, 0xfc, 0x5c, 0x5e, 0x85, 0x39, 0xa1, 0xbd, 0x7c, 0x24, 0x3f, 0xee, 0x04, 0x78, 0xfc, 0xfd, 0xfb, 0x0f, - 0x1f, 0xde, 0x1d, 0xa0, 0xac, 0xbf, 0x77, 0x70, 0xa6, 0x1c, 0xc7, 0x0f, 0x1e, 0x6d, 0x72, 0xad, 0x5d, 0xad, 0x7f, - 0x77, 0x17, 0xf7, 0x96, 0x6e, 0x34, 0xd7, 0x5b, 0xc0, 0xab, 0xa2, 0x35, 0x37, 0xb9, 0x53, 0x60, 0xfa, 0x99, 0x95, - 0x62, 0x21, 0x40, 0xb1, 0xb9, 0xaa, 0x39, 0x0a, 0x28, 0xe4, 0x05, 0x90, 0xfd, 0xb0, 0xda, 0xb3, 0x19, 0xab, 0xae, - 0xcd, 0x28, 0x8b, 0x2a, 0x13, 0x57, 0xe7, 0x48, 0x1f, 0x3e, 0x6b, 0x53, 0x9a, 0x65, 0xa2, 0x28, 0x4a, 0x7b, 0x3f, - 0x36, 0x50, 0xaa, 0xb4, 0x3d, 0xa6, 0xde, 0x65, 0x20, 0x2b, 0x29, 0xeb, 0xa9, 0xff, 0xb1, 0x31, 0x16, 0xf0, 0xd3, - 0x14, 0x86, 0x17, 0x1c, 0x7f, 0xec, 0x24, 0x1e, 0x99, 0xf6, 0xdd, 0xe2, 0x95, 0xf9, 0x38, 0x69, 0x25, 0xcc, 0x86, - 0x93, 0x68, 0x42, 0x6c, 0x68, 0x01, 0x4d, 0xe5, 0xbe, 0x1b, 0xbd, 0x78, 0xf3, 0xe1, 0xd5, 0x87, 0x7f, 0x9d, 0x3f, - 0x3b, 0xfd, 0xf0, 0xe2, 0xbb, 0xb7, 0xef, 0x5f, 0xbd, 0x38, 0x43, 0x1c, 0x3d, 0x8d, 0x55, 0x18, 0x6e, 0xb4, 0x41, - 0x6c, 0xb3, 0xac, 0x48, 0xd4, 0xa4, 0xd9, 0x14, 0x05, 0x16, 0x84, 0x99, 0x6d, 0x91, 0x3f, 0xbf, 0x79, 0xfe, 0xe2, - 0xe5, 0xab, 0x37, 0x2f, 0x9e, 0xb7, 0xbf, 0x1e, 0x4e, 0x6a, 0x52, 0xbb, 0x99, 0xd3, 0xc1, 0x31, 0xb8, 0x1d, 0xaf, - 0x0e, 0x0a, 0x86, 0x0a, 0x59, 0x9f, 0x82, 0x65, 0x40, 0xb1, 0x98, 0x12, 0xd1, 0xe2, 0x6f, 0x1d, 0x88, 0x2a, 0x6b, - 0x6d, 0x80, 0x12, 0x07, 0x33, 0xa3, 0x8a, 0x64, 0x44, 0x02, 0x76, 0x83, 0x2d, 0x07, 0x0c, 0x5f, 0x53, 0x0a, 0x48, - 0x3e, 0x1d, 0xbb, 0x83, 0x2a, 0x7c, 0xfd, 0xf3, 0x24, 0xae, 0xf8, 0x95, 0x2c, 0xee, 0xa3, 0x6c, 0xd4, 0x4a, 0xa1, - 0x8d, 0x25, 0x11, 0x85, 0x20, 0x65, 0x6c, 0x24, 0x11, 0x45, 0x4e, 0x66, 0xde, 0xa0, 0xb8, 0x71, 0x9e, 0x3b, 0xe8, - 0xf8, 0x76, 0xc1, 0x64, 0xb1, 0xdd, 0x76, 0x0c, 0x63, 0x27, 0xbd, 0x8c, 0xe6, 0x99, 0x22, 0xa4, 0x0b, 0xe0, 0xd2, - 0xe0, 0x48, 0x54, 0xe7, 0x1d, 0x33, 0x47, 0xe4, 0xa9, 0x0e, 0x01, 0x09, 0xa6, 0x69, 0xee, 0xb5, 0x89, 0x32, 0xd2, - 0x3c, 0x43, 0xc7, 0x2d, 0x2a, 0x6d, 0x00, 0x5f, 0x5b, 0xa9, 0x6a, 0xe1, 0x69, 0xa0, 0x3d, 0x98, 0x3b, 0x88, 0xcd, - 0x64, 0xe4, 0x78, 0x61, 0x0e, 0xe6, 0x12, 0x8d, 0x19, 0x37, 0xe3, 0x90, 0x47, 0xd2, 0x60, 0xa6, 0x81, 0xfd, 0xd8, - 0x9e, 0x5c, 0xcb, 0xa8, 0x68, 0xa0, 0x1f, 0xca, 0xe6, 0xa0, 0x1e, 0x17, 0xcd, 0x67, 0x58, 0xd8, 0xad, 0x2c, 0x28, - 0xbf, 0x6b, 0x66, 0x82, 0x7b, 0x66, 0x32, 0x53, 0xd5, 0x8f, 0x2a, 0xf9, 0xa3, 0xbc, 0x35, 0xb2, 0xc2, 0xe3, 0xa2, - 0x23, 0x11, 0x77, 0x4b, 0x14, 0x1f, 0x27, 0xea, 0xc7, 0xa4, 0xde, 0x73, 0x70, 0xd4, 0x6e, 0x80, 0xad, 0x2c, 0xfb, - 0x77, 0xc5, 0x3f, 0x9f, 0x3f, 0xda, 0x64, 0xfa, 0xa4, 0xaa, 0x7f, 0xcf, 0x6c, 0x24, 0xd0, 0x06, 0x33, 0x52, 0xeb, - 0xa1, 0xf7, 0x81, 0x27, 0x3b, 0xb2, 0xe9, 0xf5, 0xc1, 0xb2, 0x4e, 0x8e, 0x66, 0xa4, 0x1e, 0xb9, 0x0a, 0x8c, 0xd8, - 0x9d, 0x85, 0xdf, 0xf1, 0x24, 0xec, 0x6a, 0x98, 0x92, 0x35, 0x98, 0x2e, 0x20, 0x16, 0xef, 0xfe, 0x43, 0xc1, 0x7e, - 0x86, 0xbf, 0xb3, 0x14, 0xfe, 0x56, 0xb5, 0x77, 0x30, 0xbc, 0x7b, 0x7b, 0xf6, 0x01, 0x14, 0x1c, 0x31, 0x6a, 0x24, - 0x37, 0x81, 0x36, 0x66, 0x18, 0x82, 0xca, 0x20, 0x88, 0x82, 0x78, 0x05, 0x27, 0x3b, 0xb2, 0x8e, 0x87, 0x77, 0xc3, - 0xdb, 0xdb, 0xdb, 0xff, 0xd3, 0xdc, 0xb3, 0x6e, 0xb7, 0x6d, 0x23, 0xfd, 0xbf, 0x4f, 0xc1, 0x30, 0xd9, 0x94, 0x4c, - 0x48, 0x9a, 0x94, 0x2c, 0x5b, 0x91, 0x2c, 0xb9, 0xcd, 0xa5, 0x5b, 0x77, 0xdd, 0xa6, 0x27, 0x71, 0xfb, 0xed, 0xae, - 0xeb, 0x63, 0x51, 0x12, 0x24, 0x71, 0x43, 0x91, 0x3a, 0x24, 0xe5, 0x4b, 0x15, 0xee, 0xb3, 0xec, 0x23, 0x7c, 0xcf, - 0xd0, 0x27, 0xfb, 0xce, 0xcc, 0x00, 0x24, 0x78, 0x93, 0xe4, 0x4d, 0xda, 0x7e, 0xa7, 0x4d, 0x22, 0x82, 0x00, 0x08, - 0x0c, 0x80, 0xb9, 0x61, 0x2e, 0x26, 0x98, 0x36, 0x9a, 0xeb, 0xc8, 0x67, 0xc1, 0x24, 0x84, 0xc4, 0x24, 0xa9, 0x40, - 0xf8, 0xac, 0x84, 0xf0, 0x21, 0x2e, 0x2a, 0x4f, 0xac, 0xf1, 0x7e, 0x11, 0xde, 0x7e, 0xed, 0xfb, 0xb2, 0xfc, 0x2e, - 0x98, 0x3e, 0x2e, 0xd2, 0x16, 0x10, 0x88, 0x06, 0xd7, 0x10, 0x96, 0x17, 0x5f, 0xf3, 0x8b, 0xe3, 0xe9, 0xf5, 0xf8, - 0xfe, 0x9a, 0x2b, 0xa7, 0xb3, 0xc0, 0xb4, 0xaf, 0x46, 0x27, 0x53, 0xef, 0x46, 0x41, 0xce, 0x74, 0xa0, 0x82, 0x57, - 0x8f, 0xcf, 0xc6, 0xeb, 0x24, 0x09, 0x03, 0x33, 0x0a, 0x6f, 0xd5, 0xe1, 0x09, 0x3d, 0x88, 0x0a, 0x2e, 0x3d, 0xaa, - 0xca, 0x57, 0x13, 0xdf, 0x9b, 0x7c, 0x18, 0xa8, 0x4f, 0x36, 0xde, 0x60, 0x58, 0xe2, 0x3f, 0xed, 0x54, 0x1d, 0xc2, - 0x58, 0x95, 0xaf, 0x7d, 0xff, 0xe4, 0x80, 0x5a, 0x0c, 0x4f, 0x0e, 0xa6, 0xde, 0xcd, 0x50, 0xca, 0x11, 0xc2, 0x2f, - 0xd0, 0x06, 0x3c, 0x16, 0x63, 0x66, 0x72, 0x14, 0xa3, 0x73, 0xff, 0x84, 0x69, 0xb9, 0x14, 0x04, 0x41, 0x47, 0x68, - 0xbc, 0xda, 0x04, 0xf5, 0xaa, 0x3e, 0xf0, 0xfc, 0x1f, 0x3f, 0x6a, 0x99, 0x41, 0xe2, 0x42, 0x8a, 0xd6, 0x85, 0xf7, - 0x3d, 0x58, 0xc5, 0xc0, 0x90, 0x23, 0xba, 0x26, 0x62, 0x8a, 0xf9, 0xba, 0x31, 0x49, 0x0d, 0x4c, 0xb5, 0xe2, 0xae, - 0x80, 0x47, 0xe0, 0x3f, 0x25, 0xd1, 0x68, 0x02, 0xe9, 0x95, 0x25, 0x04, 0xaf, 0x4b, 0xca, 0x77, 0x3a, 0x9b, 0x3c, - 0x60, 0x1c, 0x68, 0xcd, 0xf1, 0x3b, 0xa4, 0x10, 0xd7, 0x7c, 0x1d, 0xd2, 0x7b, 0x65, 0x51, 0x5a, 0xdc, 0x54, 0x24, - 0xd4, 0x12, 0x70, 0x39, 0x2d, 0xac, 0x50, 0xaf, 0xbc, 0x5e, 0x22, 0x7c, 0xe0, 0xa3, 0xb8, 0x69, 0xc9, 0xe0, 0x32, - 0x47, 0x4b, 0x8c, 0x12, 0x3d, 0x06, 0xf7, 0x2e, 0xe9, 0x06, 0x81, 0x19, 0xda, 0x65, 0x6c, 0x84, 0x57, 0x39, 0x0d, - 0x8b, 0x09, 0x7d, 0xf6, 0xc2, 0x34, 0x8f, 0xe4, 0x4b, 0xab, 0x3e, 0x7c, 0xb2, 0x09, 0x90, 0xe8, 0xc5, 0x83, 0x61, - 0x71, 0x1f, 0x24, 0xee, 0xd8, 0xa4, 0xcd, 0xac, 0x2a, 0x5f, 0x4d, 0xc7, 0x7e, 0xb6, 0xd8, 0x74, 0x34, 0x16, 0x6e, - 0x30, 0xf5, 0xd9, 0x85, 0x3b, 0xfe, 0x16, 0xeb, 0xbc, 0x1e, 0xfb, 0xaf, 0xa0, 0x42, 0xaa, 0x0e, 0x9f, 0x6c, 0x88, - 0xac, 0xd7, 0xa1, 0xf1, 0x94, 0xb6, 0x40, 0xf9, 0x3b, 0x3c, 0xf7, 0x0e, 0x8b, 0xa8, 0x35, 0x0e, 0x96, 0x48, 0x31, - 0xe1, 0xd9, 0xe2, 0xc8, 0x78, 0xee, 0x17, 0xd8, 0x9b, 0x0a, 0x3f, 0x94, 0x30, 0xae, 0x50, 0x1c, 0x50, 0x79, 0x67, - 0xca, 0x83, 0x25, 0x92, 0xfb, 0x2e, 0xbc, 0x15, 0x23, 0xe5, 0x00, 0xa0, 0x58, 0x85, 0xa7, 0xaf, 0x46, 0x27, 0xf2, - 0xfd, 0x00, 0x2a, 0x51, 0xa9, 0x5f, 0xf8, 0x95, 0xaa, 0x4a, 0x9e, 0x09, 0x68, 0x75, 0xa7, 0x0e, 0x4f, 0x0e, 0xe4, - 0xda, 0xc3, 0x51, 0xef, 0x5c, 0x9a, 0x1c, 0xf6, 0x0a, 0x40, 0x28, 0x96, 0x55, 0xa8, 0x0e, 0x24, 0xc7, 0xcb, 0xe9, - 0x12, 0x6d, 0x0f, 0x81, 0x16, 0x43, 0xbd, 0x97, 0xad, 0x11, 0xd9, 0xe0, 0x89, 0xde, 0x46, 0xfc, 0xdf, 0x7c, 0xce, - 0xa8, 0xd3, 0x64, 0x41, 0x1c, 0x46, 0x2a, 0xcc, 0xa3, 0x9c, 0x21, 0x47, 0x91, 0x32, 0x73, 0xe1, 0x8c, 0x6a, 0xa9, - 0x29, 0x40, 0xe4, 0xa0, 0xdc, 0x54, 0x9a, 0xd8, 0x48, 0xcf, 0x7f, 0x28, 0x7c, 0x32, 0x25, 0xac, 0x94, 0x0d, 0xb0, - 0x39, 0xf3, 0xd0, 0xe5, 0x5b, 0xcf, 0xf8, 0x9f, 0xd0, 0x98, 0xbb, 0xc6, 0xd2, 0x35, 0xde, 0x07, 0x57, 0x69, 0xed, - 0xea, 0x64, 0x59, 0xc3, 0x0c, 0xd6, 0xd7, 0x20, 0xd6, 0x0e, 0xd7, 0x80, 0x70, 0xbd, 0x80, 0x67, 0x71, 0xeb, 0x80, - 0x0b, 0x37, 0x9a, 0x33, 0x91, 0xac, 0x4b, 0xbc, 0x4d, 0x38, 0x54, 0x74, 0x09, 0x2c, 0x10, 0x88, 0x4a, 0x08, 0x38, - 0x9e, 0x35, 0x49, 0x22, 0xff, 0x6f, 0xec, 0x1e, 0x24, 0xcf, 0x38, 0x09, 0x57, 0xa0, 0x9d, 0x70, 0xe7, 0x5c, 0xdb, - 0x6c, 0x00, 0x2f, 0xb3, 0xcf, 0xe7, 0x3e, 0x7e, 0x64, 0x52, 0xfe, 0xa8, 0x24, 0x9c, 0xcf, 0x7d, 0xa6, 0x49, 0x79, - 0xa6, 0xb2, 0xcf, 0x9c, 0x3e, 0xb2, 0x45, 0x8c, 0x62, 0x3d, 0x6d, 0x3a, 0x39, 0x39, 0x2b, 0x28, 0xee, 0x75, 0x49, - 0x58, 0xc7, 0xdb, 0xa8, 0x1b, 0xbc, 0xd0, 0xe5, 0xeb, 0x92, 0x9f, 0x4c, 0x73, 0x1a, 0xae, 0xc7, 0x3e, 0x33, 0x71, - 0xbb, 0xc3, 0x27, 0x37, 0xe3, 0xf5, 0x78, 0xec, 0x53, 0x62, 0x28, 0x88, 0xb4, 0x15, 0xc6, 0xa8, 0x01, 0x4b, 0xf5, - 0x3e, 0x32, 0x68, 0x49, 0x79, 0xf8, 0x60, 0x1d, 0x07, 0x62, 0x03, 0x7d, 0x20, 0x01, 0x6d, 0x57, 0xf5, 0xd0, 0x0e, - 0x54, 0x10, 0x57, 0x58, 0xac, 0xf6, 0x6b, 0x38, 0xb9, 0xc1, 0xa5, 0xfa, 0x1e, 0x21, 0x8c, 0xd9, 0xeb, 0x5f, 0xd1, - 0xde, 0x55, 0x0d, 0x95, 0x8c, 0x7c, 0x78, 0x1e, 0x31, 0xd5, 0x50, 0x5f, 0x7b, 0xee, 0x3c, 0x08, 0xe3, 0xc4, 0x9b, - 0xa8, 0x57, 0xfd, 0x33, 0x4f, 0xbb, 0x5c, 0x26, 0x9a, 0x7e, 0x65, 0xfc, 0x55, 0xce, 0xf8, 0x24, 0x50, 0x21, 0x26, - 0x7c, 0x6a, 0xa8, 0x23, 0x9f, 0x9e, 0x6d, 0xf5, 0x04, 0xca, 0xc5, 0x3a, 0x7f, 0x1d, 0x40, 0xad, 0x52, 0xee, 0x28, - 0x4c, 0x0a, 0x08, 0xb9, 0xa3, 0xfe, 0xaa, 0xf7, 0x49, 0x2b, 0xf3, 0x6a, 0xbd, 0x41, 0x5e, 0x21, 0xc9, 0xa9, 0x2b, - 0x86, 0x3b, 0x17, 0x3e, 0x82, 0xf4, 0xfc, 0x48, 0xb6, 0x6f, 0x2f, 0xd0, 0xe9, 0xd1, 0xd7, 0x45, 0xc6, 0x03, 0x18, - 0x04, 0x30, 0x2e, 0x0b, 0xc2, 0x44, 0x81, 0x18, 0x5e, 0xf0, 0xc1, 0x51, 0xd9, 0x1e, 0x96, 0xf7, 0xaa, 0xe9, 0x29, - 0xc7, 0x02, 0x2f, 0x91, 0x58, 0x8a, 0xec, 0xef, 0x18, 0x8e, 0xb2, 0x10, 0xb1, 0x87, 0x7b, 0x61, 0xc1, 0xf2, 0x15, - 0xd8, 0x36, 0x09, 0xb1, 0x17, 0x09, 0xf6, 0x93, 0x4d, 0x7c, 0x2a, 0xa8, 0xf6, 0x59, 0x8c, 0x6b, 0x09, 0xfc, 0x08, - 0x27, 0xe3, 0xa9, 0xaa, 0x9c, 0x0a, 0x52, 0x83, 0x75, 0x0b, 0xf8, 0x53, 0x13, 0x5c, 0xae, 0x48, 0xea, 0xae, 0xf1, - 0x14, 0xd4, 0x82, 0xef, 0x2a, 0x1d, 0x3d, 0x08, 0xcb, 0x93, 0xb1, 0x54, 0x09, 0xd8, 0xd6, 0x22, 0x45, 0x00, 0xcc, - 0xc5, 0x99, 0x80, 0x51, 0x7a, 0x0d, 0xfc, 0x23, 0xc4, 0xaa, 0x12, 0x73, 0x34, 0x42, 0x39, 0x5d, 0x98, 0x17, 0xac, - 0xd6, 0x09, 0xc6, 0x20, 0x87, 0x01, 0xb0, 0x54, 0x55, 0x50, 0x5a, 0x04, 0x64, 0x9e, 0x4b, 0x41, 0xa9, 0xaa, 0x78, - 0xd3, 0x6a, 0x19, 0x57, 0xdd, 0x00, 0x8e, 0xc3, 0x69, 0xa0, 0x06, 0x1f, 0x1e, 0x23, 0x3e, 0x8d, 0x89, 0x91, 0x27, - 0xf0, 0xd0, 0x26, 0x78, 0xd3, 0x5d, 0x83, 0x40, 0x26, 0xd4, 0x4f, 0x5f, 0xf3, 0x6b, 0x27, 0x0b, 0x71, 0x89, 0x0b, - 0xd3, 0x1c, 0x3d, 0xd9, 0x04, 0xe9, 0x29, 0xc0, 0x6e, 0xf0, 0x64, 0xe3, 0x66, 0x46, 0x54, 0xea, 0x85, 0x4a, 0x16, - 0x54, 0x23, 0x04, 0xc3, 0x28, 0xbd, 0xce, 0x5d, 0x1a, 0xf3, 0xf9, 0xc2, 0x96, 0xa4, 0x72, 0x05, 0x6d, 0x9a, 0x06, - 0xdc, 0x72, 0x69, 0x15, 0x79, 0x4b, 0x37, 0xba, 0x27, 0x43, 0x27, 0x43, 0xb6, 0x86, 0xd2, 0x55, 0x85, 0xe8, 0x01, - 0x01, 0x80, 0x48, 0x83, 0xaa, 0x7c, 0x95, 0x95, 0x31, 0x3e, 0xdb, 0xcc, 0xda, 0x03, 0xbe, 0x75, 0xad, 0x3e, 0x67, - 0x16, 0xa9, 0x34, 0xa8, 0x49, 0x5f, 0x8b, 0x1b, 0xa6, 0x17, 0x17, 0xa7, 0x17, 0x14, 0x37, 0x1a, 0x4e, 0x86, 0x28, - 0x05, 0x8d, 0x1b, 0x67, 0x86, 0xe9, 0x0e, 0xeb, 0x57, 0x94, 0xde, 0xfd, 0xa1, 0xcb, 0xc1, 0x60, 0x39, 0x02, 0x58, - 0x0e, 0xe2, 0xae, 0x7f, 0x7a, 0x77, 0x96, 0xe5, 0x57, 0x04, 0xd5, 0xf8, 0x88, 0x6f, 0xcc, 0x18, 0xd9, 0x8c, 0x08, - 0x59, 0x0c, 0xca, 0x84, 0xa8, 0x64, 0x5b, 0x28, 0x82, 0xa3, 0x41, 0x63, 0xa7, 0xa3, 0x11, 0x0d, 0x06, 0x21, 0xb6, - 0x8a, 0xd2, 0x93, 0x03, 0xaa, 0x4d, 0x44, 0x91, 0x2a, 0x01, 0x18, 0x22, 0x98, 0x61, 0x0e, 0x05, 0x48, 0x05, 0x3d, - 0x70, 0x72, 0xf9, 0xc6, 0x5a, 0xe2, 0x05, 0xa4, 0x73, 0x5a, 0xe4, 0x68, 0xb0, 0x95, 0x3a, 0x3c, 0xc1, 0xe4, 0x8e, - 0x40, 0xd6, 0x21, 0xfc, 0xd1, 0xc9, 0x01, 0x3d, 0x2a, 0xa5, 0x13, 0x91, 0x77, 0x22, 0x14, 0x94, 0x3d, 0xde, 0xc1, - 0x83, 0x8e, 0x4a, 0x9c, 0xb0, 0x15, 0x94, 0xba, 0xa9, 0xaa, 0x2c, 0x39, 0x07, 0xc5, 0xe3, 0xac, 0x41, 0x10, 0x16, - 0x1b, 0x8c, 0xdf, 0x55, 0x65, 0xe9, 0xde, 0xe1, 0xcc, 0xc5, 0x1b, 0xf7, 0x4e, 0x73, 0xf8, 0xab, 0xfc, 0xac, 0xc5, - 0xc5, 0xb3, 0x36, 0xe1, 0x8b, 0x0b, 0x1e, 0x56, 0x82, 0x73, 0xd6, 0x16, 0x68, 0xb9, 0x52, 0xb3, 0xb8, 0x0b, 0xb1, - 0xb8, 0xd3, 0x86, 0xc5, 0x9d, 0x6e, 0x59, 0x5c, 0x9f, 0x2f, 0xa4, 0x92, 0x81, 0x2e, 0x42, 0xaf, 0xd9, 0x0c, 0x78, - 0x9c, 0x1f, 0xe9, 0xf1, 0x73, 0x86, 0x70, 0x32, 0x63, 0x1f, 0xac, 0x46, 0x1b, 0x60, 0x55, 0x07, 0x17, 0x09, 0x10, - 0xd5, 0x89, 0x67, 0xa7, 0x6e, 0x22, 0x29, 0x04, 0x34, 0xbf, 0x3c, 0x5f, 0xd8, 0xa5, 0xd8, 0xd0, 0xd0, 0x16, 0x0d, - 0x33, 0x5d, 0x6c, 0x99, 0xe9, 0xa4, 0x70, 0x74, 0xf9, 0xb4, 0xe9, 0x10, 0xca, 0x93, 0x82, 0x3d, 0x08, 0x96, 0xf4, - 0xb8, 0x65, 0x8a, 0xfb, 0xb0, 0x19, 0xc7, 0x4a, 0x3b, 0x6a, 0xe5, 0xc6, 0xf1, 0x6d, 0x18, 0xc1, 0x55, 0x34, 0x74, - 0xf3, 0xb0, 0x2d, 0xb5, 0xf4, 0x02, 0x1e, 0xe5, 0xaa, 0x71, 0x33, 0xe5, 0xef, 0xe5, 0x2d, 0xd5, 0xea, 0x74, 0xa8, - 0xc6, 0xca, 0x4d, 0x12, 0x16, 0x21, 0xd0, 0x5d, 0x48, 0x87, 0xf0, 0xff, 0x64, 0x9b, 0xd5, 0xe0, 0x10, 0x5f, 0xc2, - 0xea, 0x88, 0xa1, 0x57, 0xc0, 0x82, 0xd1, 0xdd, 0x53, 0xa0, 0x6f, 0xa4, 0x88, 0x99, 0x51, 0x06, 0xf8, 0x1f, 0xf0, - 0xb8, 0x6a, 0x91, 0xe4, 0xd3, 0xe9, 0x1c, 0xe9, 0xd6, 0xca, 0x9d, 0xbe, 0x07, 0x8b, 0x07, 0xad, 0x65, 0x80, 0xf7, - 0x82, 0x1c, 0x1f, 0x33, 0x22, 0x9e, 0x70, 0x92, 0x23, 0x49, 0xc4, 0x92, 0xdc, 0x36, 0x14, 0xdc, 0xca, 0x5d, 0x73, - 0x76, 0xb5, 0x69, 0xa5, 0x07, 0x73, 0x4f, 0xaf, 0x60, 0x4d, 0x40, 0x6d, 0xfe, 0x60, 0x98, 0xe9, 0xda, 0x7c, 0xc3, - 0x39, 0xd2, 0xe1, 0x4a, 0xec, 0x12, 0x12, 0x5f, 0xdb, 0x42, 0x5a, 0x1e, 0x45, 0x40, 0xb5, 0x2e, 0xed, 0xab, 0xf4, - 0xe9, 0x1c, 0x7f, 0x39, 0x57, 0xe9, 0xd3, 0x31, 0xfe, 0x6a, 0x5d, 0x61, 0x4a, 0xcf, 0x1a, 0x35, 0x81, 0x34, 0x67, - 0x75, 0x58, 0xd8, 0x4f, 0x64, 0x98, 0xfb, 0x80, 0x6d, 0xc3, 0x17, 0xf8, 0xf1, 0x93, 0x4d, 0x0c, 0xae, 0xe8, 0xf2, - 0x1c, 0x02, 0x2b, 0xd2, 0xd3, 0xda, 0xf2, 0x79, 0x43, 0xf9, 0x58, 0xff, 0x83, 0x09, 0x3f, 0xee, 0x92, 0x30, 0xa7, - 0x29, 0x45, 0x25, 0xc7, 0xf5, 0xd8, 0x0b, 0xdc, 0xe8, 0xfe, 0x9a, 0xa4, 0x10, 0x4d, 0xd2, 0xf6, 0x3e, 0xca, 0xa5, - 0xff, 0xfb, 0xa2, 0x1d, 0x40, 0x22, 0xdd, 0x65, 0xdd, 0x73, 0x42, 0x3f, 0xf8, 0x7b, 0x24, 0xf1, 0x77, 0x05, 0x39, - 0x95, 0x2f, 0x48, 0xe1, 0x43, 0xd7, 0x4f, 0x36, 0x1a, 0xab, 0x76, 0x53, 0x9a, 0x6d, 0x89, 0x81, 0x84, 0xe5, 0x41, - 0x99, 0x77, 0x39, 0xf5, 0x7a, 0x78, 0xd1, 0x3f, 0x0e, 0xef, 0xcc, 0x27, 0x9b, 0xe4, 0x54, 0x5d, 0xba, 0xd1, 0x07, - 0x36, 0x35, 0x27, 0x5e, 0x34, 0xf1, 0x81, 0x79, 0x1c, 0xfb, 0x6e, 0xf0, 0x81, 0x3f, 0x9a, 0xe1, 0x3a, 0x41, 0xd3, - 0x9d, 0x9d, 0x22, 0xb2, 0x80, 0x09, 0xe9, 0x0f, 0x91, 0xab, 0xad, 0x81, 0x82, 0xf2, 0x2a, 0xd3, 0xbf, 0xe5, 0x8c, - 0x62, 0x5e, 0xcb, 0x00, 0xcb, 0x73, 0xb0, 0x26, 0x02, 0x57, 0x7e, 0x43, 0xc5, 0xf5, 0x52, 0x0d, 0x79, 0xaa, 0x74, - 0xe5, 0x96, 0xe5, 0xa2, 0xbd, 0xc6, 0x1e, 0xfe, 0xfb, 0xcf, 0x41, 0xc9, 0x43, 0x3e, 0x97, 0xf5, 0xf2, 0x69, 0x33, - 0x84, 0x52, 0x93, 0x5c, 0xc8, 0x1e, 0xf0, 0x71, 0xce, 0x60, 0x36, 0x7f, 0x5a, 0x6e, 0xec, 0xc6, 0xf1, 0x7a, 0xc9, - 0xa6, 0x74, 0xb5, 0x76, 0x9a, 0x0f, 0xaa, 0x28, 0x87, 0xc8, 0x03, 0xfb, 0x65, 0xdd, 0x3a, 0x3e, 0x7c, 0x05, 0xa6, - 0x5c, 0xc0, 0x50, 0x86, 0xb3, 0x99, 0x9a, 0xab, 0x02, 0x76, 0x34, 0x73, 0x0e, 0x7f, 0x59, 0x7f, 0xf3, 0xc6, 0xfe, - 0x26, 0x6b, 0x1c, 0x00, 0x63, 0x2c, 0xec, 0x52, 0x38, 0x5f, 0x2c, 0x8d, 0x57, 0xcc, 0x68, 0xe6, 0x06, 0xcd, 0xd3, - 0xb9, 0x2c, 0x6c, 0xf1, 0x15, 0x63, 0x53, 0x60, 0xb8, 0x8d, 0x4a, 0xe9, 0xb5, 0xcf, 0x6e, 0x58, 0x66, 0xf3, 0x52, - 0xfd, 0x58, 0x4d, 0x0b, 0x0c, 0xca, 0xc9, 0x6f, 0x32, 0x39, 0x57, 0x27, 0x4d, 0x69, 0x84, 0x73, 0xe0, 0x33, 0x97, - 0x8f, 0x58, 0xe9, 0x48, 0x8d, 0x0c, 0x55, 0x1a, 0x40, 0xe3, 0xc8, 0x4e, 0x1b, 0xca, 0x7b, 0x80, 0xa8, 0x1b, 0xc6, - 0x66, 0x38, 0x7a, 0x0f, 0x92, 0x18, 0x70, 0x38, 0xf9, 0x70, 0xf2, 0xb4, 0x5c, 0x6b, 0xd2, 0x04, 0xb1, 0x3a, 0x5d, - 0x9a, 0x4a, 0x4a, 0x1a, 0x61, 0x06, 0x8e, 0xfe, 0x10, 0x42, 0x5d, 0x55, 0xbb, 0x36, 0x4a, 0x71, 0xe6, 0x63, 0x4c, - 0xf1, 0x1d, 0xb0, 0x38, 0x6e, 0x04, 0x58, 0xb6, 0xe8, 0x86, 0x9a, 0xd7, 0x2e, 0xc2, 0x23, 0x2f, 0x37, 0x6c, 0x03, - 0x58, 0x02, 0x9c, 0x60, 0xf9, 0x5b, 0x48, 0x5e, 0xae, 0x97, 0xdc, 0x90, 0x2f, 0x9a, 0x8f, 0x55, 0x6e, 0x64, 0xd5, - 0xf4, 0xfe, 0x56, 0xe5, 0x83, 0x2a, 0x90, 0xe9, 0xda, 0xa1, 0x69, 0x05, 0xd4, 0x5b, 0xd1, 0x2a, 0x61, 0x07, 0x62, - 0x4c, 0x25, 0xfc, 0xca, 0x66, 0x33, 0x36, 0x49, 0x62, 0x5d, 0xe8, 0x98, 0xb2, 0xb0, 0xda, 0x70, 0x7b, 0xf7, 0x68, - 0xa0, 0xfe, 0x00, 0xc1, 0x45, 0x44, 0xf4, 0x39, 0x3e, 0x20, 0x21, 0x33, 0xd5, 0x83, 0x89, 0x7a, 0x2c, 0x82, 0x88, - 0x7f, 0x05, 0xd4, 0xcc, 0x35, 0xe5, 0x38, 0x34, 0x4e, 0x7f, 0xf2, 0x7d, 0x11, 0x66, 0xe6, 0x7e, 0xdb, 0x51, 0xd1, - 0xb6, 0xe3, 0xbb, 0x71, 0xbe, 0xe9, 0x38, 0x76, 0xaa, 0x1a, 0xe0, 0xd4, 0xfa, 0xa1, 0xb4, 0x8d, 0x89, 0x40, 0x0d, - 0xd4, 0xf3, 0xb7, 0xaf, 0xfe, 0xf6, 0xe6, 0xf5, 0xbe, 0x18, 0x01, 0xbb, 0x6c, 0x43, 0x97, 0xeb, 0x60, 0x4b, 0xa7, - 0x3f, 0xfd, 0xf0, 0xb0, 0x6e, 0x5b, 0xce, 0x0b, 0x47, 0x35, 0xc8, 0x0e, 0x59, 0xc2, 0x8b, 0x93, 0xf0, 0x86, 0x45, - 0x9f, 0x0c, 0x06, 0xb9, 0xf3, 0xfa, 0xe1, 0xbe, 0xfd, 0xf1, 0xcd, 0x0f, 0x7b, 0x0f, 0xf5, 0xc8, 0xb1, 0x01, 0xb7, - 0x27, 0xe1, 0xea, 0x01, 0xb3, 0x6b, 0xab, 0x86, 0x3a, 0xf1, 0xc3, 0x98, 0x35, 0x8c, 0xe0, 0xd5, 0xf9, 0xdb, 0xf7, - 0x08, 0xae, 0x9c, 0x05, 0xa1, 0xae, 0x3e, 0x6d, 0xf2, 0x3f, 0xbe, 0x7b, 0xf3, 0xfe, 0xbd, 0x6a, 0x60, 0x5a, 0xe6, - 0x58, 0xee, 0x9d, 0x6f, 0xe2, 0x1d, 0x14, 0xa7, 0x76, 0xaf, 0x13, 0x55, 0x23, 0x41, 0xba, 0x38, 0x1b, 0x2a, 0xab, - 0x6c, 0x73, 0x4e, 0xed, 0xf8, 0x97, 0x49, 0xfa, 0xdd, 0x6b, 0x5e, 0x35, 0xf8, 0x68, 0x3b, 0x49, 0x2d, 0x94, 0x2c, - 0xbd, 0xe0, 0xba, 0xa6, 0xd4, 0xbd, 0xab, 0x29, 0x05, 0xf1, 0xb1, 0x82, 0x1f, 0xd7, 0xe1, 0x52, 0x62, 0x47, 0xd8, - 0xdd, 0x6e, 0x70, 0x49, 0x32, 0xdc, 0x27, 0x0c, 0x9a, 0xa7, 0xd5, 0x28, 0x8f, 0xba, 0xa6, 0x98, 0x0b, 0x5e, 0x19, - 0x6c, 0x27, 0x3e, 0x58, 0x5f, 0x33, 0xf9, 0x9e, 0xb1, 0xc8, 0xaa, 0x72, 0xdf, 0x89, 0x41, 0x49, 0x2a, 0xa0, 0x66, - 0x74, 0x37, 0xc3, 0x69, 0xca, 0xca, 0x9d, 0x82, 0x49, 0xb3, 0x39, 0x0e, 0x93, 0x24, 0x5c, 0xf6, 0x1c, 0x7b, 0x75, - 0xa7, 0x2a, 0x7d, 0xa1, 0xec, 0xe0, 0x16, 0xd7, 0xbd, 0xdf, 0xfe, 0x53, 0x42, 0xf3, 0x54, 0x7e, 0x9d, 0xb0, 0xe5, - 0x8a, 0x45, 0x6e, 0xb2, 0x8e, 0x58, 0xaa, 0xfc, 0xf6, 0xbf, 0xaf, 0x4a, 0x82, 0x7d, 0x5f, 0x6e, 0x43, 0x2c, 0xbd, - 0xdc, 0xe4, 0xda, 0x0f, 0x6f, 0x1f, 0xe5, 0xbe, 0x55, 0x3b, 0x2a, 0x2f, 0xbc, 0xf9, 0x22, 0xab, 0x7d, 0x9a, 0x6c, - 0x99, 0x9b, 0x18, 0x3d, 0xdd, 0x07, 0x28, 0xe7, 0xe1, 0x6d, 0xef, 0xb7, 0xff, 0x64, 0x0a, 0x9b, 0x9d, 0xbb, 0xae, - 0x7e, 0xa0, 0xc5, 0x15, 0xad, 0xaf, 0x53, 0x59, 0x62, 0x78, 0x5f, 0x59, 0xe0, 0x4a, 0x21, 0xed, 0xca, 0xea, 0xe5, - 0xdb, 0x96, 0x39, 0x7d, 0xeb, 0xcd, 0x17, 0x9f, 0x3a, 0x29, 0x00, 0xe8, 0xce, 0x59, 0x41, 0xa5, 0xcf, 0x30, 0xad, - 0x51, 0x6f, 0xff, 0x05, 0xfb, 0xc4, 0x79, 0xed, 0x9a, 0xd2, 0xe7, 0x98, 0x0d, 0xd7, 0xdc, 0xbe, 0x1a, 0x8d, 0xb2, - 0xb4, 0xa4, 0x72, 0x7b, 0xf0, 0x0e, 0x3b, 0xad, 0x94, 0x70, 0xf6, 0xa2, 0x67, 0xeb, 0x14, 0xb6, 0x65, 0x0f, 0x80, - 0xa0, 0x9d, 0x73, 0x0d, 0x38, 0x9a, 0xf1, 0x35, 0xb9, 0x2b, 0x55, 0xbe, 0x5d, 0x41, 0xd6, 0x50, 0x8a, 0x29, 0x2d, - 0xb3, 0x5b, 0x43, 0xa3, 0x7e, 0x38, 0xb7, 0x91, 0xbb, 0xa2, 0x4b, 0x02, 0x05, 0x6f, 0x4c, 0x40, 0xe9, 0x52, 0x92, - 0xa2, 0x6f, 0x5c, 0xff, 0x66, 0x3f, 0x81, 0xaa, 0x99, 0x82, 0x21, 0x69, 0xfe, 0xf3, 0x88, 0x37, 0xd2, 0xe5, 0xfd, - 0x69, 0x37, 0xa6, 0x0a, 0x7b, 0xdb, 0x64, 0x5e, 0xfd, 0xe3, 0x6e, 0xf3, 0xea, 0x8b, 0xbd, 0xcc, 0xab, 0x7f, 0xfc, - 0xec, 0xe6, 0xd5, 0x6f, 0x65, 0xf3, 0x6a, 0xd8, 0xc4, 0x6f, 0xd8, 0x5e, 0x46, 0xcf, 0xc2, 0xc8, 0x28, 0xbc, 0x8d, - 0x07, 0x0e, 0x17, 0x7a, 0xe2, 0xc9, 0x82, 0x81, 0x16, 0x89, 0x83, 0xcb, 0x0f, 0xe7, 0x60, 0x9b, 0xdc, 0x6c, 0x7d, - 0xfc, 0xb9, 0x6c, 0x8f, 0xfd, 0x70, 0xae, 0x4a, 0xc1, 0xd2, 0x03, 0x11, 0x2c, 0x1d, 0x1c, 0xc4, 0x7f, 0xb9, 0x73, - 0x5e, 0x5e, 0x3a, 0xfd, 0xb6, 0x03, 0xc1, 0x46, 0x40, 0x31, 0x80, 0x05, 0x76, 0xbf, 0xdd, 0x86, 0x82, 0x5b, 0xa9, - 0xa0, 0x05, 0x05, 0x9e, 0x54, 0xd0, 0x81, 0x82, 0x89, 0x54, 0x70, 0x04, 0x05, 0x53, 0xa9, 0xe0, 0x18, 0x0a, 0x6e, - 0xd4, 0xf4, 0x32, 0xc8, 0x8c, 0xc7, 0x8f, 0xf5, 0xab, 0x42, 0x9e, 0x8c, 0x3c, 0xff, 0x3c, 0xaf, 0x72, 0x6c, 0x88, - 0xa0, 0x8d, 0xe6, 0xa1, 0xce, 0xcd, 0xff, 0x46, 0x5f, 0x8c, 0xc0, 0x9d, 0x1a, 0x94, 0x7a, 0x06, 0xa8, 0x44, 0xa9, - 0x66, 0x5b, 0xbc, 0x56, 0x7b, 0xaa, 0x9e, 0x7d, 0xa0, 0x25, 0xec, 0xfe, 0x7a, 0xe8, 0x4a, 0x23, 0x2a, 0x77, 0x9e, - 0x2f, 0xb2, 0x08, 0x4e, 0xeb, 0x41, 0xee, 0x91, 0xd6, 0x86, 0x38, 0xb6, 0x70, 0x35, 0xfd, 0x1a, 0xf9, 0x03, 0x2b, - 0x09, 0xc1, 0xe1, 0x48, 0x44, 0x2e, 0x12, 0x1f, 0x50, 0x54, 0xfd, 0xd2, 0xbe, 0xea, 0xbb, 0x79, 0x90, 0x29, 0x1e, - 0xef, 0x8c, 0x46, 0xbf, 0xcc, 0xc2, 0x48, 0x91, 0x98, 0xbb, 0x36, 0x12, 0x77, 0xde, 0x5b, 0x18, 0xa4, 0xe3, 0xee, - 0xcd, 0x21, 0x2e, 0xe8, 0xe9, 0xb4, 0xb7, 0x32, 0x6e, 0x17, 0x2c, 0xe8, 0xcd, 0xb8, 0x39, 0x28, 0xac, 0x3f, 0x59, - 0xf1, 0x2c, 0x75, 0x61, 0x94, 0x86, 0x7b, 0x22, 0x7f, 0x4b, 0xa3, 0x34, 0xb3, 0xad, 0x94, 0x5b, 0x4e, 0x69, 0xb2, - 0xfe, 0xfb, 0x73, 0xd8, 0xb9, 0xbc, 0x66, 0xe3, 0xf5, 0x5c, 0x39, 0x0f, 0xe7, 0x3b, 0x6d, 0x5a, 0xe4, 0x57, 0x30, - 0x4a, 0x95, 0x2e, 0xfa, 0x4c, 0xb1, 0xbd, 0xf9, 0xb7, 0xe8, 0x31, 0x2d, 0xd6, 0x4f, 0x60, 0x6c, 0x4a, 0x42, 0x28, - 0x1b, 0xbe, 0x03, 0xd0, 0x96, 0x8c, 0x4a, 0xce, 0x01, 0x7e, 0xd2, 0xf3, 0x85, 0x2b, 0x8d, 0x67, 0xf8, 0x3d, 0x8b, - 0x63, 0x77, 0x2e, 0xea, 0x57, 0xc7, 0x09, 0x3e, 0x36, 0x99, 0xa4, 0x8f, 0x00, 0x04, 0x9d, 0xb1, 0x57, 0xb1, 0x05, - 0x02, 0x53, 0x66, 0x30, 0x7b, 0x83, 0x45, 0xcb, 0x0d, 0x67, 0x3c, 0x0b, 0x96, 0xa7, 0x68, 0xe2, 0x02, 0x48, 0xe4, - 0x86, 0xf9, 0xe5, 0xc2, 0xc4, 0x9d, 0x97, 0x8b, 0x68, 0xad, 0x53, 0x79, 0x6c, 0x99, 0x85, 0x49, 0xa1, 0xf0, 0x53, - 0x4c, 0x26, 0xfc, 0x70, 0xfe, 0xbb, 0xda, 0x4b, 0x6c, 0xb1, 0x73, 0x79, 0x1f, 0x18, 0x41, 0x32, 0xb2, 0x10, 0xc6, - 0x8a, 0x05, 0x20, 0xec, 0x05, 0xc9, 0xc2, 0x44, 0xef, 0x6e, 0xad, 0x15, 0xe8, 0x86, 0x85, 0x6b, 0xbb, 0x29, 0xc7, - 0xb4, 0xe8, 0x45, 0xf3, 0xb1, 0xab, 0x39, 0xad, 0x63, 0x43, 0xfc, 0xb1, 0xec, 0x8e, 0x9e, 0x62, 0x0f, 0xca, 0xd4, - 0xbb, 0xd9, 0xcc, 0xc2, 0x20, 0x31, 0x67, 0xee, 0xd2, 0xf3, 0xef, 0x7b, 0xcb, 0x30, 0x08, 0xe3, 0x95, 0x3b, 0x61, - 0xfd, 0x5c, 0x75, 0xd3, 0xc7, 0x68, 0x49, 0xdc, 0x61, 0xdf, 0xb1, 0x5a, 0x11, 0x5b, 0x52, 0xeb, 0x2c, 0x18, 0xd2, - 0xcc, 0x67, 0x77, 0x29, 0xff, 0x7c, 0xa1, 0x32, 0x55, 0xc5, 0x2d, 0x47, 0x2d, 0x40, 0x0e, 0xe1, 0x91, 0x96, 0x20, - 0xbe, 0x60, 0x9f, 0x33, 0xf3, 0x3d, 0xab, 0xd5, 0x89, 0xd8, 0x52, 0xb1, 0x3a, 0x8d, 0x9d, 0x47, 0xe1, 0xed, 0x10, - 0x46, 0x8b, 0x8d, 0xcd, 0x98, 0xf9, 0x33, 0x7c, 0x63, 0xa2, 0x73, 0xa7, 0xe8, 0xc7, 0x44, 0x95, 0x0f, 0xf4, 0xc6, - 0x96, 0x7d, 0x78, 0xdd, 0x6b, 0x29, 0x76, 0x7f, 0xe9, 0x05, 0x26, 0x4d, 0xe7, 0xd8, 0x5e, 0x49, 0x7d, 0xc9, 0xf0, - 0xd3, 0x37, 0x58, 0xdd, 0x51, 0xec, 0x3e, 0x88, 0xf6, 0x33, 0x3f, 0xbc, 0xed, 0x2d, 0xbc, 0xe9, 0x94, 0x05, 0x7d, - 0x1c, 0x73, 0x56, 0xc8, 0x7c, 0xdf, 0x5b, 0xc5, 0x5e, 0xdc, 0x5f, 0xba, 0x77, 0xbc, 0xd7, 0xc3, 0xa6, 0x5e, 0xdb, - 0xbc, 0xd7, 0xf6, 0xde, 0xbd, 0x4a, 0xdd, 0x80, 0x23, 0x29, 0xf5, 0xc3, 0x87, 0xd6, 0x51, 0xec, 0xd2, 0x3c, 0xf7, - 0xee, 0x75, 0x15, 0xb1, 0xcd, 0xd2, 0x8d, 0xe6, 0x5e, 0xd0, 0xb3, 0x53, 0xeb, 0x66, 0x43, 0x1b, 0xe3, 0x71, 0xb7, - 0xdb, 0x4d, 0xad, 0xa9, 0x78, 0xb2, 0xa7, 0xd3, 0xd4, 0x9a, 0x88, 0xa7, 0xd9, 0xcc, 0xb6, 0x67, 0xb3, 0xd4, 0xf2, - 0x44, 0x41, 0xbb, 0x35, 0x99, 0xb6, 0x5b, 0xa9, 0x75, 0x2b, 0xd5, 0x48, 0x2d, 0xc6, 0x9f, 0x22, 0x36, 0xed, 0xe3, - 0x46, 0xe2, 0x76, 0xe9, 0xc7, 0xb6, 0x9d, 0x22, 0x06, 0xb8, 0x2c, 0xe0, 0x26, 0xd4, 0x2a, 0x5e, 0x6d, 0xf6, 0xae, - 0xa9, 0xe4, 0x9f, 0x9b, 0x4c, 0x6a, 0xeb, 0x4d, 0xdd, 0xe8, 0xc3, 0x95, 0x22, 0xcd, 0xc2, 0x75, 0xa9, 0xda, 0x46, - 0x80, 0xc1, 0xbc, 0xeb, 0x41, 0xd4, 0xcc, 0xfe, 0x38, 0x8c, 0xe0, 0xcc, 0x46, 0xee, 0xd4, 0x5b, 0xc7, 0x3d, 0xa7, - 0xb5, 0xba, 0x13, 0x45, 0x7c, 0xaf, 0xe7, 0x05, 0x78, 0xf6, 0x7a, 0x71, 0xe8, 0x7b, 0x53, 0x51, 0xd4, 0x74, 0x96, - 0x9c, 0x96, 0xde, 0xc7, 0x98, 0x31, 0x1e, 0x46, 0x3e, 0x72, 0x7d, 0x5f, 0xb1, 0xda, 0xb1, 0xc2, 0xdc, 0x18, 0x6f, - 0x32, 0x14, 0x3b, 0x26, 0xb8, 0x60, 0x7c, 0x18, 0xe7, 0x70, 0x75, 0x97, 0xed, 0x79, 0xe7, 0x68, 0x75, 0x97, 0x7e, - 0xb5, 0x64, 0x53, 0xcf, 0x55, 0xb4, 0x7c, 0x37, 0x39, 0x36, 0xdc, 0x76, 0xe8, 0x9b, 0x86, 0x6d, 0x2a, 0x8e, 0x05, - 0x44, 0x17, 0x7e, 0xe4, 0x2d, 0x57, 0x61, 0x94, 0xb8, 0x41, 0x92, 0xa6, 0xa3, 0xab, 0x34, 0xed, 0x5f, 0x78, 0xda, - 0xe5, 0x3f, 0x34, 0xa2, 0x85, 0x74, 0x3b, 0x98, 0xea, 0x57, 0xc6, 0x1b, 0x26, 0x5b, 0x32, 0x01, 0x19, 0x43, 0x2b, - 0x26, 0xb9, 0x32, 0xd1, 0xdb, 0x6a, 0x65, 0x02, 0x72, 0x56, 0x9d, 0x0c, 0xa3, 0x8a, 0x55, 0x90, 0x02, 0x41, 0x85, - 0x37, 0x6c, 0x70, 0x21, 0x99, 0x45, 0x01, 0xd3, 0x83, 0x95, 0xc9, 0xb5, 0xef, 0x49, 0x13, 0xef, 0xf9, 0xf5, 0x6e, - 0xde, 0xf3, 0x9f, 0xc9, 0x3e, 0xbc, 0xe7, 0xd7, 0x9f, 0x9d, 0xf7, 0x7c, 0x52, 0x75, 0xed, 0x3b, 0x0b, 0x07, 0x6a, - 0x76, 0x97, 0x05, 0xa4, 0x29, 0xa2, 0xa0, 0x79, 0x67, 0xc9, 0x7f, 0xeb, 0x8a, 0x27, 0x7a, 0xa3, 0x34, 0xb0, 0x44, - 0xb9, 0x81, 0x81, 0x7f, 0x1b, 0x0c, 0xfe, 0x1e, 0xc9, 0xcf, 0xb3, 0xd9, 0xe0, 0x75, 0x28, 0x15, 0x64, 0x4f, 0xdc, - 0xcc, 0xa7, 0x10, 0xe0, 0x88, 0xde, 0x64, 0x86, 0x58, 0x90, 0x02, 0x0a, 0xe2, 0xa3, 0x90, 0xb1, 0xfd, 0x34, 0x33, - 0x87, 0xec, 0x17, 0x87, 0xa0, 0x65, 0x06, 0xc6, 0xc2, 0x0b, 0xb6, 0xa2, 0xb4, 0x9e, 0xb3, 0x84, 0x87, 0xad, 0x78, - 0x79, 0x7f, 0x36, 0xd5, 0xce, 0x42, 0x3d, 0xf5, 0xe2, 0xb7, 0x65, 0x1f, 0x54, 0x21, 0x82, 0xc8, 0xd3, 0x49, 0xb9, - 0x49, 0xa3, 0x14, 0x6a, 0x06, 0x5f, 0x53, 0xf3, 0xd3, 0xc2, 0x4c, 0x7b, 0x72, 0x43, 0x9e, 0x6b, 0xb2, 0x42, 0x8c, - 0xb9, 0x4b, 0xdf, 0x86, 0x73, 0x79, 0x98, 0x3e, 0x13, 0x43, 0x77, 0x4c, 0xa9, 0xb9, 0x37, 0x4d, 0x53, 0xbd, 0x2f, - 0x00, 0x21, 0x11, 0x5a, 0xb6, 0x8b, 0x89, 0x8b, 0x73, 0x81, 0x96, 0xdf, 0x45, 0xd3, 0x45, 0xf3, 0x19, 0x98, 0x6e, - 0xf0, 0x6b, 0x69, 0x0e, 0x33, 0x55, 0x21, 0xf0, 0x91, 0x49, 0x8f, 0x34, 0x21, 0xb0, 0x35, 0x90, 0x0d, 0xe1, 0x0a, - 0x0b, 0x52, 0x35, 0x2a, 0x26, 0xe0, 0xa0, 0xed, 0x09, 0x04, 0xda, 0x11, 0xda, 0x2e, 0x42, 0x3b, 0xbc, 0x0e, 0x3e, - 0xa4, 0x6a, 0xc6, 0xfb, 0xe1, 0xf6, 0x1b, 0x9e, 0x1c, 0x40, 0x83, 0x61, 0x49, 0x93, 0xb5, 0xc3, 0x64, 0x16, 0x58, - 0x89, 0xf8, 0xd6, 0xb0, 0xe2, 0x5b, 0xe5, 0xd9, 0x46, 0x04, 0xa9, 0x4a, 0xdc, 0x95, 0x09, 0xea, 0x13, 0xc4, 0xbd, - 0x1c, 0xe3, 0x49, 0xf1, 0xb0, 0xfa, 0xeb, 0x18, 0x70, 0x23, 0x4a, 0xf2, 0x88, 0x7f, 0xfa, 0x93, 0x75, 0x14, 0x87, - 0x51, 0x6f, 0x15, 0x7a, 0x41, 0xc2, 0xa2, 0x14, 0x41, 0x75, 0x89, 0xf0, 0x11, 0xe0, 0xb9, 0xda, 0x84, 0x2b, 0x77, - 0xe2, 0x25, 0xf7, 0x3d, 0x9b, 0xb3, 0x14, 0x76, 0x9f, 0x73, 0x07, 0x76, 0x6d, 0xfd, 0x1e, 0x87, 0xe6, 0x73, 0x64, - 0xfc, 0xa2, 0x2a, 0x3b, 0x23, 0x6f, 0xf3, 0xbe, 0xf4, 0x96, 0xc2, 0x74, 0x01, 0xfb, 0xe1, 0x46, 0xe6, 0x1c, 0xb0, - 0x3c, 0x2c, 0xb5, 0x3d, 0x65, 0x73, 0x03, 0xb1, 0x36, 0xdc, 0x00, 0x89, 0x3f, 0x56, 0x47, 0x57, 0xec, 0xfa, 0x62, - 0xe0, 0x78, 0xf4, 0x7d, 0x46, 0xd6, 0x73, 0x21, 0xa9, 0xa5, 0xb1, 0x4f, 0xcd, 0x31, 0x9b, 0x85, 0x11, 0xa3, 0x90, - 0xee, 0x4e, 0x77, 0x75, 0xb7, 0x7f, 0xf7, 0xdb, 0xa7, 0x5f, 0xdf, 0x4f, 0x10, 0x26, 0x9a, 0xe8, 0x4c, 0xdf, 0xd1, - 0x5b, 0x95, 0x9e, 0x01, 0x6b, 0x48, 0x90, 0x9f, 0x90, 0xc3, 0x49, 0x4f, 0x55, 0xfb, 0xb5, 0x91, 0x33, 0x57, 0x21, - 0xa7, 0x79, 0x11, 0xf3, 0xdd, 0xc4, 0xbb, 0x11, 0x3c, 0x63, 0xfb, 0x68, 0x75, 0x27, 0xd6, 0x18, 0x09, 0xde, 0x03, - 0x16, 0xa9, 0x34, 0x14, 0xb1, 0x48, 0xe5, 0x62, 0x5c, 0xa4, 0x7e, 0x65, 0x36, 0x22, 0x98, 0x54, 0x89, 0xd2, 0x77, - 0x56, 0x77, 0x32, 0x89, 0xce, 0x9b, 0x65, 0x94, 0xba, 0x1c, 0x05, 0x74, 0xe9, 0x4d, 0xa7, 0x3e, 0x4b, 0x0b, 0x0b, - 0x5d, 0x5c, 0x4b, 0x09, 0x38, 0x19, 0x1c, 0xdc, 0x71, 0x1c, 0xfa, 0xeb, 0x84, 0xd5, 0x83, 0x8b, 0x80, 0xd3, 0xb2, - 0x73, 0xe0, 0xe0, 0xef, 0xe2, 0x58, 0x3b, 0xc0, 0x6e, 0xc3, 0x36, 0xb1, 0xfb, 0x10, 0xf4, 0xdf, 0x6c, 0x17, 0x87, - 0x0e, 0xaf, 0xb2, 0x41, 0x1b, 0x35, 0x13, 0x31, 0x80, 0x2c, 0x11, 0xf6, 0x56, 0x2c, 0x87, 0x97, 0x65, 0x81, 0xcf, - 0xb3, 0xa2, 0xb4, 0x38, 0x99, 0xdf, 0xe7, 0x8c, 0xbd, 0xa8, 0x3f, 0x63, 0x2f, 0xc4, 0x19, 0xdb, 0xbe, 0x33, 0x1f, - 0xcf, 0x1c, 0xf8, 0xaf, 0x9f, 0x4f, 0xa8, 0x67, 0x2b, 0xed, 0xd5, 0x9d, 0xe2, 0xac, 0xee, 0x14, 0xb3, 0xb5, 0xba, - 0x53, 0xb0, 0x6b, 0xb4, 0x3c, 0x32, 0xac, 0x96, 0x6e, 0xd8, 0x0a, 0x14, 0xc2, 0x1f, 0xbb, 0xf0, 0xca, 0x39, 0x84, - 0x77, 0xd0, 0xaa, 0x53, 0x7d, 0xd7, 0xda, 0x7e, 0xd4, 0xe9, 0x2c, 0x09, 0xa4, 0xad, 0x5b, 0x89, 0x3b, 0x1e, 0xb3, - 0x69, 0x6f, 0x16, 0x4e, 0xd6, 0xf1, 0xbf, 0xf9, 0xf8, 0x39, 0x10, 0xb7, 0x22, 0x82, 0x52, 0x3f, 0xa2, 0x29, 0x68, - 0xf7, 0x6e, 0x98, 0xe8, 0x61, 0x93, 0xad, 0x53, 0x8f, 0x32, 0x14, 0xb4, 0xac, 0xc3, 0x9a, 0x4d, 0x5e, 0x0f, 0xe8, - 0xdf, 0x6d, 0x95, 0x9a, 0x51, 0xcc, 0x27, 0x80, 0x65, 0x2b, 0x38, 0x1e, 0x0e, 0x0d, 0xbe, 0x9a, 0x76, 0xb7, 0x7e, - 0xb8, 0x97, 0xe2, 0x4b, 0x57, 0x82, 0xa8, 0x70, 0xba, 0xc5, 0xdd, 0xa0, 0xb6, 0xf7, 0xda, 0xb4, 0x47, 0x2a, 0xbd, - 0x6e, 0x21, 0x08, 0x79, 0xdd, 0x3d, 0xb1, 0xfc, 0xe3, 0x17, 0x87, 0xf0, 0x1f, 0x71, 0xf5, 0xff, 0x4c, 0xea, 0x18, - 0xf5, 0xb3, 0xa4, 0xc0, 0xa8, 0x13, 0xab, 0x84, 0x8c, 0xf8, 0xfe, 0xf5, 0x67, 0xb3, 0x87, 0x35, 0xd8, 0xbb, 0x36, - 0x19, 0xed, 0x95, 0x6b, 0xbf, 0x0c, 0x43, 0xc8, 0x9e, 0x5d, 0xad, 0x2e, 0xc0, 0x43, 0x1e, 0x18, 0xc9, 0x00, 0x1a, - 0x09, 0x39, 0x82, 0xec, 0x45, 0x54, 0x6c, 0x43, 0xa2, 0xc4, 0x9b, 0x26, 0x51, 0xe2, 0xf5, 0x6e, 0x51, 0xe2, 0xbb, - 0xbd, 0x44, 0x89, 0xd7, 0x9f, 0x5d, 0x94, 0x78, 0x53, 0x15, 0x25, 0x2e, 0x42, 0x61, 0xa9, 0x6d, 0x9c, 0xad, 0xf9, - 0xcf, 0x9f, 0xe9, 0x2a, 0xf6, 0x3c, 0x1c, 0x74, 0x6c, 0xca, 0x3a, 0x70, 0xf1, 0x5f, 0x0b, 0x16, 0xb8, 0x11, 0xdf, - 0xa1, 0xe1, 0x62, 0x2e, 0x5a, 0x70, 0xcc, 0x8e, 0xdf, 0x91, 0x8a, 0xfd, 0x30, 0x98, 0xff, 0x08, 0x57, 0xf1, 0xa0, - 0x0e, 0x8c, 0xa4, 0x17, 0x5e, 0xfc, 0x63, 0xb8, 0x5a, 0xaf, 0xce, 0xa0, 0xaf, 0x9f, 0xbd, 0xd8, 0x1b, 0xfb, 0x2c, - 0x8b, 0x06, 0x42, 0x86, 0x96, 0x5c, 0xb7, 0x0e, 0xb6, 0xcd, 0xe2, 0xa7, 0x7b, 0x27, 0x7e, 0xa2, 0xf5, 0x33, 0xff, - 0x4d, 0x16, 0x9c, 0x6a, 0xbd, 0x20, 0x02, 0x61, 0xf3, 0x4a, 0x83, 0x7e, 0xb8, 0x30, 0x72, 0x11, 0xea, 0x35, 0xb3, - 0x14, 0x96, 0x35, 0x8d, 0xfd, 0xb0, 0x8a, 0x50, 0xb3, 0xd6, 0x8d, 0x2c, 0x0a, 0x66, 0x55, 0x9d, 0xbf, 0x0c, 0xd7, - 0x31, 0x9b, 0x86, 0xb7, 0x81, 0x6a, 0x04, 0xdc, 0x1c, 0x94, 0x12, 0x09, 0x66, 0x6d, 0x30, 0x7f, 0xf3, 0x7b, 0x64, - 0x94, 0x21, 0x62, 0x02, 0xa4, 0x0f, 0x5f, 0xaf, 0x4c, 0x32, 0x30, 0x30, 0x71, 0x8a, 0x6a, 0x96, 0x68, 0xf0, 0x91, - 0xa6, 0x85, 0x83, 0x87, 0xb5, 0x14, 0x46, 0x41, 0xa1, 0xc5, 0xb5, 0xc2, 0xb1, 0x16, 0x08, 0xe5, 0xa2, 0x08, 0x45, - 0x55, 0xb3, 0x70, 0xfc, 0x0d, 0x85, 0xfa, 0xc8, 0xdf, 0x42, 0x64, 0x88, 0x74, 0xcd, 0xd7, 0x83, 0x07, 0x66, 0xa2, - 0xc7, 0x57, 0x12, 0x18, 0xdf, 0xde, 0xb0, 0xc8, 0x77, 0xef, 0x35, 0x3d, 0x0d, 0x83, 0xef, 0x01, 0x00, 0xaf, 0xc3, - 0xdb, 0x40, 0xae, 0x80, 0xf9, 0xd2, 0x6a, 0xf6, 0x52, 0x6d, 0x08, 0x31, 0x70, 0xa7, 0x92, 0x46, 0x00, 0x99, 0xea, - 0xe7, 0xec, 0xef, 0x06, 0xfd, 0xfb, 0x0f, 0x3d, 0x35, 0xce, 0xc3, 0xec, 0x43, 0x3f, 0xad, 0xf6, 0xf8, 0xcc, 0xd3, - 0xa7, 0x8f, 0x9a, 0xa7, 0xad, 0x4d, 0x7c, 0xe6, 0x8a, 0xfc, 0xf3, 0x5a, 0x4d, 0x6b, 0xbd, 0xf1, 0x14, 0xc0, 0x28, - 0x2e, 0xc2, 0xf5, 0x64, 0x81, 0x26, 0xd5, 0x9f, 0x6f, 0xbe, 0x09, 0xf4, 0x89, 0x89, 0xc2, 0xb3, 0xa9, 0x97, 0x8a, - 0x72, 0x28, 0xe0, 0xf7, 0xdf, 0x40, 0x0c, 0xec, 0x3f, 0x11, 0x0c, 0xd5, 0x5d, 0x93, 0xb9, 0x5b, 0x3f, 0x68, 0xf3, - 0xf6, 0x21, 0x9f, 0x35, 0x8f, 0x2e, 0x25, 0x2e, 0xe9, 0xea, 0x91, 0x4c, 0x5a, 0x06, 0x9a, 0x1c, 0xc9, 0xb5, 0x29, - 0x48, 0xad, 0xf8, 0x0a, 0xb3, 0x48, 0x4c, 0xe7, 0x2e, 0x2d, 0x06, 0xe3, 0xd8, 0xaa, 0x84, 0x64, 0xb8, 0xa1, 0x0b, - 0x43, 0xf4, 0x55, 0x7e, 0xb7, 0xf4, 0x02, 0x03, 0x13, 0xb1, 0x54, 0xdf, 0xb8, 0x77, 0x90, 0x8a, 0x00, 0x90, 0x5b, - 0xf9, 0x15, 0x14, 0x1a, 0xb2, 0x23, 0x27, 0x64, 0x5b, 0x54, 0x6b, 0x21, 0x21, 0x6e, 0x03, 0x47, 0x5f, 0x28, 0x8a, - 0xa2, 0x64, 0x62, 0x84, 0x92, 0xc9, 0x11, 0x58, 0x8e, 0xe2, 0x00, 0xdc, 0x96, 0xa4, 0xab, 0x3b, 0x2a, 0x01, 0xc9, - 0x00, 0xaf, 0xb6, 0x45, 0x01, 0x8f, 0xb6, 0xdb, 0xb1, 0x45, 0x81, 0x10, 0xe8, 0x21, 0x52, 0xaa, 0x1b, 0x41, 0x50, - 0xfe, 0x9e, 0x82, 0x02, 0x3b, 0xbe, 0xe5, 0x9a, 0x60, 0xc5, 0xa6, 0xc7, 0x51, 0x9f, 0xd5, 0x87, 0x65, 0x0d, 0x24, - 0x2c, 0x08, 0xb7, 0x0e, 0xa5, 0x2c, 0x0b, 0x06, 0xab, 0xc1, 0x8d, 0x28, 0x17, 0xdd, 0x25, 0x4b, 0x16, 0xac, 0x55, - 0x4c, 0xcb, 0x88, 0x61, 0x72, 0xa1, 0xce, 0x6b, 0x62, 0xb6, 0x00, 0xdb, 0xd4, 0xb7, 0x5c, 0x10, 0x2d, 0x8c, 0x39, - 0x4a, 0x75, 0x8d, 0x09, 0xf7, 0x4d, 0x8c, 0x39, 0x6e, 0x2b, 0x53, 0x08, 0xbe, 0xa4, 0x61, 0x11, 0x9b, 0x73, 0x6f, - 0x64, 0xe4, 0x14, 0x28, 0x42, 0x15, 0x57, 0x17, 0x09, 0xb0, 0x6b, 0x6e, 0x79, 0xd1, 0xb2, 0x1b, 0x19, 0xb7, 0xa4, - 0x28, 0x8a, 0xf4, 0x6a, 0x37, 0x7c, 0x9c, 0x10, 0x1b, 0xb0, 0xb1, 0x9f, 0x49, 0xa5, 0x9f, 0x86, 0x49, 0x7f, 0x60, - 0xf7, 0x44, 0x48, 0x08, 0x54, 0x1f, 0xd8, 0x3d, 0xdc, 0xdb, 0xbf, 0x01, 0x6d, 0x8a, 0xba, 0x05, 0x5d, 0x1b, 0x90, - 0x6d, 0x67, 0x02, 0xf1, 0x22, 0xb7, 0x1c, 0x20, 0x3b, 0xdd, 0x82, 0xc5, 0x11, 0xc4, 0x81, 0x11, 0xf7, 0xc5, 0x21, - 0xe6, 0xce, 0x24, 0x5a, 0x2d, 0x8c, 0xcd, 0x9a, 0xa3, 0xa1, 0x3f, 0x73, 0x6c, 0xfb, 0xa0, 0x52, 0x1f, 0x14, 0xd9, - 0x75, 0xb5, 0x75, 0x23, 0x19, 0x38, 0xb6, 0xe9, 0x3d, 0xb3, 0x5a, 0xfd, 0x0a, 0x8d, 0x96, 0xc2, 0x39, 0x8f, 0x50, - 0xfd, 0x35, 0x7c, 0xb2, 0xd1, 0x2a, 0x07, 0x52, 0x2f, 0x3b, 0x67, 0xe0, 0xd8, 0x52, 0xae, 0xff, 0x1a, 0x55, 0x49, - 0x3f, 0x05, 0x93, 0xa6, 0xd4, 0x62, 0x23, 0x48, 0x48, 0xa0, 0xc1, 0x31, 0xfa, 0x8b, 0xf2, 0x5c, 0xd1, 0xe8, 0xf8, - 0xe8, 0xfa, 0xa8, 0x2f, 0x30, 0x8a, 0xf0, 0x5e, 0x94, 0x3b, 0x28, 0x7d, 0x31, 0x2e, 0x63, 0x38, 0x1e, 0xfa, 0x9c, - 0xe5, 0x37, 0x7a, 0x5b, 0xb9, 0x05, 0xec, 0xbf, 0x81, 0x7c, 0x5a, 0x63, 0x88, 0xaf, 0x01, 0x35, 0x20, 0x7d, 0xc9, - 0xce, 0x0e, 0x21, 0x6c, 0x92, 0xdc, 0x5d, 0x91, 0x48, 0xee, 0xdf, 0x19, 0x12, 0x1d, 0xbc, 0x43, 0xcb, 0xfa, 0xab, - 0x27, 0x77, 0x0f, 0xec, 0x92, 0x05, 0xd3, 0x62, 0x87, 0x25, 0xfa, 0xb5, 0x7f, 0x77, 0x05, 0x8c, 0x02, 0x79, 0x7d, - 0xc2, 0x1a, 0x8c, 0x92, 0x86, 0x01, 0x6e, 0x7e, 0x3a, 0x6e, 0xde, 0x5e, 0x5c, 0x0c, 0x36, 0xa0, 0xa0, 0x9c, 0x59, - 0x33, 0x49, 0x29, 0x0e, 0xc9, 0x23, 0xd0, 0xb9, 0x59, 0x13, 0x8c, 0x68, 0xe3, 0x4e, 0x4c, 0x84, 0x25, 0x69, 0xde, - 0xc6, 0xe3, 0xe1, 0xa0, 0xf7, 0xd5, 0x5a, 0x7b, 0xbb, 0xb5, 0xd6, 0xc9, 0x2e, 0xad, 0x35, 0x39, 0xee, 0x91, 0xf9, - 0x53, 0xe6, 0xc0, 0x28, 0x98, 0x73, 0xd9, 0x05, 0xb4, 0xa0, 0xea, 0x46, 0x3f, 0x3f, 0xd1, 0xaa, 0xd2, 0x1b, 0xd9, - 0x86, 0xa2, 0xfa, 0x5b, 0x12, 0x50, 0xc4, 0x85, 0xba, 0xac, 0x1b, 0xbf, 0xc8, 0x75, 0xe3, 0x24, 0xd5, 0xe4, 0x2e, - 0x5b, 0x82, 0xfb, 0x97, 0xdc, 0x21, 0x33, 0xe9, 0x20, 0x77, 0x8b, 0xcc, 0x47, 0x2a, 0x39, 0xfa, 0xe5, 0x82, 0x86, - 0xe4, 0x3e, 0x2a, 0xa4, 0x8c, 0xa2, 0x17, 0x69, 0xb1, 0x6a, 0xee, 0xe7, 0x97, 0x97, 0x83, 0xd6, 0x1d, 0x87, 0x9c, - 0x15, 0xcb, 0xdb, 0xa6, 0xe8, 0xe8, 0x25, 0xbf, 0x96, 0x36, 0x49, 0xe6, 0x91, 0x45, 0x00, 0x16, 0x6a, 0xfa, 0xd2, - 0xbd, 0x76, 0x66, 0x03, 0x81, 0x83, 0xac, 0x71, 0x20, 0xdd, 0xad, 0x9d, 0xa7, 0x94, 0x45, 0xf9, 0xd5, 0xb5, 0x83, - 0xd4, 0x9d, 0x6e, 0x82, 0x65, 0x7d, 0x04, 0xc2, 0xfa, 0x4a, 0xd2, 0x20, 0xf4, 0x6c, 0xc5, 0xee, 0xd7, 0x30, 0x00, - 0x48, 0xff, 0xcb, 0xcf, 0x9c, 0x15, 0x00, 0x4d, 0xa4, 0x62, 0xcb, 0x77, 0xfe, 0x78, 0x88, 0x4d, 0x32, 0x3f, 0xc3, - 0xaa, 0xd5, 0x6f, 0x92, 0xbe, 0x67, 0xc3, 0xdd, 0xb5, 0x8a, 0xea, 0x7c, 0x5e, 0xa3, 0x27, 0xc6, 0xc1, 0x77, 0x59, - 0xb4, 0x0e, 0x30, 0x13, 0x8d, 0x99, 0x44, 0xee, 0xe4, 0xc3, 0x46, 0xfa, 0x1e, 0x57, 0x89, 0x82, 0xba, 0xb8, 0x78, - 0xa9, 0xd0, 0x77, 0x31, 0x70, 0x33, 0xeb, 0x59, 0xad, 0x58, 0x52, 0xd4, 0xf4, 0x1e, 0xdb, 0x6d, 0xf7, 0xc5, 0xec, - 0xb0, 0xa4, 0x3f, 0x6d, 0x75, 0x8a, 0xda, 0xf5, 0x6c, 0x1c, 0xcb, 0xf0, 0x57, 0xee, 0xd8, 0xfa, 0xc7, 0x7f, 0x3a, - 0xe6, 0xdf, 0x2c, 0xad, 0xd1, 0xa7, 0x0c, 0x01, 0xda, 0x17, 0x2e, 0xa6, 0xe5, 0x6b, 0x9a, 0x4a, 0x49, 0xd3, 0xb0, - 0x66, 0x9e, 0xef, 0x9b, 0x3e, 0xb8, 0x17, 0x6d, 0x3e, 0x69, 0x7a, 0xd8, 0xcf, 0x1a, 0x52, 0x06, 0x7c, 0x42, 0x3f, - 0xc5, 0x9d, 0x92, 0x2c, 0xd6, 0xcb, 0xf1, 0x46, 0x56, 0x94, 0x4b, 0xfa, 0xf3, 0xaa, 0xce, 0x5c, 0xfe, 0xec, 0x6c, - 0x36, 0x2b, 0x6a, 0x8d, 0x6d, 0xe5, 0x10, 0x35, 0xbf, 0x8f, 0x6d, 0xdb, 0x2e, 0xc3, 0xb7, 0xe9, 0xa0, 0xd0, 0xc1, - 0x30, 0x51, 0x09, 0xdf, 0xdd, 0xbd, 0xa7, 0xfe, 0xa0, 0xd1, 0x52, 0x57, 0x4d, 0xe7, 0x91, 0xb6, 0xda, 0xff, 0x8b, - 0xa1, 0x20, 0x6a, 0xd8, 0x75, 0xfc, 0xab, 0x7b, 0x65, 0x4b, 0x4f, 0xe5, 0x03, 0xfc, 0xb0, 0xc6, 0x3b, 0xf6, 0xfa, - 0x1e, 0x4d, 0x9b, 0xb6, 0x77, 0x6a, 0xe5, 0x64, 0xb7, 0x60, 0xb3, 0xd4, 0x27, 0x4b, 0x25, 0x2f, 0x61, 0xcb, 0xb8, - 0x37, 0x61, 0x78, 0x41, 0x6a, 0x49, 0xd4, 0x16, 0xad, 0x7a, 0xcc, 0x39, 0xd8, 0x71, 0x39, 0x02, 0x0f, 0xdb, 0x0a, - 0x5e, 0x56, 0x55, 0x6e, 0xd6, 0xc4, 0x47, 0x90, 0x8a, 0x6d, 0xaa, 0x17, 0x4e, 0xb8, 0x4d, 0x3b, 0xf6, 0x5f, 0x0a, - 0xf5, 0x14, 0xe0, 0x4e, 0x37, 0xc2, 0xda, 0x84, 0x2e, 0x4f, 0xf0, 0xef, 0xec, 0x72, 0xee, 0xc5, 0xea, 0xae, 0x68, - 0xdc, 0xd5, 0x85, 0xeb, 0xa6, 0x9c, 0x94, 0xd1, 0xa8, 0xeb, 0x50, 0x5f, 0x66, 0x02, 0x34, 0x93, 0xad, 0x5b, 0xc0, - 0x82, 0xa6, 0x90, 0x20, 0xaf, 0xe6, 0x6e, 0x0c, 0xc5, 0x59, 0xd8, 0x79, 0xb9, 0x7e, 0x3f, 0x4f, 0xef, 0x0c, 0x73, - 0x30, 0x9e, 0x77, 0xf1, 0x72, 0xaf, 0xb0, 0x55, 0xd1, 0x54, 0x06, 0xf7, 0x80, 0x90, 0x48, 0x95, 0x75, 0xe4, 0x9b, - 0x94, 0x3d, 0x4e, 0xd3, 0x37, 0xd5, 0x79, 0x37, 0x77, 0xef, 0x74, 0xe0, 0x5e, 0xa3, 0x0a, 0xaa, 0xbd, 0xae, 0xf6, - 0xca, 0x77, 0xd8, 0x62, 0x9c, 0xb0, 0x02, 0xe0, 0x8a, 0xa2, 0xa0, 0xd1, 0x90, 0x52, 0xc2, 0x7d, 0x34, 0xe9, 0xec, - 0xad, 0x8c, 0xac, 0xc5, 0x3c, 0xb1, 0xbb, 0xfa, 0x2a, 0xd4, 0xb7, 0xb8, 0x19, 0x04, 0xd8, 0x71, 0xec, 0x84, 0xcf, - 0x26, 0xec, 0x18, 0x19, 0x5d, 0x39, 0xb8, 0x83, 0xf0, 0x94, 0x9a, 0x14, 0xdf, 0x96, 0x4e, 0x29, 0xde, 0x25, 0x7c, - 0x57, 0xab, 0xbc, 0xbf, 0x28, 0x68, 0xe3, 0xb9, 0x3f, 0x50, 0x4b, 0xdf, 0xab, 0xf6, 0xd2, 0x0b, 0xf6, 0xaf, 0xeb, - 0xde, 0xed, 0x5d, 0x17, 0x98, 0xc3, 0xbd, 0x2b, 0x03, 0x77, 0x49, 0x56, 0x4a, 0xc9, 0xe0, 0x3b, 0xe9, 0xf2, 0x40, - 0x8e, 0x65, 0xa1, 0x62, 0x2b, 0x92, 0xe8, 0x2f, 0xd6, 0x83, 0xd1, 0xc9, 0xe9, 0xdd, 0xd2, 0x57, 0x6e, 0x58, 0x04, - 0x99, 0x34, 0x07, 0xaa, 0x63, 0xd9, 0xaa, 0x82, 0x91, 0x19, 0xbc, 0x60, 0x3e, 0x50, 0x7f, 0xba, 0xf8, 0xc6, 0xec, - 0xaa, 0xa7, 0x60, 0x8e, 0x71, 0x33, 0x47, 0x16, 0xf7, 0xdc, 0xbd, 0x67, 0xd1, 0x75, 0x4b, 0x55, 0x30, 0x61, 0x26, - 0x31, 0xb7, 0x58, 0xa6, 0xb4, 0xd4, 0x3d, 0xf2, 0xb2, 0x29, 0x22, 0xb5, 0xb2, 0x0a, 0x88, 0xd5, 0x69, 0x75, 0x15, - 0xa7, 0x75, 0x68, 0x1d, 0x75, 0xd5, 0xe1, 0x17, 0x8a, 0x72, 0x32, 0x65, 0xb3, 0x78, 0x88, 0xea, 0x98, 0x13, 0xe4, - 0x07, 0xe9, 0xb7, 0xa2, 0x58, 0x13, 0x3f, 0x36, 0x1d, 0x65, 0xc3, 0x1f, 0x15, 0x05, 0x90, 0x51, 0x4f, 0x79, 0x3c, - 0x6b, 0xcd, 0x0e, 0x67, 0x2f, 0xfa, 0xbc, 0x38, 0xfd, 0xa2, 0x50, 0xdd, 0xa0, 0x7f, 0x5b, 0x52, 0xb3, 0x38, 0x89, - 0xc2, 0x0f, 0x8c, 0xf3, 0x92, 0x4a, 0xa6, 0x28, 0x2a, 0x37, 0x6d, 0x55, 0xbf, 0xe4, 0x74, 0xc7, 0x93, 0x59, 0x2b, - 0xaf, 0x8e, 0x63, 0x3c, 0xc8, 0x06, 0x79, 0x72, 0x20, 0x86, 0x7e, 0x22, 0x83, 0xc9, 0x31, 0xeb, 0x00, 0xe5, 0xa8, - 0x7c, 0x8e, 0x73, 0x31, 0xbf, 0x13, 0x08, 0x7b, 0x9e, 0x7b, 0x70, 0xc4, 0xd8, 0x6c, 0xa0, 0x7e, 0xef, 0xb4, 0xba, - 0x86, 0xe3, 0x1c, 0x59, 0x47, 0xdd, 0x89, 0x6d, 0x1c, 0x5a, 0x87, 0x66, 0xdb, 0x3a, 0x32, 0xba, 0x66, 0xd7, 0xe8, - 0x7e, 0xdb, 0x9d, 0x98, 0x87, 0xd6, 0xa1, 0x61, 0x9b, 0x5d, 0x28, 0x34, 0xbb, 0x66, 0xf7, 0xc6, 0x3c, 0xec, 0x4e, - 0x6c, 0x2c, 0x6d, 0x59, 0x9d, 0x8e, 0xe9, 0xd8, 0x56, 0xa7, 0x63, 0x74, 0xac, 0xa3, 0x23, 0xd3, 0x69, 0x5b, 0x47, - 0x47, 0xe7, 0x9d, 0xae, 0xd5, 0x86, 0x77, 0xed, 0xf6, 0xa4, 0x6d, 0x39, 0x8e, 0x09, 0x7f, 0x19, 0x5d, 0xab, 0x45, - 0x3f, 0x1c, 0xc7, 0x6a, 0x3b, 0x86, 0xed, 0x77, 0x5a, 0xd6, 0xd1, 0x0b, 0x03, 0xff, 0xc6, 0x6a, 0x06, 0xfe, 0x05, - 0xdd, 0x18, 0x2f, 0xac, 0xd6, 0x11, 0xfd, 0xc2, 0x0e, 0x6f, 0x0e, 0xbb, 0xff, 0x54, 0x0f, 0x1a, 0xe7, 0xe0, 0xd0, - 0x1c, 0xba, 0x1d, 0xab, 0xdd, 0x36, 0x0e, 0x1d, 0xab, 0xdb, 0x5e, 0x98, 0x87, 0x2d, 0xeb, 0xe8, 0x78, 0x62, 0x3a, - 0xd6, 0xf1, 0xb1, 0x61, 0x9b, 0x6d, 0xab, 0x65, 0x38, 0xd6, 0x61, 0x1b, 0x7f, 0xb4, 0xad, 0xd6, 0xcd, 0xf1, 0x0b, - 0xeb, 0xa8, 0xb3, 0x38, 0xb2, 0x0e, 0x7f, 0x3e, 0xec, 0x5a, 0xad, 0xf6, 0xa2, 0x7d, 0x64, 0xb5, 0x8e, 0x6f, 0x8e, - 0xac, 0xc3, 0x85, 0xd9, 0x3a, 0xda, 0xda, 0xd2, 0x69, 0x59, 0x00, 0x23, 0x7c, 0x0d, 0x2f, 0x0c, 0xfe, 0x02, 0xfe, - 0x2c, 0xb0, 0xed, 0x1f, 0xd8, 0x4d, 0x5c, 0x6d, 0xfa, 0xc2, 0xea, 0x1e, 0x4f, 0xa8, 0x3a, 0x14, 0x98, 0xa2, 0x06, - 0x34, 0xb9, 0x31, 0xe9, 0xb3, 0xd8, 0x9d, 0x29, 0x3a, 0x12, 0x7f, 0xf8, 0xc7, 0x6e, 0x4c, 0xf8, 0x30, 0x7d, 0xf7, - 0x4f, 0xed, 0x27, 0x5b, 0x72, 0x48, 0x14, 0xff, 0x05, 0xff, 0x87, 0x72, 0x2c, 0x8e, 0x8c, 0xf3, 0xa6, 0x4b, 0xc9, - 0x77, 0xbb, 0x2f, 0x25, 0xbf, 0x59, 0xef, 0x73, 0x29, 0xf9, 0xee, 0xb3, 0x5f, 0x4a, 0x9e, 0x97, 0x7d, 0x6b, 0xde, - 0x95, 0x53, 0x41, 0x7d, 0xb7, 0x29, 0xab, 0x1c, 0x3c, 0x57, 0xbb, 0xbc, 0x58, 0x5f, 0x41, 0x68, 0xbf, 0x77, 0xe1, - 0xe0, 0x9b, 0x75, 0xc1, 0xe0, 0x33, 0x04, 0x1c, 0xfb, 0x2e, 0x24, 0x1c, 0xfb, 0xc3, 0x7a, 0x00, 0x56, 0x66, 0x9c, - 0xcd, 0xf1, 0xa6, 0xe6, 0xc2, 0xf5, 0x67, 0x19, 0x8b, 0x04, 0x25, 0x7d, 0x2c, 0x06, 0xc7, 0x35, 0x20, 0xcf, 0x20, - 0xc9, 0xac, 0x97, 0x41, 0x0c, 0x16, 0xc1, 0x60, 0xc9, 0x31, 0x8b, 0xd2, 0x52, 0x63, 0x4b, 0x04, 0x43, 0xbc, 0xe6, - 0x5e, 0x50, 0x8d, 0xef, 0xd1, 0x00, 0xb8, 0xbe, 0x77, 0xa7, 0xda, 0xaf, 0x02, 0x96, 0x75, 0xc2, 0x40, 0x1a, 0xb8, - 0xfd, 0xba, 0xf7, 0x45, 0x33, 0xdc, 0x92, 0xe1, 0x75, 0xf3, 0x48, 0x61, 0x24, 0xe5, 0xf6, 0x4e, 0xd1, 0x8c, 0x77, - 0xd7, 0x34, 0x6b, 0x3e, 0x5f, 0x68, 0xbe, 0xc5, 0x86, 0x38, 0xeb, 0xb8, 0x0c, 0xaa, 0x52, 0x22, 0xe3, 0x5a, 0x80, - 0xe4, 0x02, 0x6a, 0x6e, 0x68, 0x9c, 0x73, 0xaa, 0xb6, 0x82, 0xfc, 0x8e, 0x2d, 0xbd, 0x2b, 0xf4, 0x29, 0x1b, 0x27, - 0x3f, 0xdb, 0xa0, 0x5c, 0xe1, 0xfd, 0x0a, 0x9c, 0x28, 0xe7, 0x78, 0xc6, 0xa1, 0x0c, 0xe7, 0x8d, 0xd4, 0x2f, 0x69, - 0x23, 0xd2, 0x85, 0xb3, 0xa9, 0xf2, 0xa2, 0x8d, 0x6e, 0x09, 0x0e, 0x5b, 0x0a, 0x2e, 0x08, 0x3f, 0x4f, 0x4e, 0x00, - 0x29, 0x39, 0x6a, 0xa0, 0x9f, 0xc3, 0xb6, 0xce, 0x44, 0xbd, 0xc7, 0xb0, 0x89, 0x79, 0xd0, 0x65, 0x45, 0x8e, 0x36, - 0xb3, 0x99, 0xf9, 0xa1, 0x9b, 0xf4, 0x90, 0x4d, 0x93, 0x58, 0xde, 0x16, 0x7a, 0x2c, 0xf4, 0xb7, 0x18, 0xd3, 0xc9, - 0x1d, 0xf3, 0x4e, 0xd0, 0xf3, 0x61, 0x9b, 0xfd, 0x5d, 0xe6, 0x70, 0xb6, 0x29, 0x98, 0xa3, 0x38, 0x9d, 0x63, 0xc3, - 0x39, 0x32, 0xac, 0xe3, 0x8e, 0x9e, 0x8a, 0x03, 0x27, 0x77, 0x59, 0x00, 0x08, 0x38, 0x40, 0x64, 0xc3, 0xf4, 0x02, - 0x2f, 0xf1, 0x5c, 0x3f, 0x05, 0x7e, 0xb8, 0x28, 0xa4, 0xfc, 0x6b, 0x1d, 0x27, 0x30, 0x47, 0xc1, 0xf4, 0xa2, 0xf3, - 0x87, 0x39, 0x66, 0xc9, 0x2d, 0x63, 0x41, 0x83, 0x61, 0x4c, 0xd9, 0x97, 0xe4, 0xf7, 0xb3, 0xac, 0x4f, 0xc9, 0x6a, - 0x6d, 0x9c, 0x04, 0x7c, 0x7f, 0x08, 0xc7, 0x87, 0x74, 0x64, 0xfc, 0xda, 0x84, 0x70, 0xff, 0xb5, 0x1b, 0xe1, 0x26, - 0x6c, 0x1f, 0x84, 0xfb, 0xaf, 0xcf, 0x8e, 0x70, 0x7f, 0x95, 0x11, 0x6e, 0xc1, 0x7f, 0x30, 0xbf, 0x61, 0x7a, 0x8f, - 0xcf, 0x1a, 0x24, 0x51, 0x79, 0xae, 0x1e, 0x10, 0x03, 0x0f, 0xf9, 0x25, 0x44, 0x14, 0xaf, 0x97, 0x85, 0x64, 0x9d, - 0xa8, 0x00, 0xc5, 0x04, 0x1d, 0x94, 0x18, 0xd0, 0x03, 0x57, 0xb7, 0x2c, 0x39, 0x20, 0xbb, 0x55, 0xce, 0x82, 0xc4, - 0xb7, 0xde, 0x71, 0x39, 0x12, 0x2e, 0x74, 0xbf, 0x09, 0xa3, 0xa5, 0x8b, 0xd1, 0x5f, 0x55, 0x4c, 0xf2, 0x0d, 0x0f, - 0x36, 0x38, 0xe3, 0x4e, 0xc2, 0x60, 0x9a, 0xdd, 0x4a, 0xb2, 0xc1, 0x25, 0x71, 0xdc, 0xea, 0x3d, 0x73, 0x23, 0xd5, - 0xa0, 0xd7, 0xb0, 0xb8, 0xcf, 0xda, 0xf6, 0xb3, 0xd6, 0xe1, 0xb3, 0x23, 0x1b, 0xfe, 0x77, 0x58, 0x3b, 0x35, 0x78, - 0xc5, 0x65, 0x18, 0x40, 0x9e, 0x41, 0x51, 0xb3, 0xa9, 0xda, 0x2d, 0x63, 0x1f, 0xf2, 0x5a, 0xc7, 0xf5, 0x95, 0xa6, - 0xee, 0x7d, 0x5e, 0xa7, 0xb6, 0xc6, 0x22, 0x5c, 0x4b, 0xc3, 0xaa, 0x19, 0x8d, 0x17, 0xac, 0x41, 0xcf, 0x2e, 0xd5, - 0x90, 0x5f, 0xf3, 0xe9, 0xe6, 0xf3, 0x62, 0xed, 0xf4, 0x2a, 0x4f, 0x66, 0x2a, 0x92, 0x2a, 0xee, 0x84, 0x20, 0xbf, - 0xa2, 0xb4, 0x31, 0xde, 0x37, 0x66, 0x9c, 0x80, 0x68, 0xdf, 0x59, 0x0a, 0x4a, 0x97, 0x16, 0x28, 0x89, 0xd6, 0xc1, - 0x44, 0xc3, 0x9f, 0xee, 0x38, 0xd6, 0xbc, 0x83, 0xc8, 0xe2, 0x1f, 0xd6, 0x71, 0xd5, 0xdc, 0xa1, 0x9d, 0x67, 0x7e, - 0x8b, 0xc5, 0xaa, 0xb8, 0xcf, 0x12, 0x23, 0xc2, 0x7b, 0x6c, 0x5a, 0x5a, 0x73, 0xe0, 0x3e, 0xcb, 0x1a, 0x3e, 0x4b, - 0x8c, 0xe0, 0x39, 0xdc, 0x7d, 0x0e, 0xec, 0xa7, 0x4f, 0xa9, 0x16, 0x64, 0x51, 0xa7, 0x69, 0x9d, 0x4e, 0xf2, 0xa0, - 0xa1, 0x8a, 0x3b, 0x0f, 0x29, 0x6e, 0x68, 0x6f, 0x62, 0x84, 0xcf, 0x9f, 0x0f, 0x07, 0x8e, 0x8e, 0x59, 0x45, 0x45, - 0x76, 0x70, 0x9e, 0xb0, 0xf6, 0x7c, 0x3f, 0x43, 0x23, 0xbd, 0xd6, 0x95, 0x76, 0x05, 0x32, 0x93, 0x2d, 0xdc, 0x11, - 0x38, 0xf6, 0x82, 0x04, 0x72, 0x64, 0x50, 0xe0, 0x0a, 0x83, 0x1f, 0x51, 0x27, 0x93, 0xba, 0xda, 0x96, 0x6d, 0xd9, - 0x6a, 0xd6, 0x70, 0xe6, 0xcd, 0x07, 0x9b, 0x30, 0x71, 0x21, 0x15, 0xa7, 0x1f, 0xce, 0xc1, 0x8f, 0x2e, 0xf1, 0x12, - 0x1f, 0xf2, 0x3a, 0x82, 0x43, 0xdd, 0x92, 0xe4, 0xf2, 0x94, 0x7b, 0x37, 0xb8, 0xd1, 0x07, 0xcc, 0xed, 0x2d, 0x5c, - 0x71, 0x31, 0x8e, 0xdd, 0xf7, 0x40, 0x0c, 0x35, 0x55, 0x03, 0xdd, 0x00, 0x8b, 0x62, 0x53, 0xf6, 0x16, 0xea, 0x29, - 0xd0, 0x46, 0x57, 0xf9, 0x24, 0x66, 0x91, 0xbb, 0x84, 0x1c, 0x48, 0x9b, 0xd4, 0xe0, 0x98, 0x56, 0xe5, 0xa8, 0x56, - 0x71, 0x5e, 0x1c, 0x19, 0x4a, 0xcb, 0x31, 0x14, 0x1b, 0xd0, 0xad, 0x9a, 0x1a, 0x9b, 0xf4, 0xaa, 0xbf, 0xcb, 0xe0, - 0x81, 0xf0, 0xcb, 0x63, 0x9a, 0x07, 0x99, 0x3a, 0xf0, 0xab, 0xa4, 0x84, 0xe2, 0x17, 0x6b, 0x52, 0x66, 0x13, 0x8f, - 0x2e, 0x3d, 0x2f, 0xd8, 0x5d, 0xa2, 0x63, 0xde, 0x43, 0x5e, 0xc5, 0xd3, 0x37, 0xe8, 0x30, 0xec, 0x05, 0x8a, 0xf7, - 0xf1, 0xa3, 0xe6, 0x81, 0x33, 0xd3, 0x40, 0x82, 0x0f, 0x3c, 0xeb, 0x05, 0x80, 0x79, 0xf9, 0x35, 0x3d, 0x02, 0x0b, - 0x3c, 0x0d, 0xe1, 0xdf, 0xbc, 0x58, 0xfc, 0xe0, 0x66, 0x12, 0x96, 0xef, 0x06, 0x73, 0x40, 0x69, 0x6e, 0x30, 0xaf, - 0x98, 0x63, 0x91, 0xcf, 0x73, 0xa9, 0x34, 0xef, 0x2a, 0x37, 0x95, 0x8a, 0x5f, 0xde, 0x5f, 0x50, 0x5e, 0x57, 0x4d, - 0x05, 0x2a, 0x87, 0x2e, 0xba, 0xf9, 0x4d, 0xee, 0xf3, 0xc1, 0x97, 0x27, 0x4b, 0x96, 0xb8, 0x74, 0x0d, 0x04, 0xc2, - 0x2f, 0xb0, 0x03, 0x0a, 0x27, 0x34, 0x3c, 0x36, 0xd4, 0x60, 0xca, 0x6e, 0xbc, 0x09, 0x97, 0x4b, 0x0d, 0x85, 0xd3, - 0x29, 0x13, 0x2d, 0x3e, 0x07, 0x8e, 0x41, 0x0e, 0x07, 0x13, 0x17, 0x43, 0x1d, 0x0f, 0x82, 0x50, 0x1d, 0x7e, 0x99, - 0xf9, 0x66, 0x36, 0x2d, 0x02, 0x24, 0x57, 0xbf, 0x8c, 0x98, 0xff, 0xef, 0xc1, 0x97, 0x40, 0xb8, 0xbf, 0xbc, 0x52, - 0xf5, 0x7e, 0x62, 0x2d, 0x22, 0x36, 0x1b, 0x7c, 0x59, 0x93, 0x64, 0x1c, 0xc5, 0x7b, 0x1a, 0x8b, 0xda, 0x6e, 0xe5, - 0x21, 0xe7, 0xda, 0x7b, 0x09, 0xf5, 0x43, 0x2e, 0xad, 0x83, 0x04, 0xb8, 0x29, 0xc8, 0xd8, 0x4e, 0x1f, 0xe5, 0xe7, - 0xb1, 0xef, 0x4e, 0x3e, 0xf4, 0xe9, 0x4d, 0xe1, 0xc1, 0x04, 0x6a, 0x3d, 0x71, 0x57, 0x3d, 0x24, 0xaf, 0x72, 0x21, - 0x78, 0x4f, 0x53, 0x69, 0xc6, 0xd9, 0xd5, 0xee, 0x65, 0xdc, 0xca, 0x1b, 0xfc, 0x32, 0x7e, 0xea, 0x76, 0xe1, 0x25, - 0x4c, 0x7c, 0x0a, 0x1f, 0xd2, 0x54, 0x08, 0xea, 0x24, 0xa2, 0xa2, 0x60, 0x6d, 0xb5, 0x15, 0xa7, 0xfb, 0x6d, 0xe7, - 0xc6, 0xb1, 0x17, 0x2d, 0xc7, 0xea, 0xfe, 0xec, 0x74, 0x17, 0x6d, 0xeb, 0xd8, 0x37, 0xdb, 0xd6, 0x31, 0xfc, 0xf9, - 0xf9, 0xd8, 0xea, 0x2e, 0xcc, 0x96, 0x75, 0xf8, 0xb3, 0xd3, 0xf2, 0xcd, 0xae, 0x75, 0x0c, 0x7f, 0xce, 0xa9, 0x15, - 0x08, 0x40, 0x24, 0xef, 0x7c, 0x59, 0xc0, 0x02, 0xd2, 0xef, 0xec, 0x4e, 0xd6, 0x28, 0x90, 0xb7, 0x9a, 0x7b, 0x5d, - 0x40, 0x19, 0x94, 0xfa, 0x07, 0x4d, 0x11, 0xfa, 0x5a, 0x30, 0x60, 0x94, 0xec, 0x47, 0x98, 0xb7, 0x09, 0x3f, 0x74, - 0x91, 0x5f, 0xa5, 0xf6, 0x18, 0xf1, 0x36, 0xf5, 0x39, 0x45, 0x44, 0x22, 0x58, 0xba, 0x08, 0xfe, 0x69, 0x85, 0xa1, - 0xf1, 0x44, 0xfa, 0x2c, 0x09, 0x2b, 0xe5, 0xc9, 0xc8, 0xd3, 0xdd, 0x03, 0x47, 0x6f, 0x7e, 0x96, 0x25, 0xc3, 0xfc, - 0xac, 0x7d, 0x4b, 0x59, 0xca, 0x3e, 0xa9, 0x1f, 0xcc, 0xce, 0x94, 0x27, 0x56, 0x82, 0x88, 0xe2, 0x53, 0x2f, 0xca, - 0x86, 0x27, 0xa1, 0x68, 0xa7, 0x3e, 0x19, 0x8b, 0x0e, 0x59, 0x03, 0xcf, 0x80, 0x4b, 0xbe, 0x71, 0x7d, 0xc9, 0x90, - 0x4d, 0x6a, 0xf9, 0x28, 0xc3, 0xfc, 0x4f, 0x9f, 0xe6, 0x83, 0x33, 0x4b, 0xe3, 0x3e, 0x71, 0x3a, 0x40, 0x76, 0x3b, - 0xac, 0xbd, 0xd5, 0xa6, 0x72, 0x77, 0x2c, 0xfa, 0x3c, 0x08, 0xb5, 0xb0, 0x9b, 0x12, 0x16, 0x1b, 0x8d, 0x86, 0x9d, - 0x15, 0x7b, 0x0d, 0x88, 0xe2, 0x5f, 0x12, 0x75, 0x54, 0xbd, 0x1f, 0x08, 0xf3, 0x83, 0x60, 0x4b, 0xfc, 0x7d, 0x2e, - 0x8b, 0xa9, 0x00, 0x9a, 0x2d, 0xf3, 0xd8, 0xe1, 0x20, 0xfe, 0x67, 0x4f, 0x02, 0x9d, 0x35, 0xc1, 0x5e, 0xa2, 0x74, - 0x5a, 0x0b, 0xce, 0x7b, 0x19, 0x5d, 0x25, 0x82, 0xca, 0xe2, 0x53, 0x15, 0x8a, 0x20, 0x9d, 0x2c, 0x66, 0x90, 0xce, - 0x8c, 0x45, 0x33, 0x6a, 0x91, 0x17, 0x18, 0x1e, 0x26, 0x33, 0x11, 0x8e, 0xa3, 0xfa, 0xd3, 0xa7, 0x8d, 0x44, 0x88, - 0x8c, 0x73, 0x62, 0x96, 0x64, 0x39, 0x2e, 0x55, 0x19, 0xbf, 0xa9, 0x32, 0x8a, 0xc9, 0xfa, 0x45, 0xac, 0x21, 0x6c, - 0x5c, 0x69, 0xef, 0xe1, 0xcf, 0x31, 0x73, 0x13, 0x8b, 0x5f, 0x96, 0x6a, 0x12, 0x71, 0x37, 0x1c, 0xd6, 0x06, 0xeb, - 0x56, 0x1e, 0x41, 0x93, 0x47, 0xa8, 0x7d, 0xb2, 0x79, 0xb9, 0xe6, 0x51, 0x1d, 0xa0, 0x8f, 0x8f, 0x76, 0x1e, 0x80, - 0xec, 0x6d, 0xe2, 0x52, 0x60, 0x18, 0x99, 0xe4, 0x86, 0x89, 0x2b, 0xd2, 0x36, 0x02, 0x5f, 0xde, 0xaf, 0x35, 0xbf, - 0x90, 0x22, 0x3f, 0x0c, 0xdf, 0x5e, 0x7c, 0xad, 0xf0, 0xfd, 0x4f, 0xd6, 0x02, 0x28, 0xc8, 0x50, 0x6a, 0x9f, 0x01, - 0xa5, 0xf6, 0x51, 0x78, 0x6e, 0x29, 0xc8, 0x77, 0x93, 0x1e, 0x10, 0x04, 0x51, 0x01, 0x4d, 0x36, 0x14, 0xcb, 0xb5, - 0x9f, 0x78, 0x2b, 0x37, 0x4a, 0x0e, 0x30, 0xaf, 0x0f, 0x20, 0x39, 0xb5, 0x29, 0x1e, 0x04, 0x99, 0x61, 0x88, 0xc0, - 0xad, 0x49, 0x20, 0xec, 0x30, 0x66, 0x9e, 0x9f, 0x99, 0x61, 0x88, 0x0f, 0xb8, 0x93, 0x09, 0x5b, 0x25, 0x83, 0x42, - 0xfe, 0xa0, 0x70, 0x92, 0xb0, 0xc4, 0x8c, 0x93, 0x88, 0xb9, 0x4b, 0x35, 0x0b, 0x10, 0x5e, 0xed, 0x2f, 0x5e, 0x8f, - 0x97, 0x5e, 0x92, 0x45, 0xd8, 0xa5, 0x09, 0x82, 0x41, 0x04, 0x0c, 0x71, 0x38, 0x4a, 0x39, 0x08, 0xcf, 0xc3, 0x79, - 0x69, 0x47, 0xe5, 0x9c, 0xcb, 0x29, 0xc6, 0x6f, 0x27, 0x49, 0x06, 0xb4, 0xc5, 0x93, 0xd0, 0xbf, 0xe6, 0x31, 0x2c, - 0xb2, 0x40, 0xc0, 0xea, 0xf0, 0x84, 0x8b, 0xb7, 0x0a, 0x86, 0x6f, 0x51, 0x3b, 0x36, 0x44, 0xa8, 0x6f, 0x8a, 0x6e, - 0x71, 0xc0, 0x2b, 0x03, 0x69, 0xa2, 0x9e, 0x31, 0xc9, 0x08, 0x8d, 0xe5, 0x02, 0x18, 0xa1, 0x82, 0xc1, 0xcc, 0xc2, - 0x19, 0x66, 0xee, 0x94, 0x38, 0x2a, 0xe4, 0x95, 0x3e, 0x7e, 0x7c, 0x35, 0xfa, 0xed, 0x3f, 0x90, 0x09, 0x65, 0xe1, - 0x88, 0x98, 0x12, 0x97, 0x72, 0x2d, 0xce, 0x7d, 0x1a, 0x23, 0x34, 0x96, 0x62, 0x53, 0x11, 0xa2, 0x47, 0x6c, 0xad, - 0x74, 0x74, 0x25, 0x42, 0x34, 0x42, 0x92, 0x24, 0x5d, 0x44, 0xbe, 0x18, 0xc1, 0xf2, 0x8e, 0x44, 0x4c, 0x14, 0xe5, - 0x97, 0xbb, 0x97, 0xc7, 0x4a, 0x1e, 0xc3, 0xa8, 0xce, 0xa2, 0x87, 0xf6, 0xd0, 0xf0, 0xc4, 0x55, 0x90, 0x69, 0x41, - 0xf6, 0x23, 0xee, 0x1d, 0xc0, 0x34, 0x17, 0xe1, 0x92, 0x59, 0x5e, 0x78, 0x70, 0xcb, 0xc6, 0xa6, 0xbb, 0xf2, 0xc8, - 0x2e, 0x07, 0xf5, 0x6e, 0x0a, 0x71, 0x7e, 0x99, 0xb9, 0x0b, 0xf1, 0xd7, 0x69, 0x0e, 0xca, 0xb0, 0x18, 0x93, 0xb3, - 0xd3, 0xca, 0xef, 0x01, 0x21, 0x7e, 0x81, 0x04, 0xc7, 0x70, 0x78, 0x72, 0xe0, 0x0e, 0x8b, 0x41, 0x81, 0x2d, 0x91, - 0xbd, 0xa6, 0x48, 0x04, 0x4e, 0x29, 0xb6, 0xaf, 0x08, 0xe3, 0x9b, 0x3f, 0x98, 0xe1, 0x6c, 0x26, 0x07, 0xf2, 0xb5, - 0x8a, 0xc3, 0xcb, 0x80, 0x96, 0x6f, 0xe9, 0x70, 0x45, 0x5f, 0xaa, 0x7e, 0x22, 0xfb, 0xa9, 0xf6, 0x30, 0x82, 0x37, - 0xcc, 0x19, 0x8e, 0x7b, 0x25, 0x20, 0x70, 0x06, 0xb1, 0xc7, 0x54, 0x89, 0xe3, 0x91, 0x72, 0xfa, 0x89, 0x06, 0xce, - 0xe5, 0xd1, 0x60, 0x40, 0x68, 0xae, 0x8c, 0xed, 0x00, 0x88, 0x35, 0x99, 0x7c, 0x60, 0xb2, 0x09, 0x34, 0x34, 0xc9, - 0x5d, 0x16, 0x1b, 0x95, 0xa7, 0x53, 0x1d, 0xe3, 0x81, 0x2b, 0xb6, 0x5f, 0x61, 0x83, 0xc2, 0xc6, 0xe3, 0xeb, 0x0e, - 0xf8, 0x5d, 0xf4, 0x53, 0x42, 0xf3, 0xca, 0x57, 0x84, 0xd1, 0x4d, 0xdf, 0xbd, 0x0f, 0x25, 0x33, 0x26, 0x1e, 0xd1, - 0xe4, 0x1c, 0x4b, 0x2f, 0x84, 0x27, 0x71, 0xe5, 0xa0, 0x65, 0x09, 0x51, 0xaa, 0x87, 0x4d, 0x4e, 0x62, 0xb2, 0xeb, - 0xac, 0xc9, 0x75, 0x8b, 0x93, 0x41, 0xe4, 0x99, 0xe6, 0xe7, 0xb0, 0xf0, 0x12, 0xd1, 0x42, 0x7a, 0x72, 0x00, 0xf3, - 0x83, 0x28, 0x2c, 0x05, 0xc6, 0xc9, 0xd3, 0x21, 0xd4, 0x8b, 0x1b, 0x93, 0x29, 0xd6, 0xd9, 0x54, 0xf0, 0x7c, 0x28, - 0x58, 0x4a, 0xd9, 0xfd, 0xa4, 0x2a, 0x55, 0x5e, 0xc6, 0xae, 0x67, 0x02, 0x77, 0x67, 0x0f, 0xfa, 0x61, 0x8d, 0xa9, - 0x83, 0xd2, 0x7e, 0xc2, 0x44, 0x90, 0x83, 0xf3, 0xa4, 0x21, 0x0e, 0x42, 0x53, 0x15, 0xe2, 0x67, 0xb7, 0x54, 0xc8, - 0xf7, 0xf1, 0xb6, 0x5a, 0x39, 0xe7, 0x94, 0x55, 0x5b, 0xb9, 0x9a, 0xfa, 0x18, 0x77, 0x7c, 0xa5, 0x36, 0x96, 0x42, - 0xbd, 0xf3, 0x64, 0x00, 0x55, 0x85, 0x2e, 0xde, 0x5d, 0xad, 0xa8, 0xb2, 0xde, 0x3f, 0x39, 0x20, 0xb1, 0x74, 0x48, - 0x3b, 0x6c, 0x78, 0x02, 0xa6, 0xdc, 0xb4, 0xe8, 0xee, 0x6a, 0xc5, 0x97, 0x94, 0x7e, 0xd1, 0x9b, 0x83, 0x45, 0xb2, - 0xf4, 0x87, 0xff, 0x07, 0x52, 0xaf, 0x09, 0x6c, 0x30, 0x6a, 0x03, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0x69, 0x7b, 0x1b, 0x37, 0xb2, 0x30, 0xfa, + 0xf9, 0xde, 0x5f, 0x21, 0xf5, 0x71, 0x34, 0x0d, 0x11, 0x6c, 0x91, 0xd4, 0x62, 0xb9, 0x29, 0x88, 0xd7, 0xeb, 0xd8, + 0x59, 0x6c, 0xc7, 0xb2, 0x9d, 0x49, 0x18, 0x1e, 0x07, 0xec, 0x06, 0x49, 0xc4, 0x4d, 0x80, 0x69, 0x80, 0x96, 0x14, + 0x92, 0xff, 0xfd, 0x3e, 0x85, 0xa5, 0x1b, 0x4d, 0xd2, 0x9e, 0x39, 0xef, 0x5d, 0x9e, 0xf7, 0xe4, 0x8c, 0xc5, 0xc6, + 0x8e, 0x42, 0xa1, 0x50, 0x55, 0xa8, 0x2a, 0x5c, 0x1d, 0xe6, 0x32, 0xd3, 0xf7, 0x0b, 0x76, 0x30, 0xd3, 0xf3, 0xe2, + 0xfa, 0xca, 0xfd, 0xcb, 0x68, 0x7e, 0x7d, 0x55, 0x70, 0xf1, 0xf9, 0xa0, 0x64, 0x05, 0xe1, 0x99, 0x14, 0x07, 0xb3, + 0x92, 0x4d, 0x48, 0x4e, 0x35, 0x4d, 0xf9, 0x9c, 0x4e, 0xd9, 0xc1, 0xc9, 0xf5, 0xd5, 0x9c, 0x69, 0x7a, 0x90, 0xcd, + 0x68, 0xa9, 0x98, 0x26, 0x1f, 0xde, 0xbf, 0x68, 0x5f, 0x5e, 0x5f, 0xa9, 0xac, 0xe4, 0x0b, 0x7d, 0x00, 0x4d, 0x92, + 0xb9, 0xcc, 0x97, 0x05, 0xbb, 0x3e, 0x39, 0xb9, 0xbd, 0xbd, 0x4d, 0xfe, 0x54, 0xff, 0xe7, 0x17, 0x5a, 0x1e, 0xfc, + 0xb3, 0x24, 0x6f, 0xc6, 0x7f, 0xb2, 0x4c, 0x27, 0x39, 0x9b, 0x70, 0xc1, 0xde, 0x96, 0x72, 0xc1, 0x4a, 0x7d, 0xdf, + 0x87, 0xcc, 0x9f, 0x4b, 0x12, 0x73, 0xac, 0x31, 0x43, 0xe4, 0x5a, 0x1f, 0x70, 0x71, 0xc0, 0x07, 0xff, 0x2c, 0x4d, + 0xca, 0x8a, 0x89, 0xe5, 0x9c, 0x95, 0x74, 0x5c, 0xb0, 0xf4, 0xb0, 0x83, 0x33, 0x29, 0x26, 0x7c, 0xba, 0xac, 0xbe, + 0x6f, 0x4b, 0xae, 0xfd, 0xef, 0x2f, 0xb4, 0x58, 0xb2, 0x94, 0x6d, 0x50, 0xca, 0x87, 0x7a, 0x44, 0x98, 0x69, 0xf9, + 0x73, 0xdd, 0x70, 0xfc, 0xb3, 0x69, 0xf2, 0x7e, 0xc1, 0xe4, 0xe4, 0x40, 0x1f, 0x92, 0x48, 0xdd, 0xcf, 0xc7, 0xb2, + 0x88, 0x06, 0xba, 0x15, 0x45, 0x29, 0x94, 0xc1, 0x0c, 0xf5, 0x33, 0x29, 0x94, 0x3e, 0x10, 0x9c, 0xdc, 0x72, 0x91, + 0xcb, 0x5b, 0x7c, 0x2b, 0x88, 0xe0, 0xc9, 0xcd, 0x8c, 0xe6, 0xf2, 0xf6, 0x9d, 0x94, 0xfa, 0xe8, 0x28, 0x76, 0xdf, + 0xf7, 0x4f, 0x6f, 0x6e, 0x08, 0x21, 0x5f, 0x24, 0xcf, 0x0f, 0x3a, 0xeb, 0x75, 0x90, 0x9a, 0x08, 0xaa, 0xf9, 0x17, + 0x66, 0x2b, 0xa1, 0xa3, 0xa3, 0x88, 0xe6, 0x72, 0xa1, 0x59, 0x7e, 0xa3, 0xef, 0x0b, 0x76, 0x33, 0x63, 0x4c, 0xab, + 0x88, 0x8b, 0x83, 0x67, 0x32, 0x5b, 0xce, 0x99, 0xd0, 0xc9, 0xa2, 0x94, 0x5a, 0xc2, 0xc0, 0x8e, 0x8e, 0xa2, 0x92, + 0x2d, 0x0a, 0x9a, 0x31, 0xc8, 0x7f, 0x7a, 0x73, 0x53, 0xd7, 0xa8, 0x0b, 0xe1, 0xcf, 0x82, 0xdc, 0x98, 0xa1, 0xc7, + 0x08, 0xff, 0x22, 0x88, 0x60, 0xb7, 0x07, 0xbf, 0x30, 0xfa, 0xf9, 0x27, 0xba, 0xe8, 0x67, 0x05, 0x55, 0xea, 0xe0, + 0xb5, 0x5c, 0x99, 0x69, 0x94, 0xcb, 0x4c, 0xcb, 0x32, 0xd6, 0x98, 0x61, 0x81, 0x56, 0x7c, 0x12, 0xeb, 0x19, 0x57, + 0xc9, 0xa7, 0x07, 0x99, 0x52, 0xef, 0x98, 0x5a, 0x16, 0xfa, 0x01, 0x39, 0xec, 0x60, 0x71, 0x48, 0xc8, 0x67, 0x81, + 0xf4, 0xac, 0x94, 0xb7, 0x07, 0xcf, 0xcb, 0x52, 0x96, 0x71, 0xf4, 0xf4, 0xe6, 0xc6, 0x96, 0x38, 0xe0, 0xea, 0x40, + 0x48, 0x7d, 0x50, 0xb5, 0x07, 0xd0, 0x4e, 0x0e, 0x3e, 0x28, 0x76, 0xf0, 0xc7, 0x52, 0x28, 0x3a, 0x61, 0x4f, 0x6f, + 0x6e, 0xfe, 0x38, 0x90, 0xe5, 0xc1, 0x1f, 0x99, 0x52, 0x7f, 0x1c, 0x70, 0xa1, 0x34, 0xa3, 0x79, 0x12, 0xa1, 0xbe, + 0xe9, 0x2c, 0x53, 0xea, 0x3d, 0xbb, 0xd3, 0x44, 0x63, 0xf3, 0xa9, 0x09, 0xdb, 0x4c, 0x99, 0x3e, 0x50, 0xd5, 0xbc, + 0x62, 0xb4, 0x2a, 0x98, 0x3e, 0xd0, 0xc4, 0xe4, 0x4b, 0x07, 0x7f, 0x66, 0x3f, 0x75, 0x9f, 0x4f, 0xe2, 0x5b, 0x71, + 0x74, 0xa4, 0x2b, 0x40, 0xa3, 0x95, 0x5b, 0x21, 0xc2, 0x0e, 0x7d, 0xda, 0xd1, 0x11, 0x4b, 0x0a, 0x26, 0xa6, 0x7a, + 0x46, 0x08, 0xe9, 0xf6, 0xc5, 0xd1, 0x51, 0xac, 0xc9, 0x2f, 0x22, 0x99, 0x32, 0x1d, 0x33, 0x84, 0x70, 0x5d, 0xfb, + 0xe8, 0x28, 0xb6, 0x40, 0x90, 0x44, 0x1b, 0xc0, 0x35, 0x60, 0x8c, 0x12, 0x07, 0xfd, 0x9b, 0x7b, 0x91, 0xc5, 0xe1, + 0xf8, 0x11, 0x16, 0x47, 0x47, 0xbf, 0x88, 0x44, 0x41, 0x8b, 0x58, 0x23, 0xb4, 0x29, 0x99, 0x5e, 0x96, 0xe2, 0x40, + 0x6f, 0xb4, 0xbc, 0xd1, 0x25, 0x17, 0xd3, 0x18, 0xad, 0x7c, 0x5a, 0x50, 0x71, 0xb3, 0xb1, 0xc3, 0xfd, 0xad, 0x24, + 0x9c, 0x5c, 0x43, 0x8f, 0xaf, 0x65, 0xec, 0x70, 0x90, 0x13, 0x12, 0x29, 0x53, 0x37, 0x1a, 0xf0, 0x94, 0xb7, 0xa2, + 0x08, 0xdb, 0x51, 0xe2, 0xcf, 0x02, 0x61, 0xa1, 0x01, 0x75, 0x93, 0x24, 0xd1, 0x88, 0x5c, 0xaf, 0x3c, 0x58, 0x78, + 0x30, 0xd1, 0x01, 0x1f, 0x76, 0x46, 0xa9, 0x4e, 0x4a, 0x96, 0x2f, 0x33, 0x16, 0xc7, 0x02, 0x2b, 0x2c, 0x11, 0xb9, + 0x16, 0xad, 0xb8, 0x24, 0xd7, 0xb0, 0xde, 0x65, 0x73, 0xb1, 0x09, 0x39, 0xec, 0x20, 0x37, 0xc8, 0xd2, 0x8f, 0x10, + 0x40, 0xec, 0x06, 0x54, 0x12, 0x12, 0x89, 0xe5, 0x7c, 0xcc, 0xca, 0xa8, 0x2a, 0xd6, 0x6f, 0xe0, 0xc5, 0x52, 0xb1, + 0x83, 0x4c, 0xa9, 0x83, 0xc9, 0x52, 0x64, 0x9a, 0x4b, 0x71, 0x10, 0xb5, 0xca, 0x56, 0x64, 0xf1, 0xa1, 0x42, 0x87, + 0x08, 0x6d, 0x50, 0xac, 0x50, 0x8b, 0x0f, 0x65, 0xab, 0x3b, 0xc2, 0x30, 0x4a, 0xd4, 0x77, 0xed, 0x39, 0x08, 0x30, + 0xcc, 0x61, 0x92, 0x1b, 0xfc, 0xbd, 0xdd, 0xf9, 0x30, 0xc5, 0x5b, 0x31, 0xe0, 0xc9, 0xee, 0x4e, 0x21, 0x3a, 0x99, + 0xd3, 0x45, 0xcc, 0xc8, 0x35, 0x33, 0xd8, 0x45, 0x45, 0x06, 0x63, 0x6d, 0x2c, 0xdc, 0x80, 0xa5, 0x2c, 0xa9, 0x71, + 0x0a, 0xa5, 0x3a, 0x99, 0xc8, 0xf2, 0x39, 0xcd, 0x66, 0x50, 0xaf, 0xc2, 0x98, 0xdc, 0x6f, 0xb8, 0xac, 0x64, 0x54, + 0xb3, 0xe7, 0x05, 0x83, 0xaf, 0x38, 0x32, 0x35, 0x23, 0x84, 0x15, 0x6c, 0xf5, 0x82, 0xeb, 0xd7, 0x52, 0x64, 0xac, + 0xaf, 0x02, 0xfc, 0x32, 0x2b, 0xff, 0x58, 0xeb, 0x92, 0x8f, 0x97, 0x9a, 0xc5, 0x91, 0x80, 0x12, 0x11, 0x56, 0x08, + 0x8b, 0x44, 0xb3, 0x3b, 0xfd, 0x54, 0x0a, 0xcd, 0x84, 0x26, 0xcc, 0x43, 0x15, 0xf3, 0x84, 0x2e, 0x16, 0x4c, 0xe4, + 0x4f, 0x67, 0xbc, 0xc8, 0x63, 0x81, 0x36, 0x68, 0x83, 0x7f, 0x15, 0x04, 0x26, 0x49, 0xae, 0x79, 0x0a, 0xff, 0x7c, + 0x7d, 0x3a, 0xb1, 0x26, 0xd7, 0x66, 0x5b, 0x30, 0x12, 0x45, 0xfd, 0x89, 0x2c, 0x63, 0x37, 0x85, 0x03, 0x20, 0x5d, + 0xd0, 0xc7, 0xbb, 0x65, 0xc1, 0x14, 0x62, 0x2d, 0x22, 0xaa, 0x75, 0x74, 0x10, 0xfe, 0xad, 0x8c, 0x19, 0x2c, 0x00, + 0x47, 0x29, 0x37, 0x24, 0xf0, 0x47, 0xee, 0x36, 0x55, 0x5e, 0x11, 0xb5, 0xbf, 0x04, 0xc9, 0x79, 0xa2, 0xcb, 0xa5, + 0xd2, 0x2c, 0x7f, 0x7f, 0xbf, 0x60, 0x0a, 0x6b, 0x4a, 0xfe, 0x12, 0x83, 0xbf, 0x44, 0xc2, 0xe6, 0x0b, 0x7d, 0x7f, + 0x63, 0xa8, 0x79, 0x1a, 0x45, 0xf8, 0x5f, 0xa6, 0x68, 0xc9, 0x68, 0x06, 0x24, 0xcd, 0x81, 0xec, 0xad, 0x2c, 0xee, + 0x27, 0xbc, 0x28, 0x6e, 0x96, 0x8b, 0x85, 0x2c, 0x35, 0xd6, 0x82, 0xac, 0xb4, 0xac, 0xe1, 0x03, 0x2b, 0xba, 0x52, + 0xb7, 0x5c, 0x67, 0xb3, 0x58, 0xa3, 0x55, 0x46, 0x15, 0x3b, 0x78, 0x22, 0x65, 0xc1, 0xa8, 0x48, 0x39, 0xe1, 0x03, + 0x4d, 0x53, 0xb1, 0x2c, 0x8a, 0xfe, 0xb8, 0x64, 0xf4, 0x73, 0xdf, 0x64, 0xdb, 0xc3, 0x21, 0x35, 0xbf, 0x1f, 0x97, + 0x25, 0xbd, 0x87, 0x82, 0x84, 0x40, 0xb1, 0x01, 0x4f, 0xbf, 0xbf, 0x79, 0xf3, 0x3a, 0xb1, 0x7b, 0x85, 0x4f, 0xee, + 0x63, 0x5e, 0xed, 0x3f, 0xbe, 0xc1, 0x93, 0x52, 0xce, 0xb7, 0xba, 0xb6, 0xa0, 0xe3, 0xfd, 0xaf, 0x0c, 0x81, 0x11, + 0x7e, 0x68, 0x9b, 0x0e, 0x47, 0xf0, 0xda, 0x60, 0x3e, 0x64, 0x12, 0xd7, 0x2f, 0xfc, 0x93, 0xda, 0xe4, 0x98, 0xa3, + 0x6f, 0x8f, 0x56, 0x97, 0xf7, 0x2b, 0x46, 0xcc, 0x38, 0x17, 0x70, 0x30, 0xc2, 0x18, 0x33, 0xaa, 0xb3, 0xd9, 0x8a, + 0x99, 0xc6, 0x36, 0x7e, 0xc4, 0x6c, 0xb3, 0xc1, 0x7f, 0x4b, 0x8f, 0xf5, 0xfa, 0x90, 0x10, 0x6e, 0xe8, 0x15, 0xd1, + 0xeb, 0x35, 0x27, 0x84, 0x23, 0xfc, 0x8e, 0x93, 0x15, 0xf5, 0x13, 0x82, 0x93, 0x0d, 0xb6, 0x67, 0x6a, 0xa9, 0x0c, + 0x9c, 0x80, 0x5f, 0x58, 0xa9, 0x59, 0x99, 0x6a, 0x81, 0x4b, 0x36, 0x29, 0x60, 0x1c, 0x87, 0x5d, 0x3c, 0xa3, 0xea, + 0xe9, 0x8c, 0x8a, 0x29, 0xcb, 0xd3, 0xbf, 0xe5, 0x06, 0x33, 0x41, 0xa2, 0x09, 0x17, 0xb4, 0xe0, 0x7f, 0xb3, 0x3c, + 0x72, 0xe7, 0xc2, 0x47, 0x7d, 0xc0, 0xee, 0x34, 0x13, 0xb9, 0x3a, 0x78, 0xf9, 0xfe, 0xa7, 0x1f, 0xdd, 0x62, 0x36, + 0xce, 0x0a, 0xb4, 0x52, 0xcb, 0x05, 0x2b, 0x63, 0x84, 0xdd, 0x59, 0xf1, 0x9c, 0x1b, 0x3a, 0xf9, 0x13, 0x5d, 0xd8, + 0x14, 0xae, 0x3e, 0x2c, 0x72, 0xaa, 0xd9, 0x5b, 0x26, 0x72, 0x2e, 0xa6, 0xe4, 0xb0, 0x6b, 0xd3, 0x67, 0xd4, 0x65, + 0xe4, 0x55, 0xd2, 0xa7, 0x07, 0xcf, 0x0b, 0x33, 0xf7, 0xea, 0x73, 0x19, 0xa3, 0x8d, 0xd2, 0x54, 0xf3, 0xec, 0x80, + 0xe6, 0xf9, 0x2b, 0xc1, 0x35, 0x37, 0x23, 0x2c, 0x61, 0x89, 0x00, 0x57, 0x99, 0x3d, 0x35, 0xfc, 0xc8, 0x63, 0x84, + 0xe3, 0xd8, 0x9d, 0x05, 0x33, 0xe4, 0xd6, 0xec, 0xe8, 0xa8, 0xa6, 0xfc, 0x03, 0x96, 0xda, 0x4c, 0x32, 0x1c, 0xa1, + 0x64, 0xb1, 0x54, 0xb0, 0xd8, 0xbe, 0x0b, 0x38, 0x68, 0xe4, 0x58, 0xb1, 0xf2, 0x0b, 0xcb, 0x2b, 0x04, 0x51, 0x31, + 0x5a, 0x6d, 0xf5, 0xe1, 0xb6, 0x87, 0x26, 0xc3, 0x51, 0x3f, 0x24, 0xe1, 0xcc, 0x21, 0xbb, 0xe5, 0x54, 0x38, 0x53, + 0x15, 0x51, 0x89, 0xe1, 0x40, 0xad, 0x08, 0x8b, 0x22, 0x7e, 0x7e, 0x8b, 0x58, 0x00, 0x0f, 0x11, 0x52, 0x0e, 0x7f, + 0xe6, 0x3e, 0xff, 0x62, 0x0e, 0x0f, 0x85, 0x05, 0xc2, 0xda, 0x8e, 0x54, 0x21, 0xb4, 0x41, 0x58, 0xfb, 0xe1, 0x5a, + 0xa2, 0xe4, 0xf9, 0x22, 0x38, 0xb5, 0xc9, 0x3b, 0x6e, 0x8e, 0x6d, 0xa0, 0x6d, 0x54, 0xb3, 0xa3, 0xa3, 0x98, 0x25, + 0x15, 0x62, 0x90, 0xc3, 0xae, 0x5b, 0xa4, 0x00, 0x5a, 0x5f, 0x19, 0x37, 0xf4, 0x6c, 0x18, 0x9c, 0x43, 0x96, 0x08, + 0xf9, 0x38, 0xcb, 0x98, 0x52, 0xb2, 0x3c, 0x3a, 0x3a, 0x34, 0xe5, 0x2b, 0xce, 0x02, 0x16, 0xf1, 0xcd, 0xad, 0xa8, + 0x87, 0x80, 0xea, 0xd3, 0xd6, 0xf3, 0x4d, 0xa4, 0xe6, 0x9b, 0x3c, 0x13, 0x92, 0x46, 0x9f, 0x3e, 0x45, 0x2d, 0x8d, + 0x1d, 0x1c, 0xa6, 0xcc, 0x77, 0x7d, 0xff, 0x8c, 0x59, 0xb6, 0xd0, 0x30, 0x21, 0x3b, 0xa0, 0xd9, 0xcb, 0x0f, 0xc6, + 0xcd, 0x21, 0x61, 0x8d, 0x15, 0xda, 0x04, 0x2b, 0xba, 0xb7, 0x69, 0xc3, 0xdf, 0xd8, 0xa5, 0x5b, 0x4d, 0x0d, 0x4f, + 0x11, 0xac, 0xe3, 0x90, 0x8d, 0x36, 0xd8, 0xc0, 0xde, 0xcf, 0x46, 0x9a, 0x81, 0x0e, 0xf5, 0xa8, 0xef, 0xf2, 0x89, + 0xb2, 0x90, 0x2b, 0xd9, 0x5f, 0x4b, 0xa6, 0xb4, 0x45, 0xe4, 0x58, 0x63, 0x89, 0xe1, 0x8c, 0xda, 0x66, 0x3a, 0x1b, + 0x2c, 0xe9, 0xbe, 0xb1, 0xbd, 0x59, 0xc0, 0xd9, 0xa8, 0x00, 0xa9, 0xbf, 0x8d, 0x4f, 0x30, 0x56, 0x8d, 0xd6, 0xeb, + 0x77, 0xdc, 0xb7, 0x52, 0xaf, 0x65, 0xc5, 0xaf, 0x6d, 0x2d, 0x0a, 0x13, 0xc8, 0x1d, 0xce, 0x87, 0x5d, 0x37, 0x7e, + 0x31, 0x22, 0x87, 0x9d, 0x0a, 0x8b, 0x1d, 0x58, 0xed, 0x78, 0x2c, 0x14, 0xdf, 0xd8, 0xa6, 0x90, 0x39, 0xeb, 0x1b, + 0xf8, 0x92, 0xcc, 0x76, 0x70, 0x75, 0x46, 0x86, 0xc0, 0x75, 0x24, 0xb3, 0xd1, 0xd7, 0xf0, 0xc9, 0x53, 0x84, 0x58, + 0xef, 0xe6, 0xd5, 0x84, 0xe3, 0x4b, 0x93, 0x70, 0x6c, 0x4d, 0x23, 0x5a, 0x54, 0x55, 0xa2, 0x0a, 0xcd, 0xdc, 0x56, + 0xaf, 0xb3, 0xb0, 0x30, 0x83, 0xa9, 0xa7, 0x14, 0x34, 0xf1, 0x9a, 0xce, 0x99, 0x8a, 0x19, 0xc2, 0x5f, 0x2b, 0x60, + 0xf1, 0x13, 0x8a, 0x8c, 0x82, 0x33, 0x54, 0xc1, 0x19, 0x0a, 0xec, 0x2e, 0x30, 0x69, 0xcd, 0x2d, 0xa7, 0x30, 0x1b, + 0xaa, 0x51, 0xcd, 0xdb, 0x05, 0x93, 0x37, 0x87, 0xb3, 0x43, 0x70, 0x0f, 0x3f, 0x9b, 0x66, 0x81, 0x66, 0x58, 0x08, + 0x85, 0xf0, 0x61, 0x67, 0x7b, 0x25, 0x7d, 0xa9, 0x7a, 0x8e, 0xc3, 0x11, 0xac, 0x83, 0x39, 0x36, 0x12, 0xae, 0xcc, + 0xdf, 0xc6, 0x56, 0x03, 0xb0, 0xdd, 0x00, 0x66, 0x24, 0x93, 0x82, 0xea, 0xb8, 0x7b, 0xd2, 0x01, 0xc6, 0xf4, 0x0b, + 0x83, 0x53, 0x05, 0xa1, 0xdd, 0xa9, 0xb0, 0x64, 0x29, 0xd4, 0x8c, 0x4f, 0x74, 0xfc, 0xab, 0x30, 0x44, 0x85, 0x15, + 0x8a, 0x81, 0x84, 0x13, 0xb0, 0xc7, 0x86, 0xe0, 0xfc, 0x2a, 0xa0, 0x9f, 0x7e, 0x75, 0x10, 0xb9, 0x91, 0x1a, 0xc2, + 0x05, 0xe4, 0xa1, 0x66, 0xad, 0x6b, 0x32, 0x53, 0x31, 0x6e, 0xc0, 0x3d, 0x76, 0x07, 0xb6, 0xc5, 0xd4, 0x51, 0x03, + 0x11, 0x70, 0xb0, 0x22, 0x0d, 0x49, 0x84, 0x4b, 0xd4, 0x89, 0x96, 0x3f, 0xca, 0x5b, 0x56, 0x3e, 0xa5, 0x30, 0xf8, + 0xd4, 0x56, 0xdf, 0xd8, 0xa3, 0xc0, 0x50, 0x7c, 0xdd, 0xf7, 0xf8, 0xf2, 0xc9, 0x4c, 0xfc, 0x6d, 0x29, 0xe7, 0x5c, + 0x31, 0xe0, 0xdb, 0x2c, 0xfc, 0x05, 0x6c, 0x34, 0xb3, 0x23, 0xe1, 0xb8, 0x61, 0x15, 0x7e, 0x3d, 0xfe, 0xb1, 0x89, + 0x5f, 0x9f, 0x1e, 0x3c, 0x9f, 0x7a, 0x0a, 0xd8, 0xdc, 0xc7, 0x08, 0xc7, 0x4e, 0xbc, 0x08, 0x4e, 0xba, 0x64, 0x86, + 0xdc, 0x31, 0xbf, 0x5e, 0xeb, 0x40, 0x8c, 0x6b, 0x70, 0x8e, 0xcc, 0x6e, 0x1b, 0xb4, 0xa1, 0x79, 0x0e, 0x2c, 0x5e, + 0x29, 0x8b, 0x22, 0x38, 0xac, 0xb0, 0xe8, 0x57, 0xc7, 0xd3, 0xa7, 0x07, 0xcf, 0x6f, 0xbe, 0x75, 0x42, 0x41, 0x7e, + 0x78, 0x48, 0xf9, 0x81, 0x8a, 0x9c, 0x95, 0x20, 0x57, 0x06, 0xab, 0xe5, 0xce, 0xd9, 0xa7, 0x52, 0x08, 0x96, 0x69, + 0x96, 0x83, 0xd0, 0x22, 0x88, 0x4e, 0x66, 0x52, 0xe9, 0x2a, 0xb1, 0x1e, 0xbd, 0x08, 0x85, 0xd0, 0x24, 0xa3, 0x45, + 0x11, 0x5b, 0x01, 0x65, 0x2e, 0xbf, 0xb0, 0x3d, 0xa3, 0xee, 0x37, 0x86, 0x5c, 0x35, 0xc3, 0x82, 0x66, 0x58, 0xa2, + 0x16, 0x05, 0xcf, 0x58, 0x75, 0x78, 0xdd, 0x24, 0x5c, 0xe4, 0xec, 0x0e, 0xe8, 0x08, 0xba, 0xbe, 0xbe, 0xee, 0xe0, + 0x2e, 0xda, 0x58, 0x80, 0xaf, 0x76, 0x00, 0xfb, 0x8d, 0x63, 0xd3, 0x0a, 0xe2, 0xab, 0xbd, 0x64, 0x0d, 0x05, 0x67, + 0x25, 0xf7, 0x82, 0x96, 0x25, 0xcf, 0x08, 0xe7, 0xac, 0x60, 0x9a, 0x79, 0x72, 0x0e, 0xcc, 0xb4, 0xdd, 0xba, 0xef, + 0x2a, 0xf8, 0x55, 0xe8, 0xe4, 0x77, 0x99, 0x5f, 0x73, 0x55, 0x89, 0xee, 0xf5, 0xf2, 0xd4, 0xd0, 0x1e, 0x68, 0xbb, + 0x3c, 0x54, 0x6b, 0x9a, 0xcd, 0xac, 0xc4, 0x1e, 0xef, 0x4c, 0xa9, 0x6e, 0xc3, 0x91, 0xf6, 0x6a, 0x13, 0x7d, 0x5f, + 0xba, 0x61, 0xee, 0x03, 0xc1, 0x8d, 0x23, 0x0a, 0x0c, 0x84, 0x40, 0xbb, 0x6c, 0x4f, 0x69, 0x51, 0x8c, 0x69, 0xf6, + 0xb9, 0x89, 0xfd, 0x35, 0x1a, 0x90, 0x6d, 0x6a, 0x1c, 0x64, 0x05, 0x24, 0x2b, 0x9c, 0xb7, 0xa7, 0xd2, 0x8d, 0x8d, + 0x12, 0x1f, 0x76, 0x6a, 0xb4, 0x6f, 0x2e, 0xf4, 0x57, 0xb1, 0xdd, 0x8c, 0x48, 0xb8, 0x99, 0xc5, 0x40, 0x05, 0xfe, + 0x2d, 0xc6, 0x79, 0x7a, 0xe0, 0xf0, 0x0e, 0x04, 0x8f, 0xcd, 0xd6, 0x40, 0x34, 0x5a, 0x6d, 0x72, 0xae, 0xbe, 0x0e, + 0x81, 0xff, 0x57, 0x46, 0xf9, 0x2c, 0xe8, 0xe1, 0x3f, 0x1d, 0x68, 0x45, 0xe3, 0x1c, 0xe3, 0x5c, 0x8d, 0xcc, 0x31, + 0x14, 0x9e, 0xd0, 0xfc, 0x00, 0xcc, 0x8b, 0xc1, 0xf7, 0x37, 0x36, 0xcb, 0xf0, 0x65, 0x30, 0x0c, 0xd5, 0x0f, 0x19, + 0x8a, 0x06, 0x0a, 0x38, 0xa2, 0x2a, 0xcc, 0x99, 0x2b, 0x1b, 0xa2, 0xa4, 0xe3, 0xda, 0xad, 0x38, 0xee, 0x68, 0x6e, + 0x49, 0xe2, 0x38, 0x56, 0x20, 0xcd, 0x79, 0xfe, 0xbe, 0x9e, 0x85, 0xda, 0x99, 0x85, 0x4a, 0x02, 0x69, 0x0b, 0xd5, + 0xc8, 0x1c, 0x54, 0x4f, 0xb5, 0x40, 0x61, 0x29, 0x60, 0x59, 0x13, 0xa0, 0xd0, 0xa8, 0x22, 0xb8, 0x05, 0xd1, 0xb8, + 0x74, 0xa2, 0x8e, 0xc3, 0x35, 0x20, 0x19, 0x75, 0x15, 0x89, 0xec, 0xe6, 0x68, 0xc8, 0xbe, 0x12, 0x97, 0x68, 0x8b, + 0xbf, 0xdf, 0x6c, 0x1c, 0x94, 0x18, 0x72, 0xab, 0xd3, 0x60, 0x8c, 0x03, 0xb0, 0x60, 0x49, 0x1c, 0x33, 0x6c, 0x59, + 0x9f, 0x6d, 0xe0, 0x54, 0xed, 0x1e, 0x12, 0x22, 0x6b, 0xd8, 0x34, 0x98, 0x4a, 0xcf, 0x5d, 0x49, 0x84, 0xa9, 0x67, + 0x4b, 0xcb, 0x7a, 0xe2, 0x84, 0x44, 0x5e, 0x3b, 0x11, 0x0d, 0x56, 0x0d, 0xe1, 0x30, 0x0d, 0x8a, 0x6d, 0x52, 0x20, + 0xaa, 0xe5, 0x3e, 0x78, 0xef, 0xc3, 0x9a, 0x46, 0x3b, 0x01, 0xc4, 0xcb, 0x06, 0xc4, 0x03, 0xd0, 0x4a, 0x4b, 0xbc, + 0xe4, 0x88, 0xd0, 0x66, 0xe5, 0x98, 0xe1, 0xd2, 0x2e, 0xc4, 0x0e, 0x14, 0xb7, 0xd9, 0x4f, 0x83, 0x85, 0x20, 0xcb, + 0x2a, 0xe0, 0xef, 0xc2, 0x23, 0x22, 0x86, 0xc1, 0x8b, 0xf5, 0x7a, 0x07, 0xed, 0xf6, 0x72, 0xa1, 0x28, 0xa9, 0xa5, + 0xc3, 0xf5, 0xfa, 0x6f, 0x89, 0x62, 0xc7, 0xff, 0x62, 0x86, 0x06, 0x9e, 0xe8, 0x3e, 0xfe, 0x11, 0x4a, 0x19, 0x76, + 0xb4, 0x4e, 0xa9, 0x04, 0x87, 0x26, 0xd6, 0x36, 0x17, 0x4a, 0x07, 0x94, 0xfb, 0xe9, 0x0e, 0x01, 0x33, 0x89, 0xee, + 0xa4, 0xae, 0xa7, 0xfc, 0xd4, 0x35, 0x2d, 0x10, 0x42, 0xa9, 0x32, 0xb2, 0xcc, 0xe1, 0x3e, 0xf9, 0xf2, 0xe8, 0x48, + 0x05, 0x0d, 0x7d, 0xaa, 0x28, 0xc5, 0x9f, 0x31, 0x9c, 0xca, 0xea, 0x5e, 0x18, 0xf6, 0xe5, 0x4f, 0x7f, 0x0e, 0xed, + 0x48, 0xa7, 0x9d, 0x3e, 0x08, 0xe6, 0xf4, 0x96, 0x72, 0x7d, 0x50, 0xb5, 0x62, 0x05, 0xf3, 0x98, 0xa1, 0x95, 0xe3, + 0x36, 0x92, 0x92, 0x01, 0xff, 0x08, 0x64, 0xc1, 0x73, 0xd1, 0x16, 0xf1, 0xb3, 0x19, 0x03, 0x55, 0xb6, 0x67, 0x24, + 0x2a, 0xf1, 0xf0, 0xd0, 0x1d, 0x24, 0xae, 0xe1, 0xfd, 0x63, 0xdf, 0x6c, 0x57, 0x6f, 0x48, 0x03, 0x0b, 0x56, 0x4e, + 0x64, 0x39, 0xf7, 0x79, 0x9b, 0xad, 0x6f, 0x47, 0x1c, 0xf9, 0x24, 0xde, 0xdb, 0xb6, 0x13, 0x01, 0xfa, 0x5b, 0xb2, + 0x77, 0x2d, 0xb5, 0x37, 0x4e, 0xd3, 0xea, 0x00, 0xb6, 0x0a, 0x42, 0x8f, 0x99, 0x2a, 0x94, 0xf2, 0x9d, 0x7a, 0xb5, + 0x6f, 0x75, 0x27, 0x87, 0xdd, 0x7e, 0x25, 0xf9, 0x79, 0x6c, 0xe8, 0x5b, 0x1d, 0x87, 0x3b, 0x55, 0xe5, 0xb2, 0xc8, + 0xdd, 0x60, 0x05, 0xc2, 0xcc, 0xe1, 0xd1, 0x2d, 0x2f, 0x8a, 0x3a, 0xf5, 0x7f, 0x42, 0xda, 0x95, 0x23, 0xed, 0xd2, + 0x93, 0x76, 0x20, 0x15, 0x40, 0xda, 0x6d, 0x73, 0x75, 0x75, 0xb9, 0xb3, 0x3d, 0xa5, 0x25, 0xea, 0xca, 0x88, 0xd3, + 0xd0, 0xdf, 0xd2, 0x8f, 0x00, 0x55, 0xcc, 0xd7, 0xe7, 0xd8, 0xe9, 0x63, 0x40, 0x0c, 0xb4, 0x3a, 0x4d, 0x16, 0x6a, + 0x2a, 0x3e, 0xc7, 0x08, 0xab, 0x0d, 0xab, 0x30, 0xfb, 0xf1, 0x73, 0x50, 0xda, 0x05, 0xd3, 0x81, 0x73, 0xcc, 0x24, + 0xff, 0x8f, 0xf8, 0x28, 0x3f, 0x3b, 0xe1, 0x66, 0xa7, 0xfc, 0xec, 0x80, 0xd6, 0xd7, 0xb3, 0xcb, 0xbf, 0x4d, 0xed, + 0xcd, 0xf4, 0x44, 0x35, 0xbd, 0x7a, 0xbd, 0xd7, 0xeb, 0x78, 0x2b, 0x05, 0x34, 0xfa, 0x4e, 0x4a, 0x29, 0xab, 0xd6, + 0x81, 0x06, 0x84, 0x90, 0x81, 0x84, 0x8d, 0x9d, 0x74, 0x75, 0xca, 0xfd, 0xf8, 0xef, 0xf4, 0x3c, 0x46, 0x71, 0x6f, + 0xeb, 0x3f, 0x95, 0xf3, 0x05, 0x30, 0x64, 0x5b, 0x28, 0x3d, 0x65, 0xae, 0xc3, 0x3a, 0x7f, 0xb3, 0x27, 0xad, 0x51, + 0xc7, 0xec, 0xc7, 0x06, 0x36, 0x55, 0x52, 0xf3, 0x61, 0x67, 0xb3, 0xac, 0x92, 0x2a, 0xc2, 0xb1, 0x4f, 0xb7, 0xf2, + 0x74, 0x5b, 0x33, 0xe3, 0x33, 0xde, 0xc4, 0xc2, 0xd2, 0x61, 0x01, 0xb4, 0x2e, 0x20, 0x3f, 0x1e, 0xdd, 0xc3, 0xf5, + 0xdf, 0xd4, 0xc0, 0x59, 0x6d, 0xb6, 0xc0, 0xb7, 0xda, 0x6c, 0x3e, 0x6a, 0x27, 0x69, 0xe3, 0x8f, 0x7b, 0xe4, 0xde, + 0x0a, 0x7a, 0x75, 0xa6, 0x93, 0x19, 0x87, 0x23, 0x48, 0xdb, 0x61, 0x21, 0xc9, 0x6a, 0x2e, 0x73, 0x96, 0x46, 0x72, + 0xc1, 0x44, 0xb4, 0x01, 0x3d, 0xab, 0x43, 0x80, 0x7f, 0x89, 0x78, 0xf5, 0xae, 0xa9, 0x6f, 0x4d, 0x3f, 0xea, 0x0d, + 0xa8, 0xc2, 0x7e, 0xe4, 0x7b, 0x94, 0xb1, 0x1f, 0x59, 0xa9, 0x0c, 0x4f, 0x5a, 0xb1, 0xb7, 0x3f, 0xf2, 0xfa, 0x80, + 0xfa, 0x91, 0xa7, 0x5f, 0xaf, 0x52, 0x0b, 0x24, 0x51, 0x37, 0xb9, 0x48, 0x4e, 0x23, 0x64, 0x34, 0xc6, 0x2f, 0xbc, + 0xc6, 0x78, 0x59, 0x69, 0x8c, 0x5f, 0x6a, 0xb2, 0xdc, 0xd2, 0x18, 0xff, 0x20, 0xc8, 0x4b, 0x3d, 0x78, 0xe9, 0xb5, + 0xe9, 0x6f, 0x65, 0xc1, 0xb3, 0xfb, 0x38, 0x2a, 0xb8, 0x6e, 0xc3, 0x6d, 0x62, 0x84, 0x57, 0x36, 0x03, 0x54, 0x8d, + 0x46, 0xdf, 0xbd, 0xf1, 0xf2, 0x1f, 0x16, 0x82, 0x44, 0x0f, 0x0a, 0xae, 0x1f, 0x44, 0x78, 0xa6, 0xc9, 0x1f, 0xf0, + 0xeb, 0xc1, 0x2a, 0xfe, 0x89, 0xea, 0x59, 0x52, 0x52, 0x91, 0xcb, 0x79, 0x8c, 0x5a, 0x51, 0x84, 0x12, 0x65, 0x84, + 0x90, 0x47, 0x68, 0xf3, 0xe0, 0x0f, 0xfc, 0xa7, 0x24, 0xd1, 0x20, 0x6a, 0xcd, 0x34, 0x66, 0x94, 0xfc, 0x71, 0xf5, + 0x60, 0xf5, 0xa7, 0xdc, 0x5c, 0xff, 0x81, 0x9f, 0xeb, 0x4a, 0xad, 0x8f, 0xef, 0x18, 0x89, 0x11, 0xb9, 0x7e, 0xee, + 0x87, 0xf4, 0x54, 0xce, 0xad, 0x82, 0x3f, 0x42, 0xf8, 0x0b, 0xe8, 0x75, 0xaf, 0x79, 0x4d, 0x84, 0xdc, 0x1d, 0xcc, + 0x21, 0x89, 0xa4, 0x51, 0x1e, 0x44, 0x47, 0x47, 0x41, 0x5a, 0xc5, 0x42, 0xe0, 0x27, 0x92, 0x34, 0x44, 0x75, 0xcc, + 0x29, 0xb4, 0xf4, 0x44, 0xc6, 0x1c, 0xf9, 0x66, 0x62, 0xaf, 0xa9, 0x76, 0x3b, 0x96, 0x0f, 0xad, 0xee, 0x21, 0xe1, + 0x9a, 0x95, 0x54, 0xcb, 0x72, 0x84, 0x42, 0xb6, 0x04, 0xbf, 0xe6, 0xe4, 0x8f, 0xe1, 0xc1, 0xff, 0xf1, 0x7f, 0xfe, + 0x3e, 0xf9, 0xbd, 0x1c, 0xfd, 0x81, 0x05, 0x23, 0x27, 0x57, 0xf1, 0x20, 0x8d, 0x0f, 0xdb, 0xed, 0xf5, 0xef, 0x27, + 0xc3, 0xff, 0xa6, 0xed, 0xbf, 0x1f, 0xb7, 0x7f, 0x1b, 0xa1, 0x75, 0xfc, 0xfb, 0xc9, 0x60, 0xe8, 0xbe, 0x86, 0xff, + 0x7d, 0xfd, 0xbb, 0x1a, 0x1d, 0xdb, 0xc4, 0x07, 0x08, 0x9d, 0x4c, 0xf1, 0x3f, 0x05, 0x39, 0x69, 0xb7, 0xaf, 0x4f, + 0xa6, 0xf8, 0x67, 0x41, 0x4e, 0xe0, 0xef, 0xbd, 0x26, 0xef, 0xd8, 0xf4, 0xf9, 0xdd, 0x22, 0xfe, 0xe3, 0x7a, 0xfd, + 0x60, 0xf5, 0x9a, 0x6f, 0xa0, 0xdd, 0xe1, 0x7f, 0xff, 0xfe, 0xbb, 0x8a, 0xfe, 0x71, 0x4d, 0x4e, 0x46, 0x2d, 0x14, + 0x9b, 0xe4, 0x63, 0x62, 0xff, 0xc4, 0x83, 0x74, 0xf8, 0xdf, 0x6e, 0x28, 0xd1, 0x3f, 0x7e, 0xff, 0xe3, 0xea, 0x9a, + 0x8c, 0xd6, 0x71, 0xb4, 0xfe, 0x07, 0x5a, 0x23, 0xb4, 0x7e, 0x80, 0xfe, 0xc0, 0xd1, 0x34, 0x42, 0xf8, 0x37, 0x41, + 0x4e, 0xfe, 0x71, 0x32, 0xc5, 0xdf, 0x0b, 0x72, 0x12, 0x9d, 0x4c, 0xf1, 0x47, 0x49, 0x4e, 0xfe, 0x3b, 0x1e, 0xa4, + 0x56, 0x09, 0xb7, 0x36, 0xea, 0x8f, 0x35, 0xdc, 0x84, 0xd0, 0x92, 0xd1, 0xb5, 0xe6, 0xba, 0x60, 0xe8, 0xc1, 0x09, + 0xc7, 0x2f, 0x25, 0x00, 0x2b, 0xd6, 0xa0, 0xa4, 0x31, 0x97, 0xb0, 0xab, 0x4f, 0xb0, 0xf0, 0x80, 0x41, 0x0f, 0x52, + 0x8e, 0xad, 0x9e, 0x40, 0xa5, 0xda, 0xde, 0xde, 0x2a, 0xb8, 0xbe, 0xc5, 0x37, 0xe4, 0xa5, 0x8c, 0xbb, 0x08, 0x0b, + 0x0a, 0x3f, 0x7a, 0x08, 0x7f, 0xd0, 0xee, 0xc2, 0x13, 0xb6, 0xb9, 0xc5, 0x30, 0x21, 0x2d, 0x3f, 0x13, 0x21, 0xfc, + 0x7c, 0x4f, 0xa6, 0x9e, 0x81, 0xfa, 0x01, 0x61, 0xad, 0xc2, 0xeb, 0x51, 0xfc, 0x54, 0x93, 0x0a, 0x39, 0xde, 0x97, + 0x8c, 0xfd, 0x42, 0x8b, 0xcf, 0xac, 0x8c, 0x9f, 0x6b, 0xdc, 0xed, 0x3d, 0xc2, 0x46, 0x55, 0x7d, 0xd8, 0x45, 0xfd, + 0xea, 0x76, 0xeb, 0x83, 0xb4, 0xf7, 0x09, 0x70, 0x0a, 0x37, 0xf5, 0x35, 0xb0, 0xf6, 0x87, 0x7c, 0x47, 0xa9, 0x55, + 0xd2, 0xdb, 0x08, 0x35, 0xaf, 0x52, 0xb9, 0xf8, 0x42, 0x0b, 0x9e, 0x1f, 0x68, 0x36, 0x5f, 0x14, 0x54, 0xb3, 0x03, + 0x37, 0xe7, 0x03, 0x0a, 0x0d, 0x45, 0x15, 0x4f, 0xf1, 0x83, 0xa8, 0x37, 0xed, 0x0f, 0x22, 0xa9, 0xf7, 0x4e, 0x0c, + 0xf7, 0x59, 0x8e, 0x2f, 0x51, 0xb4, 0xba, 0x2e, 0xdb, 0xbe, 0x11, 0x6c, 0x77, 0x41, 0x59, 0x36, 0x32, 0xe7, 0xb7, + 0xc2, 0x70, 0xbf, 0x49, 0x48, 0x6f, 0x10, 0x5d, 0xa9, 0x2f, 0xd3, 0xeb, 0x08, 0x6e, 0x72, 0x4a, 0x22, 0x98, 0x51, + 0x1e, 0x41, 0x09, 0x4a, 0x3a, 0x7d, 0x7a, 0xc5, 0xfa, 0xb4, 0xd5, 0xf2, 0x6c, 0x76, 0x46, 0xf8, 0x90, 0xda, 0xfa, + 0x05, 0x9e, 0xe1, 0x9c, 0xb4, 0xbb, 0x78, 0x49, 0x3a, 0xa6, 0x4a, 0x7f, 0x79, 0x95, 0xb9, 0x7e, 0x8e, 0x8e, 0xe2, + 0x32, 0x29, 0xa8, 0xd2, 0xaf, 0x40, 0x23, 0x40, 0x96, 0x78, 0x46, 0xca, 0x84, 0xdd, 0xb1, 0x2c, 0xce, 0x10, 0x9e, + 0x39, 0x1a, 0x84, 0xfa, 0x68, 0x49, 0x82, 0x62, 0x20, 0x67, 0x10, 0xc1, 0x06, 0xb3, 0x61, 0x77, 0x44, 0x08, 0x89, + 0x0e, 0xdb, 0xed, 0x68, 0x50, 0x92, 0x7f, 0x8a, 0x14, 0x52, 0x02, 0x76, 0x9a, 0xfc, 0x0c, 0x49, 0xbd, 0x20, 0x29, + 0xfe, 0x28, 0x13, 0xcd, 0x94, 0x8e, 0x21, 0x19, 0x94, 0x04, 0xca, 0x63, 0x78, 0x74, 0x75, 0x12, 0xb5, 0x20, 0xd5, + 0xa0, 0x28, 0xc2, 0x25, 0xb9, 0xd7, 0x28, 0x9d, 0x0d, 0x4f, 0x47, 0xe1, 0x19, 0x61, 0x53, 0xa1, 0xff, 0x7b, 0x3d, + 0x98, 0x0d, 0x3b, 0xa6, 0xff, 0xeb, 0x68, 0x10, 0x97, 0x44, 0x59, 0x36, 0x6e, 0xa0, 0x52, 0xc1, 0xcc, 0x7c, 0x51, + 0xea, 0x06, 0xe8, 0xfa, 0xce, 0x49, 0xbb, 0x97, 0xc6, 0x79, 0x38, 0x93, 0x36, 0x74, 0xe8, 0x40, 0x81, 0x0b, 0x02, + 0xe5, 0x71, 0x49, 0xa0, 0xd3, 0xba, 0xda, 0xbd, 0x4e, 0x5d, 0xc2, 0x3f, 0xa2, 0x7f, 0x0c, 0xbe, 0x17, 0xe9, 0x6f, + 0xc2, 0x8e, 0xe0, 0x7b, 0xb1, 0x5e, 0xc3, 0xdf, 0xdf, 0xc4, 0x00, 0x86, 0x65, 0xd2, 0xfe, 0xe9, 0xd2, 0x7e, 0x86, + 0x34, 0xc1, 0x52, 0x33, 0x60, 0xac, 0x2a, 0x7e, 0xcc, 0x2e, 0xce, 0x84, 0xd8, 0x19, 0x1c, 0x1d, 0xf1, 0x21, 0x6d, + 0x75, 0x47, 0x70, 0x23, 0x50, 0x6a, 0xf5, 0x0b, 0xd7, 0xb3, 0x38, 0x3a, 0xb9, 0x8e, 0xd0, 0x20, 0x3a, 0x80, 0x55, + 0xee, 0xcb, 0x16, 0x71, 0xb0, 0xce, 0x5a, 0x8c, 0xa6, 0xf9, 0x35, 0xe9, 0x0c, 0x62, 0x61, 0x89, 0x7c, 0x81, 0x70, + 0xe6, 0x68, 0x6a, 0x07, 0xe7, 0xa8, 0x25, 0x44, 0xcb, 0x7f, 0xe7, 0xa8, 0x35, 0xd3, 0xad, 0x09, 0x4a, 0x33, 0xf8, + 0x1b, 0xe7, 0x84, 0x90, 0x76, 0xaf, 0xaa, 0xe8, 0x0f, 0x4b, 0x8a, 0xd2, 0x89, 0x57, 0x8f, 0x0e, 0xcd, 0xe6, 0x90, + 0xad, 0x98, 0x0f, 0xd9, 0x68, 0xbd, 0x8e, 0xae, 0x06, 0xd7, 0x11, 0x6a, 0xc5, 0x1e, 0xed, 0x4e, 0x3c, 0xde, 0x21, + 0x84, 0xc5, 0x68, 0xe3, 0x6e, 0xa0, 0x6e, 0x59, 0xe3, 0xb6, 0x69, 0x55, 0xef, 0xff, 0x80, 0x2c, 0xb0, 0x4d, 0x25, + 0xf7, 0x58, 0xfe, 0x76, 0x01, 0x53, 0xf5, 0xb8, 0x2d, 0x49, 0x07, 0x97, 0xc4, 0xab, 0xbb, 0x29, 0xd1, 0x35, 0xfe, + 0x67, 0xa4, 0x2e, 0x8e, 0x87, 0x05, 0x9e, 0x8d, 0x88, 0xa2, 0x46, 0x7e, 0xe9, 0x7b, 0x65, 0x3a, 0x2b, 0xc8, 0x2d, + 0xdb, 0xba, 0xff, 0x2d, 0xe0, 0x4e, 0xe6, 0xa9, 0x4e, 0xb2, 0x65, 0x59, 0x32, 0xa1, 0x5f, 0xcb, 0xdc, 0x31, 0x76, + 0xac, 0x00, 0xd9, 0x0a, 0x2e, 0x76, 0x31, 0x70, 0x75, 0x3d, 0xbf, 0x53, 0xf2, 0x9d, 0xec, 0x25, 0xc9, 0x2d, 0xc3, + 0x65, 0xae, 0x7b, 0xfb, 0x4b, 0x27, 0x4a, 0xc7, 0x08, 0xe7, 0xee, 0x1e, 0x38, 0x4e, 0x92, 0x64, 0x99, 0x64, 0x90, + 0x0d, 0x1d, 0x28, 0xb4, 0x31, 0xfb, 0x2a, 0x56, 0xe4, 0xa9, 0x4e, 0x04, 0xbb, 0x33, 0xdd, 0xc6, 0xa8, 0x3e, 0xc4, + 0xfd, 0x7e, 0xbb, 0xa2, 0x7d, 0x43, 0x80, 0x54, 0x22, 0x64, 0xce, 0x00, 0x42, 0x70, 0xf7, 0xef, 0x92, 0x66, 0x54, + 0x85, 0x37, 0x5b, 0xf5, 0x00, 0x87, 0xa1, 0xca, 0x7b, 0x09, 0x7a, 0x62, 0xc3, 0x9e, 0x55, 0x85, 0xad, 0xf2, 0x1c, + 0x21, 0x3e, 0x89, 0x97, 0x09, 0xdc, 0x08, 0x1a, 0x4c, 0x12, 0x02, 0xad, 0xd7, 0xcb, 0x10, 0xb7, 0x66, 0xb5, 0x62, + 0x7a, 0x42, 0x66, 0xc3, 0xb2, 0xd5, 0x32, 0xca, 0xeb, 0xdc, 0xe2, 0xc5, 0x12, 0xe1, 0x49, 0xb5, 0xd7, 0x7c, 0xb9, + 0x05, 0x69, 0x76, 0x15, 0x4f, 0x9a, 0x4a, 0xe0, 0x96, 0x10, 0xc8, 0xe8, 0x17, 0x35, 0xb4, 0x8e, 0xa7, 0xe4, 0x24, + 0x1e, 0x26, 0x83, 0xff, 0x6b, 0x84, 0x06, 0x71, 0x72, 0x8c, 0x4e, 0x2c, 0x2d, 0x99, 0xa0, 0x7e, 0x66, 0xfb, 0x58, + 0x99, 0xdb, 0xcf, 0x2e, 0x36, 0x0a, 0xc8, 0x54, 0x62, 0x41, 0xe7, 0x2c, 0x9d, 0xc2, 0xae, 0xf7, 0xc8, 0xb3, 0xc0, + 0x80, 0x4c, 0xe9, 0xd4, 0xd1, 0x96, 0x24, 0x1a, 0x94, 0xb4, 0xfa, 0x1a, 0x44, 0x83, 0xac, 0xfe, 0xfa, 0xbf, 0xa2, + 0x41, 0x41, 0xd3, 0xa7, 0x7c, 0xe3, 0x94, 0xe4, 0x8d, 0x3e, 0x2e, 0x7c, 0x1f, 0x1b, 0xbb, 0x38, 0x01, 0xf0, 0x72, + 0xb4, 0xab, 0x1d, 0x59, 0xa2, 0x0d, 0x9f, 0x54, 0xd4, 0x49, 0x25, 0x9a, 0x4e, 0x01, 0xaa, 0xc1, 0x22, 0xa8, 0xd0, + 0x36, 0x20, 0x98, 0x32, 0x60, 0x8b, 0x47, 0x5a, 0x80, 0xe6, 0xf2, 0xba, 0x83, 0x56, 0x8d, 0xc2, 0x8e, 0xb3, 0x6a, + 0xde, 0xc5, 0x57, 0xc4, 0x7b, 0x02, 0x54, 0xf9, 0x6a, 0xd9, 0x9f, 0xb4, 0x5a, 0x48, 0x79, 0xfc, 0xca, 0x87, 0x93, + 0x11, 0xbe, 0x03, 0x14, 0xc2, 0x0d, 0x8c, 0xc2, 0x8d, 0x39, 0xf6, 0xdc, 0x1c, 0x5b, 0x2d, 0xb9, 0x41, 0xfd, 0xa0, + 0xf2, 0xd2, 0x55, 0xde, 0x6c, 0x2c, 0x64, 0xb6, 0x31, 0xee, 0x12, 0x99, 0x14, 0x30, 0x04, 0x23, 0x84, 0xfc, 0x29, + 0xd1, 0xde, 0x66, 0xa1, 0x51, 0xa8, 0x6e, 0x76, 0x2f, 0x50, 0x54, 0x7b, 0x7a, 0xc4, 0x00, 0x0b, 0xa8, 0x5a, 0xa9, + 0x91, 0x67, 0x1a, 0xe7, 0xad, 0xae, 0x41, 0xf7, 0x76, 0xb7, 0xdf, 0x6c, 0xec, 0x61, 0xdd, 0x18, 0xce, 0x5b, 0x64, + 0x56, 0xef, 0xf0, 0x8d, 0x6c, 0xb5, 0x36, 0xcd, 0xfb, 0x52, 0xbf, 0x89, 0x1b, 0xf7, 0x17, 0xcf, 0x77, 0x4c, 0x3c, + 0xfc, 0xe9, 0x5b, 0x9f, 0xb7, 0x22, 0xe1, 0x42, 0xb0, 0x12, 0x4e, 0x58, 0xa2, 0xb1, 0xd8, 0x6c, 0xaa, 0x53, 0xff, + 0x17, 0x6d, 0x6d, 0xc6, 0x08, 0x07, 0x3a, 0x64, 0xa4, 0x36, 0x2c, 0x71, 0x89, 0xa9, 0xa1, 0x22, 0x84, 0x90, 0x0f, + 0xda, 0x9b, 0xc7, 0x68, 0x43, 0x92, 0x32, 0x12, 0x9c, 0xdd, 0xb1, 0x22, 0x2c, 0xf9, 0xf4, 0xe0, 0xa9, 0xfc, 0xa6, + 0x48, 0x37, 0x14, 0xa3, 0xd4, 0x14, 0x2b, 0x1c, 0x21, 0x2b, 0xc8, 0x17, 0x90, 0x73, 0xaa, 0x0b, 0x96, 0xc4, 0x10, + 0xc4, 0x67, 0xbc, 0x64, 0x86, 0x71, 0x7f, 0xe0, 0xe5, 0xc6, 0xac, 0xc9, 0x69, 0x66, 0xa1, 0xf6, 0x07, 0xa0, 0x59, + 0x80, 0x72, 0x48, 0x92, 0x9d, 0x62, 0x9f, 0x1e, 0x3c, 0x7e, 0xb3, 0x4f, 0x86, 0x5e, 0xaf, 0x9d, 0xf4, 0x9c, 0x01, + 0xeb, 0x83, 0x8b, 0x7a, 0xa8, 0x99, 0xfb, 0x91, 0xc6, 0x99, 0x61, 0xa2, 0x8a, 0x98, 0x03, 0x32, 0x7d, 0x7a, 0xf0, + 0xf8, 0x7d, 0xcc, 0x8d, 0x6e, 0x0a, 0xe1, 0x70, 0xde, 0x71, 0x49, 0x62, 0x4a, 0x18, 0xb2, 0x93, 0xaf, 0xe8, 0x58, + 0x19, 0x9c, 0xee, 0x29, 0x35, 0x99, 0x20, 0x76, 0x0c, 0xc5, 0x88, 0x64, 0x0e, 0x04, 0x24, 0x43, 0x38, 0x6b, 0xc8, + 0x75, 0xc4, 0xac, 0x81, 0xe9, 0xec, 0x06, 0x16, 0x23, 0xb1, 0xec, 0x21, 0xc2, 0x99, 0xe9, 0x56, 0x6f, 0xec, 0x71, + 0x22, 0xe9, 0xb6, 0xa1, 0x5b, 0x2d, 0xcf, 0x7e, 0x04, 0xc1, 0xcb, 0x7f, 0xbc, 0x76, 0x6d, 0x57, 0x09, 0xcf, 0xbc, + 0x45, 0xda, 0xa7, 0x07, 0x8f, 0x7f, 0x72, 0x46, 0x69, 0x0b, 0xea, 0xc9, 0xff, 0x8e, 0x8c, 0xfa, 0xf8, 0xa7, 0xa4, + 0xce, 0x35, 0x85, 0x3f, 0x3d, 0x78, 0xfc, 0x61, 0x5f, 0x31, 0x48, 0xdf, 0x2c, 0x6b, 0x25, 0x81, 0x19, 0xdf, 0x8a, + 0x15, 0xe9, 0xca, 0x9d, 0x15, 0xa9, 0xd8, 0x60, 0x73, 0x42, 0xa5, 0x6a, 0x53, 0xe9, 0x56, 0x9e, 0x61, 0x49, 0xcc, + 0x55, 0x52, 0x73, 0xd9, 0x1c, 0x1a, 0x73, 0x29, 0x6e, 0x32, 0xb9, 0x60, 0x5f, 0xb9, 0x5f, 0x7a, 0xae, 0x51, 0xc2, + 0xe7, 0x60, 0x88, 0x63, 0xc6, 0x2e, 0xf0, 0x61, 0x07, 0xf5, 0xb7, 0xce, 0x33, 0x69, 0x10, 0xb5, 0x6c, 0x1e, 0x36, + 0x98, 0x92, 0x0e, 0xce, 0x48, 0x07, 0x17, 0x44, 0x0d, 0x3b, 0xf6, 0xc4, 0xe8, 0x17, 0x55, 0xd3, 0xf6, 0xdc, 0x81, + 0xed, 0x5e, 0xd8, 0x7d, 0x6b, 0x0f, 0xe5, 0x59, 0xbf, 0x30, 0xfa, 0x4b, 0x73, 0xd0, 0xcf, 0x0c, 0x6a, 0xbc, 0x62, + 0x71, 0x89, 0x4b, 0xd3, 0xf2, 0x0d, 0x1f, 0x17, 0x60, 0xa7, 0x02, 0x33, 0xc3, 0x1a, 0xa5, 0x55, 0xd9, 0xae, 0x2b, + 0x5b, 0x24, 0x66, 0xad, 0x4a, 0x5c, 0x24, 0x40, 0xca, 0x71, 0xe1, 0xec, 0x7a, 0xd4, 0x6e, 0x95, 0x8b, 0xa3, 0xa3, + 0xd8, 0x56, 0x9a, 0xd1, 0xb8, 0xf4, 0xf9, 0xf5, 0x0d, 0xe0, 0x47, 0x4b, 0x35, 0x66, 0xc8, 0x4c, 0xa0, 0xd5, 0xca, + 0x46, 0x1b, 0x7a, 0x48, 0x48, 0x5c, 0x34, 0xa1, 0xe8, 0x47, 0x6f, 0x98, 0xc1, 0x2d, 0x00, 0xb4, 0x5a, 0xd5, 0x75, + 0xef, 0x16, 0xc4, 0x9e, 0x6b, 0x2c, 0x37, 0x5f, 0xe2, 0xca, 0x9a, 0xa8, 0xb3, 0x63, 0x87, 0xe5, 0x47, 0x81, 0x44, + 0x88, 0xbb, 0xc2, 0xcf, 0x27, 0xd8, 0x1a, 0x02, 0xca, 0xbd, 0x72, 0x36, 0x10, 0xd8, 0x58, 0x6d, 0xb9, 0x42, 0x9e, + 0xb4, 0xf5, 0x50, 0xea, 0x0b, 0xc1, 0x05, 0x17, 0x14, 0x6a, 0x6d, 0x1c, 0x96, 0xbf, 0x62, 0xbb, 0xe6, 0x9c, 0x58, + 0x21, 0xa7, 0x2d, 0x33, 0xc3, 0x30, 0x00, 0xeb, 0x55, 0x80, 0x79, 0x49, 0x9e, 0x7f, 0x1d, 0xf5, 0x1f, 0x07, 0xa8, + 0xff, 0x84, 0xb0, 0x60, 0x1b, 0x58, 0x5d, 0x49, 0x22, 0x9d, 0x82, 0x42, 0xf9, 0xac, 0xa7, 0x0b, 0x02, 0xda, 0xb8, + 0x26, 0x54, 0x1b, 0x57, 0x94, 0x5f, 0xa1, 0x2c, 0xe1, 0x4e, 0x31, 0xfa, 0x4c, 0xec, 0xef, 0x93, 0xe3, 0xfa, 0x82, + 0x0e, 0xba, 0xde, 0xa7, 0x1c, 0x0c, 0x49, 0xe1, 0xe3, 0x0f, 0xdf, 0xbe, 0x5b, 0x7d, 0xba, 0xd8, 0xdd, 0xc1, 0x81, + 0x59, 0x29, 0xcc, 0x3a, 0xd8, 0xc0, 0x4d, 0x23, 0x53, 0xe8, 0xbf, 0xba, 0x13, 0x6f, 0x52, 0xa1, 0xad, 0xcd, 0xe8, + 0x8f, 0x43, 0x18, 0x6d, 0xb7, 0x6b, 0x4a, 0xb0, 0xa0, 0x59, 0xa0, 0x4b, 0xd6, 0xb8, 0x95, 0x96, 0x5f, 0x21, 0x23, + 0x8f, 0x4d, 0x01, 0x26, 0xf2, 0xfd, 0xd9, 0x4f, 0x36, 0x0e, 0x4f, 0xec, 0xd0, 0xd0, 0xca, 0x10, 0x42, 0x8b, 0xf7, + 0x80, 0x39, 0xf6, 0x88, 0x00, 0x10, 0x3d, 0x37, 0x90, 0xaa, 0x41, 0x16, 0x45, 0xb5, 0x22, 0xff, 0xe5, 0x21, 0x21, + 0xcf, 0x6b, 0x45, 0xe6, 0xbb, 0xda, 0x98, 0x0b, 0x10, 0x03, 0xa5, 0x70, 0x91, 0x50, 0x25, 0xd8, 0xcb, 0xd0, 0x0f, + 0xda, 0x97, 0x37, 0xd2, 0x66, 0x52, 0x73, 0xe3, 0xc1, 0x4d, 0xa9, 0x51, 0xf1, 0xd9, 0x7c, 0x0f, 0x89, 0xad, 0xdc, + 0x07, 0x90, 0xcb, 0xa9, 0x19, 0x24, 0x7c, 0xbf, 0x37, 0xa5, 0x7d, 0xbb, 0x9b, 0xcf, 0xdb, 0x16, 0x31, 0x5b, 0xeb, + 0x92, 0x70, 0xa1, 0x58, 0xa9, 0x9f, 0xb0, 0x89, 0x2c, 0xe1, 0xfe, 0xa3, 0x02, 0x0b, 0xda, 0x3c, 0x08, 0x74, 0x80, + 0x66, 0x82, 0xc1, 0xa5, 0xc3, 0xd6, 0x0c, 0xcd, 0xaf, 0xcf, 0xe6, 0x0e, 0xfc, 0xd3, 0x76, 0xad, 0xe7, 0x47, 0x47, + 0x5f, 0x58, 0x0d, 0x28, 0x37, 0x4c, 0x33, 0x8c, 0x80, 0x78, 0x59, 0x2e, 0xc7, 0xdd, 0x0c, 0x3f, 0x88, 0x6b, 0x95, + 0x81, 0x27, 0x1c, 0x21, 0x11, 0x7a, 0x49, 0xf4, 0x66, 0xba, 0x4d, 0xef, 0x9d, 0x36, 0x43, 0x84, 0x62, 0x0d, 0x90, + 0x7b, 0x90, 0xcb, 0xad, 0x92, 0x49, 0xd5, 0xb6, 0xb6, 0xd5, 0x20, 0x9e, 0x02, 0xb8, 0x62, 0x23, 0xa4, 0x04, 0x68, + 0xb8, 0x5f, 0x68, 0xf9, 0x20, 0x81, 0xfd, 0xc7, 0x2a, 0x01, 0x91, 0x16, 0x35, 0x36, 0x2e, 0x42, 0xd8, 0x9a, 0xfa, + 0x04, 0xc6, 0x09, 0x8f, 0x5f, 0xee, 0xd3, 0x50, 0x7b, 0xd4, 0x66, 0xe6, 0x0c, 0x82, 0x12, 0x12, 0x55, 0x15, 0x92, + 0x2f, 0xb1, 0x70, 0xdc, 0x9c, 0xbf, 0x87, 0x03, 0x52, 0x2c, 0x69, 0x6c, 0xef, 0xb6, 0xe0, 0xf8, 0x28, 0x93, 0x65, + 0xdc, 0xe8, 0xba, 0x5f, 0x9a, 0x6a, 0xd8, 0x81, 0x8e, 0x86, 0x70, 0x2a, 0xcd, 0x3d, 0xe1, 0xd3, 0x9a, 0xa4, 0x6a, + 0x67, 0x01, 0xe5, 0x89, 0x61, 0x6d, 0x9a, 0x12, 0xcc, 0x5f, 0x3b, 0xf3, 0xb5, 0xea, 0x98, 0x60, 0x66, 0x18, 0xb7, + 0x76, 0x15, 0xd8, 0x06, 0x70, 0x6c, 0xf5, 0x44, 0x06, 0x8b, 0xea, 0x95, 0xe2, 0xa6, 0xd3, 0x80, 0x09, 0x78, 0x07, + 0xd6, 0x33, 0xdb, 0x5b, 0xff, 0xa5, 0x39, 0x18, 0x05, 0x56, 0x0d, 0x02, 0x2f, 0x0d, 0x81, 0x47, 0xc0, 0xb8, 0x79, + 0xd3, 0xf2, 0x81, 0x33, 0xa2, 0x11, 0xfe, 0xc4, 0x73, 0x78, 0x66, 0x59, 0xee, 0x9d, 0x8f, 0xad, 0x15, 0x49, 0x05, + 0x01, 0xdb, 0x22, 0xec, 0x88, 0xbc, 0x44, 0x58, 0xb5, 0x5a, 0x7d, 0x75, 0xc5, 0x6a, 0xad, 0x4a, 0x3d, 0x4c, 0x01, + 0xb7, 0xc4, 0x80, 0xf7, 0x8d, 0x13, 0x15, 0x0c, 0x09, 0xbc, 0xf5, 0xb7, 0x02, 0xf5, 0xfd, 0xe3, 0x77, 0x71, 0x48, + 0xdf, 0xc2, 0xb2, 0xd5, 0x45, 0x2c, 0x4c, 0x29, 0xae, 0xef, 0x70, 0xde, 0x7e, 0xdb, 0x6c, 0x04, 0xc6, 0x7d, 0xd8, + 0xc5, 0x60, 0xe3, 0x86, 0xfa, 0xda, 0x92, 0x86, 0x6a, 0x13, 0xf6, 0x51, 0x6d, 0xef, 0x18, 0x76, 0xd6, 0xd7, 0xb5, + 0xb4, 0xab, 0x89, 0xda, 0x6c, 0x14, 0xab, 0x8d, 0x06, 0xb6, 0x0c, 0x3b, 0xcd, 0x31, 0xb3, 0xab, 0xc0, 0x7f, 0xba, + 0x20, 0x1a, 0x07, 0xc8, 0xfa, 0xf6, 0x6b, 0xd7, 0x29, 0xf5, 0x30, 0x61, 0x7b, 0xbb, 0xf3, 0xf1, 0x29, 0xdf, 0x77, + 0x3e, 0x62, 0xe9, 0xb6, 0xbe, 0x39, 0x1b, 0xbb, 0xff, 0xc1, 0xd9, 0xe8, 0xd4, 0xf6, 0xfe, 0x78, 0x04, 0xee, 0xa4, + 0x71, 0x3c, 0x36, 0xd7, 0x94, 0x48, 0x2c, 0xdc, 0x72, 0x5c, 0xf7, 0xd6, 0x6b, 0x31, 0xec, 0x80, 0xda, 0x29, 0x8a, + 0xe0, 0x67, 0xd7, 0xfe, 0x0c, 0x48, 0xb2, 0xd5, 0x21, 0xc7, 0xa2, 0x12, 0x65, 0x50, 0x02, 0x06, 0xd4, 0xb1, 0xb1, + 0xf5, 0x32, 0x88, 0xed, 0x70, 0xc8, 0x61, 0x39, 0x11, 0xd5, 0xd5, 0x15, 0x8c, 0xd8, 0x1c, 0x1b, 0x4e, 0xc0, 0x8c, + 0xf7, 0x5a, 0x15, 0x7a, 0xf1, 0xf3, 0xdf, 0x33, 0xa7, 0x8d, 0x23, 0xc6, 0x72, 0x12, 0x0d, 0x2b, 0x06, 0x37, 0x02, + 0xc7, 0x30, 0x1e, 0x1a, 0x09, 0xb5, 0x3e, 0xd5, 0x51, 0xe3, 0x48, 0xc2, 0x1d, 0x50, 0xbb, 0x1d, 0x9a, 0x73, 0x69, + 0xbd, 0xde, 0x7b, 0xb0, 0xe0, 0x32, 0xc0, 0xed, 0x97, 0x44, 0x37, 0x48, 0x0a, 0x25, 0x4e, 0x82, 0xc2, 0x85, 0x41, + 0x55, 0x4d, 0xe4, 0xb0, 0x33, 0x02, 0x9e, 0xb4, 0x9f, 0x5d, 0xc9, 0x5a, 0x48, 0xce, 0x5a, 0x2d, 0x54, 0x54, 0x1d, + 0xd3, 0xa1, 0x68, 0x65, 0x23, 0xcc, 0x70, 0x66, 0x05, 0x16, 0x38, 0xbd, 0xe2, 0xa2, 0xee, 0x7a, 0x98, 0x8d, 0x10, + 0x2e, 0xd7, 0xeb, 0xd8, 0x0e, 0xad, 0x40, 0xeb, 0x75, 0x11, 0x0e, 0xcd, 0xe4, 0x43, 0xc5, 0xe7, 0x03, 0x4d, 0x9e, + 0x9b, 0xf3, 0xf0, 0x39, 0x0c, 0xb2, 0x45, 0xe2, 0xc2, 0xa9, 0x04, 0x0b, 0xd0, 0x5c, 0xb5, 0xe4, 0x30, 0x6b, 0x75, + 0x47, 0x01, 0x0d, 0x1b, 0x66, 0x23, 0x52, 0x6c, 0xc0, 0x72, 0x56, 0xb9, 0x03, 0xf3, 0x4f, 0x38, 0xd8, 0xfe, 0x34, + 0xe7, 0x8c, 0x6d, 0x30, 0x5c, 0x93, 0x6d, 0x95, 0x41, 0x85, 0x57, 0x6e, 0x71, 0x7d, 0xb9, 0x86, 0x81, 0x45, 0x55, + 0x08, 0xbb, 0x6b, 0xe6, 0x01, 0x08, 0xff, 0x15, 0xb6, 0x97, 0xb4, 0x32, 0xe2, 0xde, 0x42, 0x7c, 0x6f, 0xbb, 0x9d, + 0x24, 0x09, 0x2d, 0xa7, 0xe6, 0x4a, 0xc4, 0xdf, 0xf0, 0x9a, 0x3d, 0x70, 0xea, 0xc6, 0x19, 0xf4, 0x3c, 0xac, 0x3a, + 0x1b, 0x11, 0x3b, 0x7e, 0xcf, 0xec, 0x78, 0xc7, 0x15, 0x4a, 0xf7, 0xeb, 0x22, 0xec, 0x60, 0xb2, 0xff, 0xe5, 0xc1, + 0x9c, 0xb9, 0xc1, 0x58, 0x34, 0xd9, 0x82, 0xdb, 0x57, 0xe0, 0x41, 0xe9, 0x16, 0xdc, 0xbe, 0x0e, 0x5f, 0x0f, 0xad, + 0xe2, 0xab, 0x03, 0x0c, 0xc8, 0x84, 0x1d, 0x69, 0x9d, 0x10, 0x0c, 0xf3, 0x7c, 0x9b, 0x23, 0xb3, 0x64, 0x15, 0x0e, + 0x57, 0x4d, 0x62, 0xb1, 0xb5, 0x17, 0x6a, 0x26, 0x35, 0x10, 0x8c, 0x45, 0xfa, 0x1c, 0x85, 0x4a, 0x83, 0xa6, 0x71, + 0x0c, 0x60, 0x95, 0xd3, 0xd6, 0x3f, 0x3f, 0x3a, 0x02, 0xa1, 0x01, 0x58, 0xbb, 0x24, 0xa3, 0x0b, 0xbd, 0x2c, 0x81, + 0xbf, 0x52, 0xfe, 0x37, 0x24, 0x83, 0xdb, 0x89, 0x49, 0x83, 0x1f, 0x90, 0xb0, 0xa0, 0x4a, 0xf1, 0x2f, 0x36, 0xcd, + 0xfd, 0xc6, 0x25, 0xf1, 0x18, 0xad, 0x2c, 0xa7, 0x28, 0x51, 0x5f, 0x3a, 0x74, 0x6d, 0x42, 0xee, 0xf9, 0x17, 0x26, + 0xf4, 0x8f, 0x5c, 0x69, 0x26, 0x00, 0x00, 0x35, 0xe2, 0xc1, 0x94, 0x14, 0x82, 0xad, 0xdb, 0xa8, 0x45, 0xf3, 0xfc, + 0x9b, 0x55, 0x74, 0x93, 0x2d, 0x9a, 0x51, 0x91, 0x17, 0xb6, 0x93, 0xd0, 0x66, 0xd2, 0xdb, 0x89, 0x56, 0x25, 0x43, + 0x8b, 0x9d, 0x9a, 0xfd, 0x30, 0xb4, 0x3e, 0x16, 0xc4, 0x9f, 0x0b, 0xfe, 0x2c, 0xfd, 0x26, 0x1f, 0x03, 0x57, 0xea, + 0x5f, 0x59, 0x85, 0x70, 0x26, 0x58, 0x07, 0xe4, 0x35, 0x69, 0x8e, 0xd3, 0xa3, 0xce, 0x6c, 0x47, 0xb9, 0x50, 0x19, + 0x85, 0x6d, 0x9d, 0x14, 0x06, 0x53, 0x2e, 0xbe, 0x2e, 0x71, 0xfd, 0xe4, 0x8f, 0x11, 0x7f, 0x74, 0x88, 0xff, 0x94, + 0x4a, 0xa3, 0x55, 0x85, 0x60, 0xc8, 0xef, 0x48, 0xa6, 0xe0, 0x2a, 0xb6, 0xe0, 0xfa, 0xa5, 0x9e, 0x17, 0x5b, 0x9e, + 0x38, 0x7d, 0xa6, 0x2a, 0xe8, 0xa8, 0xf8, 0x96, 0xe1, 0x57, 0x0c, 0xee, 0x8d, 0x5f, 0xf0, 0xa0, 0xca, 0xee, 0x7d, + 0xf1, 0x8b, 0xe0, 0xbe, 0xf8, 0x05, 0x4f, 0x77, 0x8b, 0x06, 0xf7, 0xc4, 0xbd, 0xe4, 0x32, 0xe9, 0x44, 0x9e, 0x8f, + 0xca, 0x69, 0xed, 0x5f, 0x69, 0xb7, 0x06, 0xae, 0x6d, 0xe2, 0xc0, 0x38, 0xaf, 0x29, 0x42, 0x31, 0x67, 0xce, 0x68, + 0x39, 0xfc, 0xaf, 0xad, 0x93, 0x3b, 0x79, 0xa4, 0x95, 0x42, 0xde, 0xd2, 0x52, 0x3f, 0x80, 0x0d, 0x57, 0xee, 0xf8, + 0x00, 0x52, 0x02, 0xca, 0xb6, 0xff, 0xac, 0x8b, 0x40, 0x1c, 0x57, 0xd6, 0xf9, 0x28, 0x6c, 0x9f, 0x94, 0x15, 0x57, + 0xd7, 0x14, 0x42, 0xee, 0x8c, 0x96, 0x00, 0x61, 0xea, 0x5d, 0xf3, 0x98, 0xa3, 0xc9, 0x2c, 0x5d, 0x6d, 0x2a, 0xd5, + 0x41, 0x69, 0xb9, 0x3a, 0x8e, 0x70, 0xb9, 0x31, 0x37, 0xe8, 0x7f, 0x73, 0xfc, 0x27, 0x77, 0x34, 0xf2, 0xfb, 0x8a, + 0x02, 0x7d, 0xdc, 0xef, 0x6b, 0xb3, 0x87, 0x44, 0xda, 0x39, 0x54, 0x96, 0x02, 0x80, 0xd5, 0x06, 0x5f, 0x37, 0x1e, + 0xa7, 0x9e, 0x49, 0x37, 0x9b, 0xaf, 0x1a, 0xc2, 0x62, 0x56, 0x59, 0xf0, 0x98, 0x6e, 0xf6, 0x58, 0x8e, 0x7a, 0x59, + 0x5c, 0x57, 0x7b, 0xac, 0xd1, 0x2f, 0xfa, 0x0a, 0x28, 0x6b, 0x43, 0xb4, 0xf5, 0x3a, 0x6e, 0xc2, 0x9b, 0x88, 0xe0, + 0x1a, 0x04, 0x61, 0x11, 0x18, 0x70, 0x34, 0x18, 0x6f, 0x5b, 0x27, 0x46, 0xdb, 0xf6, 0x4b, 0x9e, 0x75, 0x6f, 0x8c, + 0x23, 0x54, 0x34, 0xd8, 0xea, 0xa1, 0xe6, 0x01, 0xdb, 0xd9, 0x55, 0x1d, 0x05, 0x10, 0xca, 0xa9, 0x37, 0xce, 0xad, + 0xad, 0x68, 0xf7, 0xc0, 0x17, 0x7d, 0xc3, 0x3c, 0xd7, 0x81, 0x6e, 0x37, 0x3f, 0xb0, 0x6d, 0x7a, 0x26, 0xbf, 0x66, + 0xdb, 0xd4, 0xe0, 0x84, 0x0f, 0x3b, 0xe8, 0xdb, 0x86, 0xb0, 0xb6, 0xaf, 0xfd, 0x45, 0xfe, 0x17, 0xba, 0xeb, 0x02, + 0x7a, 0x5a, 0x30, 0x7b, 0x1a, 0xf3, 0x41, 0x6f, 0x36, 0xdf, 0x57, 0xfe, 0x0b, 0xc6, 0x56, 0xe8, 0x7b, 0xbb, 0x0b, + 0x9c, 0x58, 0x69, 0x1c, 0x82, 0xe3, 0xbf, 0x39, 0x99, 0x16, 0x72, 0x4c, 0x8b, 0xf7, 0xd0, 0x63, 0x9d, 0xfb, 0xf2, + 0x3e, 0x2f, 0xa9, 0x66, 0x8e, 0xd6, 0xd4, 0xa3, 0xf8, 0x9b, 0x07, 0xc3, 0xf8, 0x9b, 0x5b, 0xca, 0x5d, 0xb7, 0x80, + 0x57, 0x3f, 0x56, 0x4d, 0xa4, 0xdf, 0x6f, 0x3c, 0xed, 0xe0, 0x6a, 0x7f, 0x2f, 0xdb, 0x24, 0x8d, 0x57, 0x24, 0x8d, + 0xab, 0x78, 0xbb, 0xa9, 0x38, 0xfe, 0xf3, 0x2b, 0x83, 0xdd, 0x25, 0x73, 0x7f, 0x06, 0x64, 0xee, 0x4f, 0x9e, 0x7e, + 0xb3, 0x56, 0x40, 0xf1, 0x4e, 0x93, 0x53, 0x63, 0x19, 0x63, 0x47, 0xfd, 0x4e, 0x83, 0x41, 0x83, 0x26, 0xd7, 0x81, + 0xb7, 0x43, 0x7d, 0x7a, 0x79, 0xfb, 0xa3, 0x38, 0x5b, 0x2a, 0x2d, 0xe7, 0xae, 0x51, 0xe5, 0x7c, 0x9c, 0x4c, 0x26, + 0x28, 0xb0, 0xcd, 0x1d, 0x7e, 0xda, 0x74, 0x23, 0x5b, 0x7d, 0xe6, 0x22, 0x4f, 0x15, 0x76, 0x67, 0x8b, 0x4a, 0xe5, + 0x86, 0x78, 0x33, 0xe7, 0xdd, 0x3c, 0x3c, 0xe1, 0x82, 0xab, 0x19, 0x2b, 0xe3, 0x12, 0xad, 0xbe, 0xd6, 0x59, 0x09, + 0xb7, 0x39, 0xb6, 0x33, 0xbc, 0xac, 0x2c, 0x07, 0x74, 0x02, 0xad, 0x81, 0xce, 0x68, 0xce, 0xf4, 0x4c, 0xe6, 0x60, + 0xf8, 0x92, 0xe4, 0x95, 0x3b, 0xd5, 0xd1, 0xd1, 0x61, 0x1c, 0x19, 0xfd, 0x05, 0xf8, 0xa0, 0x87, 0x39, 0x68, 0xb0, + 0x02, 0xc7, 0xa0, 0xba, 0x6b, 0x86, 0x56, 0x6c, 0xdb, 0x87, 0x46, 0x27, 0x9f, 0xd9, 0x3d, 0xe6, 0x68, 0xb3, 0x49, + 0xed, 0xa8, 0xa3, 0x09, 0x67, 0x45, 0x1e, 0xe1, 0xcf, 0xec, 0x3e, 0xad, 0xdc, 0xd6, 0x8d, 0x97, 0xb5, 0x59, 0xc4, + 0x48, 0xde, 0x8a, 0x08, 0xd7, 0x9d, 0xa4, 0xab, 0x0d, 0x96, 0x25, 0x9f, 0x02, 0x8e, 0xfe, 0xc0, 0xee, 0x53, 0xd7, + 0x5e, 0xe0, 0x2a, 0x88, 0x56, 0x1e, 0xf4, 0x49, 0x90, 0x1c, 0x2e, 0x83, 0x13, 0x38, 0x86, 0xa6, 0xee, 0x88, 0x34, + 0xca, 0xd5, 0x22, 0x24, 0x42, 0x9b, 0xff, 0x74, 0x2a, 0x78, 0x12, 0x9e, 0x73, 0xba, 0x61, 0x71, 0xbb, 0x55, 0x89, + 0x41, 0x85, 0xda, 0x82, 0xe4, 0xd7, 0x98, 0xfb, 0xdd, 0xe7, 0xbc, 0x1f, 0x02, 0x9d, 0xd9, 0x84, 0xba, 0x46, 0xd3, + 0xa5, 0xf9, 0x85, 0xea, 0x3b, 0xa8, 0xb9, 0xae, 0x2b, 0x1e, 0xfc, 0x1a, 0x03, 0xe0, 0xc1, 0x5a, 0x86, 0x1a, 0x87, + 0xd0, 0x8d, 0x37, 0x53, 0x5d, 0x50, 0x12, 0xaf, 0xfc, 0x1c, 0x52, 0x1e, 0x82, 0x51, 0x6f, 0x00, 0x0d, 0x1d, 0x82, + 0x59, 0xcb, 0x43, 0x3e, 0x89, 0xc5, 0xce, 0x19, 0x2a, 0xcd, 0x19, 0x9a, 0x04, 0x20, 0xff, 0xca, 0x99, 0xc9, 0x0c, + 0x34, 0x0c, 0x6f, 0x69, 0x0e, 0x40, 0xb7, 0xba, 0x0e, 0x87, 0xc2, 0x15, 0xad, 0x9c, 0xf7, 0xec, 0xa2, 0xcb, 0xc6, + 0xb0, 0x62, 0xd3, 0x0e, 0xda, 0xa4, 0x30, 0x25, 0x66, 0x0b, 0x6c, 0xbc, 0xde, 0x87, 0x7b, 0xbb, 0xda, 0xb8, 0x4c, + 0xfc, 0xb4, 0x88, 0x87, 0x49, 0x4c, 0xd1, 0x8a, 0xc7, 0x14, 0x4b, 0xb0, 0x83, 0x2c, 0x37, 0xd5, 0xf8, 0x59, 0xb8, + 0x1c, 0x0d, 0x2b, 0xe9, 0xfd, 0x0e, 0x86, 0xc0, 0xe5, 0x6b, 0xb0, 0x0d, 0xc5, 0xbc, 0x22, 0x2c, 0xb1, 0xf1, 0xf4, + 0x0b, 0xd6, 0x6d, 0x6a, 0x17, 0xc4, 0xaf, 0xc0, 0x82, 0xc6, 0xab, 0x60, 0x16, 0xa1, 0x53, 0xb9, 0x73, 0x38, 0x74, + 0xd7, 0x84, 0xb5, 0xf1, 0x6a, 0xac, 0xc8, 0xd6, 0xd1, 0xf3, 0x6d, 0x1b, 0xcf, 0xbf, 0x96, 0xac, 0xbc, 0xbf, 0x61, + 0x60, 0x63, 0x2d, 0xc1, 0xdd, 0xb8, 0x5e, 0x86, 0xda, 0x40, 0x7e, 0x20, 0x0d, 0xeb, 0xb2, 0xc1, 0xdf, 0x8c, 0x8a, + 0xb1, 0x31, 0xf7, 0x94, 0x81, 0xb6, 0xc6, 0x6e, 0x17, 0xf6, 0x55, 0xd7, 0x4d, 0xd6, 0x37, 0xb1, 0x12, 0x6a, 0x48, + 0xbb, 0xbb, 0x05, 0x5c, 0x86, 0xfe, 0xb0, 0x43, 0x35, 0xda, 0x56, 0xdd, 0x40, 0x12, 0x5c, 0xfb, 0xc9, 0xaf, 0x4f, + 0x75, 0x9f, 0xb5, 0xee, 0xd7, 0xa7, 0xda, 0xb8, 0x2c, 0x34, 0x86, 0x44, 0xd8, 0xf5, 0x53, 0xf9, 0x4f, 0x8b, 0xcd, + 0x06, 0x6d, 0x60, 0x78, 0x4f, 0x78, 0x3f, 0x8e, 0x9f, 0x78, 0x0b, 0xc5, 0x04, 0x2e, 0x72, 0x6f, 0x0a, 0xe9, 0x09, + 0x79, 0x3d, 0x82, 0x27, 0x7c, 0x67, 0x08, 0x4f, 0x78, 0xe0, 0xf4, 0x0a, 0x52, 0xd3, 0x54, 0xb0, 0xdc, 0xd3, 0x4f, + 0x64, 0x91, 0xd0, 0xf0, 0x71, 0x6f, 0x38, 0x11, 0xfa, 0x8f, 0x14, 0xf8, 0x2f, 0x3c, 0x5e, 0x6a, 0x2d, 0x05, 0xe6, + 0x62, 0xb1, 0xd4, 0x58, 0x99, 0xd1, 0xaf, 0x26, 0x52, 0xe8, 0xf6, 0x84, 0xce, 0x79, 0x71, 0x9f, 0x2e, 0x79, 0x7b, + 0x2e, 0x85, 0x54, 0x0b, 0x9a, 0x31, 0xac, 0xee, 0x95, 0x66, 0xf3, 0xf6, 0x92, 0xe3, 0x97, 0xac, 0xf8, 0xc2, 0x34, + 0xcf, 0x28, 0x7e, 0x27, 0xc7, 0x52, 0x4b, 0xfc, 0xe6, 0xee, 0x7e, 0xca, 0x04, 0xfe, 0x30, 0x5e, 0x0a, 0xbd, 0xc4, + 0x8a, 0x0a, 0xd5, 0x56, 0xac, 0xe4, 0x93, 0x7e, 0xbb, 0xbd, 0x28, 0xf9, 0x9c, 0x96, 0xf7, 0xed, 0x4c, 0x16, 0xb2, + 0x4c, 0xff, 0xab, 0x73, 0x4a, 0x1f, 0x4d, 0xce, 0xfa, 0xba, 0xa4, 0x42, 0x71, 0x58, 0x98, 0x94, 0x16, 0xc5, 0xc1, + 0xe9, 0x79, 0x67, 0xae, 0x0e, 0xed, 0x85, 0x1f, 0x15, 0x7a, 0xf3, 0x07, 0xfe, 0x45, 0xc2, 0x28, 0x93, 0xb1, 0x16, + 0x6e, 0x90, 0xab, 0x6c, 0x59, 0x2a, 0x59, 0xa6, 0x0b, 0xc9, 0x85, 0x66, 0x65, 0x7f, 0x2c, 0xcb, 0x9c, 0x95, 0xed, + 0x92, 0xe6, 0x7c, 0xa9, 0xd2, 0xb3, 0xc5, 0x5d, 0xbf, 0xd9, 0x83, 0xcd, 0x4f, 0x85, 0x14, 0xac, 0x0f, 0xfc, 0xc6, + 0xb4, 0x94, 0x4b, 0x91, 0xbb, 0x61, 0x2c, 0x85, 0x62, 0xba, 0xbf, 0xa0, 0x39, 0xd8, 0x01, 0xa7, 0x97, 0x8b, 0xbb, + 0xbe, 0x99, 0xf5, 0x2d, 0xe3, 0xd3, 0x99, 0x4e, 0xcf, 0x3b, 0x1d, 0xfb, 0xad, 0xf8, 0xdf, 0x2c, 0xed, 0xf6, 0x92, + 0xde, 0xf9, 0xe2, 0x0e, 0x38, 0x78, 0xcd, 0xca, 0x36, 0xc0, 0x02, 0x2a, 0x75, 0x93, 0xce, 0xa3, 0xd3, 0x87, 0x90, + 0x01, 0x36, 0x0e, 0x6d, 0x33, 0x21, 0x30, 0x76, 0x4f, 0x97, 0x8b, 0x05, 0x2b, 0xc1, 0x8b, 0xbe, 0x3f, 0xa7, 0xe5, + 0x94, 0x8b, 0x76, 0x69, 0x1a, 0x6d, 0x5f, 0x2e, 0xee, 0x36, 0x30, 0x9f, 0xd4, 0x9a, 0xad, 0xba, 0x69, 0xb9, 0xaf, + 0x55, 0x30, 0x44, 0x13, 0x93, 0x26, 0x2d, 0xa7, 0x63, 0x1a, 0x77, 0x7b, 0x0f, 0xb1, 0xff, 0x5f, 0xd2, 0x43, 0x01, + 0xd8, 0xda, 0xf9, 0xb2, 0x34, 0xb7, 0xa8, 0x69, 0x57, 0xd9, 0x66, 0x67, 0xf2, 0x0b, 0x2b, 0x7d, 0xab, 0xe6, 0x63, + 0xb5, 0x33, 0xef, 0xff, 0x51, 0xa3, 0xd4, 0xb6, 0xf5, 0x4a, 0xdd, 0x00, 0x8d, 0xde, 0x6d, 0xec, 0xbf, 0x7a, 0x97, + 0xf4, 0xe1, 0xd9, 0xb9, 0x87, 0xfb, 0x64, 0x32, 0x69, 0x00, 0xdd, 0x43, 0xb7, 0xdb, 0x59, 0xdc, 0x1d, 0xf4, 0x3a, + 0x1e, 0xc6, 0x16, 0xa6, 0x17, 0x8b, 0xbb, 0x3d, 0x2b, 0x18, 0x60, 0xc5, 0x76, 0x6f, 0x07, 0xc9, 0xa9, 0x3a, 0x60, + 0x54, 0xb1, 0xcd, 0x1f, 0x78, 0x4e, 0x01, 0x37, 0x0c, 0xd2, 0x0e, 0x8d, 0x9c, 0x0a, 0x2b, 0x30, 0x5a, 0xdd, 0xf2, + 0x5c, 0xcf, 0xd2, 0x6e, 0xa7, 0xf3, 0x5d, 0x8d, 0x49, 0xfd, 0x99, 0x5d, 0xd2, 0x6e, 0xc9, 0xe6, 0x0d, 0xfc, 0x1a, + 0xd3, 0x6a, 0x17, 0xac, 0x16, 0xd2, 0x75, 0x5a, 0xb2, 0xc2, 0x44, 0xb9, 0xd9, 0xb8, 0xad, 0xb0, 0x33, 0x65, 0x2e, + 0x66, 0xac, 0xe4, 0xba, 0xdf, 0xfc, 0xaa, 0x3b, 0xde, 0x9d, 0xd3, 0xc6, 0xca, 0xc7, 0x2b, 0x5b, 0xc3, 0x5d, 0xc6, + 0x3e, 0x85, 0x8f, 0x5d, 0xac, 0xfc, 0x42, 0xcb, 0x78, 0x6b, 0xc3, 0xe0, 0xb0, 0x06, 0xda, 0x04, 0x73, 0x2e, 0xc1, + 0x54, 0x74, 0x84, 0xbf, 0x02, 0x85, 0x8c, 0x16, 0x59, 0x0c, 0x23, 0x3a, 0x68, 0x1f, 0x9c, 0x96, 0x6c, 0x8e, 0x3c, + 0x20, 0x92, 0x87, 0xe7, 0x25, 0x9b, 0x6f, 0x12, 0x53, 0x7d, 0x65, 0x50, 0x97, 0x16, 0x7c, 0x2a, 0xd2, 0x8c, 0xc1, + 0xb6, 0xda, 0x24, 0x4c, 0x68, 0xae, 0xef, 0xdb, 0xa5, 0xbc, 0x5d, 0xe5, 0x5c, 0x2d, 0x0a, 0x7a, 0x9f, 0x4e, 0x0a, + 0x76, 0xd7, 0x37, 0xa5, 0xda, 0x5c, 0xb3, 0xb9, 0x72, 0x65, 0xfb, 0x90, 0xde, 0xce, 0xad, 0x39, 0x07, 0x40, 0x4f, + 0xde, 0x6e, 0xef, 0x6b, 0xbf, 0x68, 0x6d, 0xb9, 0xd4, 0x07, 0x1d, 0xd5, 0x9f, 0x73, 0xd1, 0x76, 0x03, 0x39, 0x03, + 0x8c, 0xd8, 0x85, 0x7c, 0xd0, 0x7f, 0xc2, 0xee, 0x16, 0x54, 0xe4, 0x2c, 0x5f, 0x05, 0xd5, 0x7a, 0x50, 0x2f, 0x2c, + 0x95, 0x0a, 0x3d, 0x6b, 0x1b, 0x1b, 0xb4, 0xb8, 0x27, 0xd0, 0x57, 0x50, 0xfe, 0x51, 0x07, 0xdb, 0xff, 0x4f, 0xba, + 0x28, 0xac, 0x7c, 0x00, 0xe1, 0xa0, 0xf8, 0xe4, 0xbe, 0x0d, 0x7f, 0x57, 0xe0, 0xf3, 0xc4, 0x33, 0x5a, 0x38, 0x88, + 0xcc, 0x79, 0x9e, 0x17, 0x8d, 0x11, 0x5d, 0x07, 0x9d, 0x75, 0xd1, 0x0a, 0xe6, 0x9f, 0x76, 0x0e, 0x3a, 0x07, 0x66, + 0x2e, 0x6e, 0x1b, 0x9c, 0x9d, 0x3d, 0x3c, 0x7d, 0xc4, 0xfa, 0x05, 0x17, 0xac, 0x31, 0xd5, 0x6f, 0x82, 0x3a, 0x6c, + 0xb8, 0xe7, 0x1a, 0xee, 0x1e, 0x74, 0x0f, 0xce, 0x3a, 0xdf, 0x79, 0x2a, 0x52, 0xb0, 0x89, 0xb6, 0xfb, 0xa6, 0x41, + 0x56, 0x2e, 0x7d, 0xd3, 0xb7, 0x25, 0x5d, 0xa4, 0x42, 0xc2, 0x9f, 0x3e, 0x6c, 0xfe, 0x49, 0x21, 0x6f, 0xd3, 0x19, + 0xcf, 0x73, 0x26, 0x6c, 0x81, 0x2a, 0x91, 0x15, 0x05, 0x5f, 0x28, 0x6e, 0x57, 0xc3, 0xe1, 0xee, 0xf9, 0x16, 0x54, + 0xc3, 0x01, 0x9d, 0x06, 0x03, 0x3a, 0xaf, 0x07, 0x54, 0xf7, 0x1f, 0x8e, 0xb0, 0xb7, 0x35, 0x57, 0x53, 0xaa, 0xdf, + 0xc0, 0xa4, 0x3f, 0x97, 0x4a, 0x03, 0xcc, 0xbd, 0xf1, 0x88, 0x39, 0x5d, 0xda, 0x63, 0xa6, 0x6f, 0x19, 0x13, 0x5f, + 0x1f, 0xc4, 0x75, 0x2a, 0x45, 0x71, 0x6f, 0x3f, 0x57, 0x61, 0x97, 0x74, 0xa9, 0xe5, 0x26, 0x19, 0x73, 0x41, 0xcb, + 0xfb, 0x4f, 0x8a, 0x09, 0x25, 0xcb, 0x4f, 0x72, 0x32, 0x59, 0x7d, 0x8d, 0xe4, 0x3d, 0x44, 0x9b, 0x44, 0x71, 0x31, + 0x2d, 0x98, 0x25, 0x70, 0x06, 0x11, 0xdc, 0x21, 0x63, 0xdb, 0x35, 0x4d, 0x36, 0x06, 0xbd, 0x49, 0xb2, 0x82, 0xcf, + 0xa9, 0x66, 0x06, 0xce, 0x01, 0xa9, 0x71, 0x93, 0xb7, 0x54, 0xae, 0x73, 0x60, 0xff, 0xd4, 0xa5, 0x61, 0x1b, 0x05, + 0x85, 0x7d, 0x93, 0x5c, 0x18, 0xfc, 0x30, 0xe0, 0x30, 0xbb, 0xc8, 0xac, 0x9e, 0x59, 0xbb, 0x00, 0x76, 0x30, 0xbb, + 0x46, 0x53, 0xd7, 0x8e, 0x2e, 0xd9, 0x16, 0xcf, 0x3b, 0xdf, 0x35, 0x73, 0x0b, 0x3a, 0x66, 0xc5, 0xca, 0x6e, 0x54, + 0x0f, 0x5c, 0xb7, 0x55, 0xc3, 0x65, 0x0e, 0x48, 0x86, 0x01, 0xd1, 0x28, 0x4d, 0xdb, 0xb7, 0x6c, 0xfc, 0x99, 0x6b, + 0xbb, 0x65, 0xda, 0xea, 0x16, 0x9c, 0x8a, 0xcc, 0x98, 0x16, 0xac, 0x5c, 0x79, 0x42, 0xde, 0x69, 0x10, 0xd0, 0x1b, + 0x61, 0x0e, 0x68, 0x4d, 0xc7, 0x6d, 0x08, 0xb1, 0xc6, 0xca, 0xd5, 0xbe, 0xc9, 0xcd, 0xe9, 0x9d, 0x43, 0xb1, 0x47, + 0x9d, 0xef, 0x1a, 0x87, 0xec, 0x59, 0xa7, 0xe3, 0x8f, 0x88, 0xb6, 0xad, 0x91, 0x76, 0x93, 0x73, 0x36, 0xaf, 0x12, + 0xb5, 0x5c, 0xa4, 0x8d, 0x84, 0xb1, 0xd4, 0x5a, 0xce, 0x6d, 0xda, 0x1e, 0x6a, 0xd4, 0x24, 0xbd, 0xdd, 0xde, 0xe2, + 0xee, 0xc0, 0xfc, 0xd3, 0x39, 0xe8, 0xec, 0x92, 0xda, 0x5d, 0xac, 0x38, 0x45, 0x1e, 0x8f, 0xa1, 0xe3, 0x2e, 0x9b, + 0xf7, 0x97, 0x0a, 0x8e, 0x7b, 0x03, 0x71, 0x73, 0xa2, 0x6d, 0xcc, 0x64, 0x01, 0xb0, 0x94, 0x0b, 0x38, 0x5d, 0xed, + 0x61, 0x07, 0x7d, 0x28, 0x09, 0xe6, 0xf0, 0x7b, 0x1b, 0x6d, 0x0e, 0xab, 0x73, 0x50, 0x0f, 0x0c, 0xfe, 0xd9, 0xfc, + 0x51, 0xf3, 0xe7, 0xcf, 0x58, 0x20, 0x1f, 0xf1, 0x56, 0x72, 0xbe, 0xee, 0x38, 0x99, 0x28, 0xd7, 0xb5, 0xa8, 0x66, + 0x3c, 0x4a, 0xe6, 0xf4, 0xce, 0xba, 0x96, 0xcc, 0xb9, 0x00, 0xc3, 0x35, 0x84, 0x75, 0x60, 0xe2, 0x3f, 0x0b, 0x1b, + 0xca, 0x75, 0x0c, 0x0d, 0x1f, 0xf7, 0x92, 0xf3, 0x73, 0x84, 0x3b, 0xb8, 0x77, 0x7e, 0x1e, 0xc8, 0x64, 0x13, 0xbd, + 0xaf, 0xe8, 0xbe, 0x92, 0x72, 0x4f, 0xc9, 0x13, 0xd3, 0xe8, 0x49, 0xb7, 0xd3, 0xc1, 0xc6, 0x7d, 0xbe, 0x2a, 0x2c, + 0xd4, 0x9e, 0x66, 0xbb, 0x9d, 0x0e, 0x34, 0x0b, 0x7f, 0xdc, 0xbc, 0x7e, 0x20, 0xab, 0x4e, 0xda, 0xc1, 0xdd, 0xb4, + 0x8b, 0x7b, 0x69, 0x0f, 0x9f, 0xa6, 0xa7, 0xf8, 0x2c, 0x3d, 0xc3, 0xe7, 0xe9, 0x39, 0xbe, 0x48, 0x2f, 0xf0, 0xc3, + 0xf4, 0x21, 0xbe, 0x4c, 0x2f, 0xf1, 0xa3, 0xf4, 0x11, 0x7e, 0x9c, 0x76, 0x3b, 0xf8, 0x49, 0xda, 0xed, 0xe2, 0xa7, + 0x69, 0xb7, 0x87, 0x9f, 0xa5, 0xdd, 0x53, 0xfc, 0x3c, 0xed, 0x9e, 0xe1, 0x17, 0x69, 0xf7, 0x1c, 0x53, 0xc8, 0x1d, + 0x43, 0x6e, 0x06, 0xb9, 0x39, 0xe4, 0x32, 0xc8, 0x9d, 0xa4, 0xdd, 0xf3, 0x0d, 0x56, 0x36, 0xe4, 0x46, 0xd4, 0xe9, + 0xf6, 0x4e, 0xcf, 0xce, 0x2f, 0x1e, 0x5e, 0x3e, 0x7a, 0xfc, 0xe4, 0xe9, 0xb3, 0xe7, 0x2f, 0xa2, 0x11, 0xfe, 0x64, + 0x3c, 0x5f, 0x94, 0x18, 0xf2, 0xa3, 0xee, 0xf9, 0x08, 0xdf, 0xfb, 0xcf, 0x98, 0x1f, 0xf5, 0xce, 0x3a, 0xe8, 0xfa, + 0xfa, 0x6c, 0xd4, 0xaa, 0x72, 0x9f, 0x18, 0x87, 0x9b, 0x3a, 0x8b, 0x10, 0x12, 0x43, 0x0e, 0xc2, 0x77, 0xd6, 0x81, + 0x86, 0xc5, 0x3c, 0x29, 0xd1, 0xd1, 0x91, 0xf9, 0x31, 0xf5, 0x3f, 0xc6, 0xfe, 0x07, 0x0d, 0x16, 0xe9, 0x0b, 0x8d, + 0x9d, 0xc7, 0xb5, 0xae, 0xfc, 0x1d, 0x2a, 0x53, 0xa2, 0x03, 0xee, 0x8c, 0xfa, 0xff, 0x2b, 0xb2, 0x46, 0x3b, 0xe4, + 0xcc, 0x2a, 0xc6, 0xce, 0x07, 0x8c, 0xac, 0xca, 0xb4, 0x77, 0x7e, 0x7e, 0xf4, 0xc3, 0x90, 0x0f, 0xbb, 0xa3, 0xd1, + 0x71, 0xf7, 0x21, 0x9e, 0x56, 0x09, 0x3d, 0x9b, 0x30, 0xae, 0x12, 0x4e, 0x6d, 0x02, 0x4d, 0x6d, 0x6d, 0x48, 0x3a, + 0x33, 0x49, 0x50, 0x62, 0x93, 0x9a, 0xb6, 0x1f, 0xda, 0xb6, 0x1f, 0x81, 0x35, 0x99, 0x69, 0xde, 0x35, 0x7d, 0x75, + 0x75, 0xb6, 0x76, 0x8d, 0xe2, 0x69, 0xea, 0x5a, 0xf3, 0x89, 0x67, 0xa3, 0x11, 0x1e, 0x9b, 0xc4, 0xf3, 0x3a, 0xf1, + 0x62, 0x34, 0x72, 0x5d, 0x3d, 0x32, 0x5d, 0x3d, 0xac, 0xb3, 0x2e, 0x47, 0x23, 0xd3, 0x25, 0x72, 0xb1, 0x03, 0x94, + 0x3e, 0xb8, 0xad, 0xf4, 0x37, 0xfc, 0xaa, 0x77, 0x7e, 0x3e, 0x00, 0x0c, 0x33, 0x36, 0xc1, 0x1e, 0x46, 0x9f, 0x03, + 0x18, 0xdd, 0xc1, 0xef, 0xc1, 0x27, 0x9a, 0xde, 0xd3, 0x0a, 0x48, 0x83, 0xe8, 0xbf, 0xa2, 0x96, 0x36, 0x30, 0x37, + 0x7f, 0xa6, 0xf6, 0xcf, 0x18, 0xb5, 0x6e, 0x29, 0x80, 0x1b, 0x34, 0x52, 0x5e, 0xa5, 0x6c, 0x7a, 0xbc, 0xa1, 0xe0, + 0xe2, 0x33, 0x53, 0x05, 0x1d, 0xac, 0x67, 0xb7, 0xe3, 0xf5, 0x4c, 0x7d, 0x41, 0xbf, 0xc7, 0xbf, 0xab, 0xe3, 0x78, + 0xd8, 0x6e, 0x25, 0xec, 0xf7, 0x1c, 0x7c, 0x89, 0x06, 0x69, 0xce, 0xa6, 0x68, 0x30, 0xfc, 0x5d, 0xe1, 0x51, 0x2b, + 0xc8, 0xf8, 0x6e, 0x37, 0x05, 0x3c, 0x8d, 0xb6, 0x13, 0xe3, 0xef, 0xd0, 0x00, 0x0d, 0x7e, 0x57, 0xc7, 0xbf, 0xa3, + 0x07, 0x27, 0x81, 0xd6, 0x44, 0xba, 0x2d, 0x5c, 0x87, 0x1f, 0x3a, 0xae, 0xb6, 0x30, 0xc3, 0xdd, 0x36, 0x83, 0x60, + 0x6d, 0xe0, 0x8a, 0x4e, 0x62, 0xd9, 0xe2, 0x27, 0xa7, 0x1d, 0xf4, 0x5d, 0xb7, 0x07, 0xca, 0x95, 0xb6, 0x38, 0xde, + 0xdd, 0xf4, 0x65, 0xfb, 0x14, 0x3f, 0x6a, 0x97, 0xb8, 0x8b, 0x70, 0xbb, 0xeb, 0xb5, 0xde, 0x43, 0x15, 0x77, 0x10, + 0x56, 0xf1, 0x25, 0xfc, 0x73, 0x86, 0x46, 0xf5, 0x86, 0xfc, 0x89, 0x6e, 0xf7, 0x0e, 0x7e, 0xb3, 0x24, 0x56, 0x2d, + 0x7e, 0x72, 0xd1, 0x41, 0xdf, 0x5d, 0x98, 0x8e, 0xd8, 0xb1, 0xde, 0xd3, 0x95, 0xc4, 0x67, 0x6d, 0x09, 0x1d, 0x75, + 0xaa, 0x7e, 0x44, 0x7c, 0x8e, 0xb0, 0x88, 0x4f, 0xe1, 0x9f, 0x6e, 0xd8, 0xcf, 0xd3, 0x9d, 0x7e, 0xcc, 0xbc, 0xbb, + 0x38, 0x39, 0xb7, 0x6e, 0xb8, 0xca, 0xde, 0x89, 0xb7, 0xd8, 0x75, 0xd7, 0x5c, 0xe6, 0x75, 0x4f, 0xe0, 0x03, 0x61, + 0x7d, 0x4c, 0x14, 0x66, 0xc7, 0xe0, 0xbf, 0x0b, 0x66, 0x2b, 0xea, 0xea, 0xb4, 0xaf, 0x5a, 0x2d, 0x24, 0x86, 0x6a, + 0x74, 0x4c, 0xba, 0x6d, 0xdd, 0x66, 0x18, 0x7e, 0xb7, 0x48, 0x15, 0x14, 0x4e, 0xd4, 0xbd, 0xbe, 0x71, 0xbd, 0xda, + 0x9b, 0x7f, 0x8f, 0x1d, 0x84, 0x10, 0x35, 0x88, 0x75, 0x9b, 0xa1, 0x13, 0xd1, 0x8a, 0xf5, 0x15, 0x1b, 0x5c, 0xa4, + 0x1d, 0x64, 0xb0, 0x53, 0x0d, 0x62, 0xd6, 0xe6, 0x90, 0xde, 0x4b, 0x63, 0xde, 0xd6, 0xf0, 0xeb, 0x2c, 0x80, 0x96, + 0x00, 0xbc, 0xab, 0xbd, 0x91, 0xca, 0x93, 0xde, 0xf9, 0x39, 0x16, 0x84, 0x27, 0x53, 0xf3, 0x4b, 0x11, 0x9e, 0x8c, + 0xcd, 0x2f, 0x49, 0x2a, 0x78, 0xd9, 0xde, 0x71, 0x49, 0x82, 0x55, 0x35, 0x29, 0x14, 0x16, 0xb4, 0x44, 0x27, 0x3d, + 0x6f, 0x16, 0x80, 0x67, 0x7e, 0x0e, 0xa0, 0x06, 0x29, 0x8d, 0x45, 0xa8, 0x6c, 0x97, 0xb8, 0x20, 0xf4, 0x3a, 0x39, + 0x1f, 0xcc, 0x4e, 0xe2, 0x5e, 0x5b, 0xb6, 0x4b, 0x94, 0xce, 0x4e, 0x4c, 0x4d, 0x9c, 0x91, 0x37, 0xd4, 0xb6, 0x86, + 0x67, 0x70, 0x97, 0x9b, 0x91, 0xec, 0xf8, 0xa2, 0xd3, 0x4a, 0xce, 0x11, 0x1e, 0x66, 0xeb, 0x0e, 0x2e, 0xd6, 0xeb, + 0x0e, 0xa6, 0xe1, 0x32, 0x08, 0x0f, 0x90, 0x4a, 0x53, 0xb7, 0x1d, 0x9b, 0x67, 0xc0, 0x63, 0x0d, 0x76, 0x09, 0x1a, + 0xbc, 0x7d, 0x34, 0xf8, 0x21, 0xa5, 0xdc, 0x5d, 0x08, 0x22, 0x13, 0x9d, 0x70, 0x12, 0xea, 0xee, 0xde, 0x08, 0xbf, + 0xae, 0xde, 0xb2, 0x54, 0xc4, 0xbf, 0x4a, 0x6c, 0xd3, 0xea, 0x62, 0x0f, 0xe8, 0x6e, 0xb1, 0xa7, 0x74, 0xa7, 0xd8, + 0xdb, 0x3d, 0xc5, 0x7e, 0xda, 0x2d, 0xf6, 0x97, 0x0c, 0x34, 0x8d, 0xfc, 0xbb, 0xd3, 0x8b, 0x4e, 0xeb, 0x14, 0x90, + 0xf5, 0xf4, 0xa2, 0x53, 0x17, 0x7a, 0x4c, 0xeb, 0xb5, 0xd2, 0xe4, 0x86, 0x5a, 0x5f, 0x0b, 0xee, 0x9d, 0xbe, 0xcd, + 0xc2, 0x59, 0x97, 0xf3, 0xca, 0xbf, 0x7c, 0x78, 0x0e, 0xb6, 0x2c, 0xc2, 0x50, 0x3b, 0x3d, 0xbc, 0x18, 0x0d, 0x66, + 0x2c, 0x6e, 0x41, 0x2a, 0x4a, 0x27, 0xda, 0xfd, 0x42, 0xd5, 0x95, 0xf6, 0x5f, 0x12, 0x92, 0x7a, 0x23, 0x84, 0x25, + 0x69, 0xe9, 0xe1, 0xe9, 0xc8, 0x9c, 0x77, 0x25, 0xfc, 0x3e, 0x33, 0xbf, 0x2b, 0x85, 0x92, 0x73, 0xc8, 0x98, 0xdd, + 0x8e, 0xa3, 0x81, 0x20, 0x0f, 0x68, 0x6c, 0x6c, 0xec, 0x51, 0x5a, 0x65, 0xa8, 0x2f, 0x90, 0xf1, 0xb6, 0xca, 0x10, + 0xe4, 0x8d, 0x70, 0xbf, 0xf1, 0xaa, 0x4c, 0xc1, 0xde, 0x06, 0x4f, 0x53, 0xb0, 0xb5, 0xc1, 0xe3, 0x54, 0x80, 0x3f, + 0x08, 0x4d, 0x59, 0x60, 0xc5, 0xff, 0xdc, 0x69, 0xf0, 0xcc, 0xad, 0x33, 0x31, 0x58, 0xda, 0x67, 0x70, 0x52, 0xfc, + 0x25, 0x63, 0xf8, 0xdb, 0xd2, 0x08, 0x33, 0x68, 0x93, 0x21, 0xcc, 0x93, 0x92, 0x40, 0x1a, 0xe6, 0xc9, 0x94, 0x30, + 0x68, 0x92, 0x27, 0x63, 0xc2, 0x86, 0xbd, 0x00, 0x4d, 0x5e, 0x19, 0xd8, 0x01, 0x70, 0x78, 0xf3, 0x22, 0x5f, 0xdb, + 0xc6, 0xc1, 0x42, 0x00, 0x9a, 0x10, 0x04, 0x62, 0x2e, 0x0c, 0xc1, 0x6c, 0x44, 0xd9, 0x9f, 0xbd, 0x3a, 0xfc, 0x25, + 0x4f, 0xa8, 0xa1, 0xde, 0x7f, 0x00, 0x59, 0x8d, 0x1f, 0xac, 0xd8, 0x06, 0x1f, 0x3c, 0x58, 0x89, 0xcd, 0x77, 0xf0, + 0x47, 0xd9, 0x3f, 0xc0, 0x3c, 0x24, 0x14, 0x6d, 0xd0, 0x1f, 0x29, 0x14, 0xdb, 0x53, 0x0a, 0xfd, 0xe1, 0xdd, 0x01, + 0x15, 0x59, 0xdd, 0xa5, 0x51, 0x4e, 0xcb, 0xcf, 0x11, 0xfe, 0x2d, 0x8d, 0x0a, 0xe0, 0x16, 0x23, 0xfc, 0x6b, 0x1a, + 0x95, 0x2c, 0xc2, 0xff, 0x4a, 0xa3, 0x71, 0xb1, 0x8c, 0xf0, 0x2f, 0x69, 0x34, 0x2d, 0x23, 0xfc, 0x11, 0x94, 0xb5, + 0x39, 0x5f, 0xce, 0x23, 0xfc, 0x21, 0x8d, 0x94, 0xf1, 0x86, 0xc0, 0x8f, 0xd3, 0x88, 0xb1, 0x08, 0xbf, 0x4f, 0x23, + 0x59, 0x44, 0xf8, 0x26, 0x8d, 0x64, 0x19, 0xe1, 0x27, 0x69, 0x54, 0xd2, 0x08, 0x3f, 0x4d, 0x23, 0x28, 0x34, 0x8d, + 0xf0, 0xb3, 0x34, 0x82, 0x96, 0x55, 0x84, 0xdf, 0xa5, 0x11, 0x17, 0x11, 0xfe, 0x39, 0x8d, 0xf4, 0xb2, 0xfc, 0x6b, + 0x29, 0xb9, 0x8a, 0xf0, 0xf3, 0x34, 0x9a, 0xf1, 0x08, 0xbf, 0x4d, 0xa3, 0x52, 0x46, 0xf8, 0x4d, 0x1a, 0xd1, 0x22, + 0xc2, 0xaf, 0xd3, 0xa8, 0x60, 0x11, 0xfe, 0x29, 0x8d, 0x72, 0x16, 0xe1, 0x1f, 0xd3, 0xe8, 0x9e, 0x15, 0x85, 0x8c, + 0xf0, 0x8b, 0x34, 0x62, 0x22, 0xc2, 0x3f, 0xa4, 0x51, 0x36, 0x8b, 0xf0, 0x3f, 0xd3, 0x88, 0x96, 0x9f, 0x55, 0x84, + 0x5f, 0xa6, 0x11, 0xa3, 0x11, 0x7e, 0x65, 0x3b, 0x9a, 0x46, 0xf8, 0xfb, 0x34, 0xba, 0x9d, 0x45, 0x1b, 0x2c, 0x15, + 0x59, 0xbd, 0xe1, 0x19, 0xfb, 0x17, 0x4b, 0xa3, 0x49, 0x67, 0x72, 0x39, 0x99, 0x44, 0x98, 0x0a, 0xcd, 0xff, 0x5a, + 0xb2, 0xdb, 0xe7, 0x1a, 0x12, 0x29, 0x1b, 0xe7, 0x0f, 0x23, 0x4c, 0xff, 0x5a, 0xd2, 0x34, 0x9a, 0x4c, 0x4c, 0x81, + 0xbf, 0x96, 0x74, 0x4e, 0xcb, 0x77, 0x2c, 0x8d, 0x1e, 0x4e, 0x26, 0x93, 0xfc, 0x2c, 0xc2, 0xf4, 0xef, 0xe5, 0xaf, + 0xa6, 0x05, 0x53, 0x60, 0xcc, 0xf8, 0x14, 0xea, 0x9e, 0x4f, 0xce, 0xf3, 0x2c, 0xc2, 0x63, 0xae, 0xfe, 0x5a, 0xc2, + 0xf7, 0x84, 0x9d, 0x65, 0x67, 0x11, 0x1e, 0x17, 0x34, 0xfb, 0x9c, 0x46, 0x1d, 0xf3, 0x4b, 0xfc, 0xc0, 0xf2, 0x37, + 0x73, 0x69, 0xae, 0x32, 0x26, 0x6c, 0x9c, 0xe5, 0x11, 0x36, 0x83, 0x99, 0xc0, 0xdf, 0x2f, 0xfc, 0x3d, 0xd3, 0x69, + 0x74, 0x49, 0x7b, 0x63, 0xd6, 0x8b, 0xf0, 0xf8, 0xed, 0xad, 0x48, 0x23, 0x7a, 0xde, 0xa3, 0x3d, 0x1a, 0xe1, 0xf1, + 0xb2, 0x2c, 0xee, 0x6f, 0xa5, 0xcc, 0x01, 0x08, 0xe3, 0xcb, 0xcb, 0x87, 0x11, 0xce, 0xe8, 0x4f, 0x1a, 0x6a, 0x9f, + 0x4f, 0x1e, 0x31, 0xda, 0x89, 0xf0, 0x0f, 0xb4, 0xd4, 0xbf, 0x2e, 0x95, 0x1b, 0x68, 0x07, 0x52, 0x64, 0xf6, 0x1e, + 0xd4, 0xfc, 0x51, 0xde, 0xbb, 0x78, 0xd4, 0x65, 0x11, 0xce, 0x6e, 0xde, 0x40, 0x6f, 0x0f, 0x27, 0xe7, 0x1d, 0xf8, + 0x10, 0x20, 0x97, 0xb2, 0x12, 0x1a, 0xb9, 0x38, 0x7b, 0x74, 0xce, 0x72, 0x93, 0xa8, 0x78, 0xf1, 0xd9, 0xcc, 0xfe, + 0x12, 0xe6, 0x93, 0x95, 0x7c, 0xae, 0xa4, 0x48, 0xa3, 0x3c, 0xeb, 0x9e, 0x9d, 0x42, 0xc2, 0x3d, 0x15, 0x1e, 0x38, + 0x77, 0x50, 0xf5, 0x72, 0x1c, 0xe1, 0x3b, 0x9b, 0x7a, 0x39, 0x36, 0x1f, 0xd3, 0xf7, 0x3f, 0x89, 0xb7, 0x79, 0x1a, + 0x8d, 0x2f, 0x2f, 0x2f, 0x3a, 0x90, 0xf0, 0x0b, 0xbd, 0x4f, 0x23, 0xfa, 0x08, 0xfe, 0x83, 0xec, 0x5f, 0x5f, 0x40, + 0x87, 0x30, 0xc2, 0xbb, 0xe9, 0xaf, 0x61, 0xce, 0xe7, 0x19, 0xfd, 0xcc, 0xd3, 0x68, 0x9c, 0x8f, 0x1f, 0x5e, 0x40, + 0xbd, 0x39, 0x9d, 0xbe, 0xd0, 0x14, 0xda, 0xed, 0x74, 0x4c, 0xcb, 0xef, 0xf9, 0x17, 0x66, 0xaa, 0x9f, 0x9f, 0x5f, + 0x8c, 0x7b, 0x30, 0x82, 0x1b, 0x50, 0xa8, 0xc0, 0x78, 0x2e, 0x33, 0xd3, 0xe0, 0x4d, 0xf6, 0x3c, 0x4f, 0xa3, 0x47, + 0x8f, 0x4e, 0x7b, 0x59, 0x16, 0xe1, 0xbb, 0x5f, 0x73, 0x5b, 0xdb, 0xe4, 0x29, 0x80, 0x7d, 0x1a, 0xb1, 0x47, 0x8f, + 0x2e, 0x1e, 0x52, 0xf8, 0x7e, 0x69, 0xda, 0xba, 0x9c, 0x8c, 0xb3, 0x4b, 0x68, 0xeb, 0x03, 0x4c, 0xe7, 0xec, 0xf2, + 0x34, 0x37, 0x7d, 0x7d, 0x30, 0xa3, 0xee, 0x4d, 0xce, 0x26, 0x67, 0x26, 0xd3, 0x0c, 0xb5, 0xfa, 0xfc, 0x99, 0xa5, + 0x51, 0xc6, 0xf2, 0x6e, 0x84, 0xef, 0xdc, 0xc2, 0x3d, 0x3a, 0xeb, 0x74, 0xf2, 0xd3, 0x08, 0xe7, 0x8f, 0x17, 0x8b, + 0x77, 0x06, 0x82, 0xdd, 0xb3, 0x47, 0xf6, 0x5b, 0x7d, 0xbe, 0x87, 0xa6, 0xc7, 0x06, 0x68, 0x39, 0x9f, 0x9b, 0x96, + 0x2f, 0x1e, 0xc1, 0x7f, 0xe6, 0xdb, 0x34, 0x5d, 0x7d, 0xcb, 0x7c, 0x6a, 0x17, 0xa5, 0xcb, 0x1e, 0x75, 0xa0, 0xc6, + 0x84, 0xff, 0x3a, 0x2e, 0x39, 0xa0, 0xd1, 0xb8, 0x07, 0xff, 0x17, 0xe1, 0x49, 0x71, 0xf3, 0xc6, 0xe1, 0xec, 0x64, + 0x42, 0x27, 0x9d, 0x08, 0x4f, 0xe4, 0xaf, 0x4a, 0xff, 0xf2, 0x58, 0xa4, 0x51, 0xaf, 0x77, 0x39, 0x36, 0x65, 0x96, + 0x3f, 0x28, 0x6e, 0xf0, 0xb8, 0x63, 0x5a, 0x99, 0xd2, 0x77, 0x6a, 0x7c, 0x23, 0x61, 0x25, 0xe1, 0xbf, 0x08, 0x4f, + 0x41, 0x0b, 0xe7, 0x5a, 0xb9, 0xb4, 0xdb, 0x61, 0xfa, 0xde, 0xa0, 0x66, 0xfe, 0x10, 0xe0, 0xe5, 0x97, 0x31, 0xa7, + 0xf4, 0xbc, 0xd7, 0x89, 0xb0, 0x19, 0xf5, 0x65, 0x07, 0xfe, 0x8b, 0xb0, 0x85, 0x9c, 0x81, 0xeb, 0xf4, 0xd7, 0x17, + 0x3f, 0xde, 0xa6, 0x11, 0xcd, 0x27, 0x13, 0x58, 0x12, 0x33, 0x19, 0x5f, 0x6c, 0x26, 0x05, 0xbb, 0xff, 0xe9, 0xd6, + 0x6d, 0x17, 0x93, 0xa0, 0x1d, 0x74, 0x2e, 0x1e, 0x8d, 0xcf, 0x22, 0xfc, 0x2e, 0xe7, 0x54, 0xc0, 0x2a, 0x65, 0xf9, + 0x79, 0x76, 0x9e, 0x99, 0x84, 0xa9, 0x4c, 0xa3, 0x33, 0x58, 0xf2, 0x5e, 0x84, 0xf9, 0x97, 0x9b, 0x7b, 0x8b, 0x6e, + 0x50, 0xdb, 0x21, 0xc8, 0xa4, 0xc3, 0x2e, 0x2e, 0xb3, 0x08, 0x17, 0xf4, 0xcb, 0x8b, 0x9f, 0xca, 0x34, 0x62, 0x17, + 0xec, 0x62, 0x42, 0xfd, 0xf7, 0xbf, 0xd4, 0xcc, 0xd4, 0xe8, 0x4c, 0xce, 0x21, 0xe9, 0x56, 0x98, 0xb1, 0x3e, 0xcc, + 0x26, 0x06, 0x43, 0x5e, 0xcf, 0xa5, 0xc8, 0x9e, 0x4f, 0x26, 0xd2, 0x62, 0x31, 0x85, 0x4d, 0xf8, 0x1b, 0x40, 0x9b, + 0xe6, 0xf9, 0x25, 0xbb, 0x88, 0xf0, 0x6f, 0x76, 0x97, 0xb8, 0x09, 0xfc, 0x66, 0x31, 0x9b, 0xb9, 0xdd, 0xfe, 0x9b, + 0x05, 0x0a, 0xcc, 0x77, 0x42, 0x27, 0x34, 0xef, 0x45, 0xf8, 0x37, 0x03, 0x97, 0xfc, 0x14, 0xfe, 0x83, 0x02, 0xd0, + 0xd9, 0xa3, 0x0e, 0x63, 0x8f, 0x3a, 0xe6, 0x2b, 0xcc, 0x73, 0x33, 0x1f, 0x5f, 0x64, 0xdd, 0x08, 0xff, 0xe6, 0xd0, + 0x71, 0x32, 0xa1, 0x1d, 0x40, 0xc7, 0xdf, 0x1c, 0x3a, 0xf6, 0x3a, 0xe3, 0x1e, 0x35, 0xdf, 0x16, 0x6b, 0x2e, 0x1f, + 0x66, 0x0c, 0x26, 0xf7, 0x9b, 0x45, 0xc8, 0x87, 0x0f, 0x2f, 0x2f, 0x1f, 0x3d, 0x82, 0x4f, 0xd3, 0x76, 0xf5, 0xa9, + 0xf4, 0xe3, 0xc2, 0x20, 0x59, 0x27, 0x3b, 0x03, 0x3a, 0xf9, 0x9b, 0x19, 0xe3, 0x64, 0x32, 0x61, 0x9d, 0x08, 0x17, + 0x7c, 0xce, 0x2c, 0x26, 0xd8, 0xdf, 0xa6, 0xa3, 0xd3, 0x5e, 0x96, 0x9f, 0xf6, 0x22, 0x5c, 0xbc, 0x7b, 0x61, 0x66, + 0xd3, 0x81, 0xd9, 0xfb, 0x2d, 0xe7, 0xb1, 0x66, 0x4e, 0xdf, 0xc2, 0x20, 0x61, 0xa5, 0xa1, 0xf2, 0xc7, 0x80, 0x1e, + 0x5e, 0x5c, 0x64, 0x39, 0x0c, 0xf4, 0x23, 0x74, 0x0b, 0x60, 0xfc, 0x68, 0x37, 0xdf, 0x98, 0x9e, 0x9f, 0xc3, 0x74, + 0x3f, 0x2e, 0x96, 0xe5, 0xe2, 0x75, 0x1a, 0x3d, 0x3a, 0x7d, 0xd8, 0xc9, 0xc7, 0x11, 0xfe, 0xe8, 0x26, 0x78, 0x9a, + 0x8d, 0x4f, 0x1f, 0x76, 0x23, 0xfc, 0xd1, 0xec, 0xb7, 0x87, 0xe3, 0x8b, 0x4b, 0x38, 0x37, 0x3e, 0xaa, 0x45, 0xf9, + 0x6e, 0x6a, 0x0a, 0x4c, 0xe8, 0x23, 0x68, 0xf6, 0x67, 0xb3, 0x1b, 0xf3, 0x2e, 0x6c, 0xe4, 0x8f, 0x66, 0x93, 0x19, + 0x3c, 0x79, 0xd8, 0x3d, 0xbf, 0x3c, 0x8f, 0xf0, 0x9c, 0xe7, 0x02, 0x08, 0xbc, 0xd9, 0x28, 0x8f, 0xba, 0x8f, 0x1e, + 0x76, 0x22, 0x3c, 0x7f, 0xa7, 0xb3, 0x5f, 0xe9, 0xdc, 0x50, 0xe3, 0x09, 0xc0, 0x6c, 0xce, 0x95, 0xbe, 0x7f, 0xab, + 0x1c, 0x3d, 0x66, 0xdd, 0x08, 0xcf, 0x65, 0x96, 0x51, 0xf5, 0xce, 0x26, 0x8c, 0xcf, 0x23, 0x2c, 0xe8, 0x17, 0xfa, + 0xa7, 0xf4, 0x9b, 0x29, 0x67, 0x34, 0x37, 0x69, 0x06, 0x87, 0x23, 0xfc, 0x3e, 0x87, 0xcb, 0xc8, 0x34, 0x9a, 0xe4, + 0x93, 0x73, 0x00, 0x0f, 0x10, 0x20, 0x8b, 0xdd, 0x00, 0x0d, 0xf8, 0xca, 0x9f, 0x8c, 0xd3, 0xe8, 0x62, 0x7c, 0xc9, + 0x7a, 0xa7, 0x11, 0xae, 0xa8, 0x11, 0x3d, 0x87, 0x7c, 0xf3, 0xf9, 0xab, 0xd9, 0x52, 0x67, 0x36, 0xc1, 0x00, 0x28, + 0xa7, 0x0f, 0x3b, 0xf9, 0x45, 0x84, 0x17, 0x6f, 0x98, 0xdf, 0x63, 0x8c, 0xb1, 0x4b, 0x80, 0x25, 0x24, 0x19, 0x04, + 0xba, 0x9c, 0x8c, 0x1f, 0x5d, 0x9a, 0x6f, 0x00, 0x03, 0x9d, 0x30, 0x06, 0x40, 0x5a, 0xbc, 0x61, 0x15, 0x20, 0xf2, + 0xf1, 0xc3, 0x0e, 0xd0, 0x97, 0x05, 0x5d, 0xd0, 0x7b, 0x7a, 0xfb, 0x7c, 0x61, 0xe6, 0x34, 0xc9, 0xcf, 0x23, 0xbc, + 0x78, 0xf9, 0xc3, 0x62, 0x39, 0x99, 0x98, 0x09, 0xd1, 0xf1, 0xa3, 0x08, 0x2f, 0x58, 0xb9, 0x84, 0x35, 0xba, 0x3c, + 0x3f, 0x9d, 0x44, 0xd8, 0xa1, 0x61, 0xd6, 0xc9, 0xc6, 0x70, 0xdb, 0xba, 0x9c, 0xa7, 0x51, 0x9e, 0xd3, 0x4e, 0x0e, + 0x77, 0xaf, 0xf2, 0xf6, 0xa7, 0xd2, 0xa2, 0x11, 0x33, 0xf8, 0xe0, 0xd6, 0x10, 0xe6, 0x0b, 0xf0, 0xf8, 0x75, 0xcc, + 0xb2, 0x8c, 0xba, 0xc4, 0x8b, 0x8b, 0xd3, 0x53, 0xc0, 0x3d, 0x3b, 0x43, 0x8b, 0x20, 0x6f, 0xd5, 0xfd, 0xb8, 0x94, + 0x70, 0x74, 0x01, 0x51, 0x05, 0xb2, 0xfa, 0xf6, 0xfe, 0x8d, 0xa1, 0xab, 0xdd, 0x8b, 0x47, 0xb0, 0x00, 0x8a, 0xe6, + 0xf9, 0x6b, 0x7b, 0xb8, 0x5d, 0x8e, 0xcf, 0xce, 0xbb, 0xa7, 0x11, 0xf6, 0x1b, 0x81, 0x5e, 0x76, 0x1e, 0xf6, 0xa0, + 0x84, 0xc8, 0xef, 0x6d, 0x89, 0xc9, 0x19, 0x3d, 0xbb, 0xe8, 0x44, 0xd8, 0x6f, 0x0d, 0x76, 0x39, 0x3e, 0x7f, 0x08, + 0x9f, 0x6a, 0xc6, 0x8a, 0xc2, 0xe0, 0xf7, 0x39, 0xc0, 0x45, 0xf1, 0x17, 0x82, 0xa6, 0x11, 0xed, 0x9c, 0xf7, 0x7a, + 0x39, 0x7c, 0x16, 0x5f, 0x58, 0x99, 0x46, 0x59, 0x07, 0xfe, 0x8b, 0x70, 0xb0, 0x93, 0xd8, 0x38, 0xc2, 0x06, 0xef, + 0x2e, 0xe8, 0xb9, 0xd9, 0xfb, 0x6e, 0x57, 0x75, 0x2e, 0x3b, 0xb0, 0x61, 0xdd, 0xa6, 0x72, 0x5f, 0x4a, 0xc8, 0x5b, + 0x47, 0x62, 0x69, 0x84, 0x03, 0x04, 0x9d, 0x3c, 0x9c, 0x44, 0xd8, 0xef, 0xb8, 0xb3, 0x8b, 0xcb, 0x1e, 0x90, 0x32, + 0x0d, 0x84, 0x22, 0xef, 0x8d, 0xcf, 0x80, 0x34, 0x69, 0xf6, 0xc6, 0xe2, 0x49, 0x84, 0xf5, 0x73, 0xa5, 0x5f, 0xa7, + 0x51, 0x7e, 0x39, 0x9e, 0xe4, 0x97, 0x11, 0xd6, 0x72, 0x4e, 0xb5, 0x34, 0x14, 0xf0, 0xf4, 0xec, 0x61, 0x84, 0x0d, + 0x9a, 0x77, 0x58, 0x27, 0xef, 0x44, 0xd8, 0x1d, 0x25, 0x8c, 0x5d, 0xf6, 0x60, 0x5a, 0xdf, 0xbf, 0xd4, 0x80, 0xcb, + 0x39, 0x1b, 0x9f, 0x46, 0xb8, 0xa2, 0xf7, 0x86, 0x10, 0xc1, 0x97, 0x9a, 0xcb, 0xcf, 0x8e, 0xf5, 0x00, 0x52, 0xe7, + 0x37, 0x3c, 0x2c, 0xc3, 0x8f, 0xb7, 0x16, 0x8d, 0xa8, 0xd9, 0xe2, 0xc1, 0x6d, 0xf4, 0x33, 0x1a, 0x7b, 0xb6, 0x9d, + 0x93, 0xd5, 0x06, 0x57, 0x41, 0x5e, 0x3f, 0xb3, 0x7b, 0x15, 0x4b, 0x65, 0x38, 0xd9, 0x20, 0x45, 0x29, 0xe4, 0xdd, + 0x1a, 0x9c, 0xe7, 0x2a, 0x08, 0x92, 0x82, 0x74, 0xfa, 0xe2, 0xca, 0x7b, 0xd3, 0xf6, 0x05, 0x84, 0x7e, 0x80, 0xf4, + 0x92, 0x50, 0xa2, 0x21, 0x42, 0x8e, 0x15, 0x26, 0xbd, 0x93, 0x81, 0x91, 0x29, 0xa5, 0x75, 0x5b, 0xa0, 0x84, 0xfa, + 0xd8, 0xf8, 0xb1, 0xc4, 0x0a, 0xa2, 0x47, 0xa1, 0xbe, 0x24, 0x26, 0xd2, 0xf5, 0x2b, 0xa1, 0x63, 0xa9, 0x86, 0xe5, + 0x08, 0x77, 0x2f, 0x10, 0x86, 0x18, 0x12, 0x64, 0x28, 0xaf, 0xaf, 0xbb, 0x17, 0x47, 0x46, 0xe8, 0xbb, 0xbe, 0xbe, + 0xb4, 0x3f, 0xe0, 0xdf, 0x51, 0x1d, 0xb7, 0x1b, 0xc6, 0xf7, 0x91, 0xd5, 0x73, 0x7c, 0x6f, 0xf8, 0xeb, 0x8f, 0x6c, + 0xbd, 0x8e, 0x3f, 0x32, 0x02, 0x33, 0xc6, 0x1f, 0x59, 0x62, 0xee, 0x48, 0xac, 0x87, 0x10, 0x19, 0x82, 0xe6, 0xac, + 0x83, 0x21, 0x9a, 0xbc, 0xe7, 0xbc, 0x3f, 0xb2, 0x21, 0x6f, 0x7a, 0x97, 0xd7, 0x21, 0x9c, 0x8f, 0x8e, 0x56, 0x65, + 0xaa, 0xad, 0x98, 0xa0, 0xad, 0x98, 0xa0, 0xad, 0x98, 0xa0, 0xeb, 0x20, 0xfa, 0x67, 0x03, 0x90, 0x52, 0x8c, 0xb2, + 0xc5, 0xf1, 0xd4, 0x3f, 0x82, 0xda, 0x03, 0xb4, 0x93, 0x83, 0x5a, 0xd9, 0x51, 0xe9, 0x2a, 0xf6, 0x2a, 0x30, 0xf6, + 0x26, 0x3a, 0x6d, 0xc7, 0xc9, 0x7f, 0xa2, 0xee, 0x78, 0xd7, 0x10, 0xcb, 0x7e, 0xdc, 0x2b, 0x96, 0xc1, 0x4a, 0x1a, + 0xd1, 0xec, 0xd0, 0xc6, 0x23, 0xd1, 0xc3, 0x87, 0x46, 0x30, 0xab, 0x83, 0xe4, 0xb5, 0x20, 0xa9, 0x0f, 0x52, 0xc8, + 0xa5, 0x91, 0xd2, 0x4a, 0x94, 0xe6, 0x3a, 0x2e, 0x41, 0x43, 0xe9, 0x15, 0x94, 0x55, 0x2c, 0xd7, 0x96, 0x01, 0x88, + 0xb2, 0x32, 0x9a, 0x95, 0xd5, 0xce, 0x41, 0x74, 0x01, 0x4d, 0x98, 0x91, 0x58, 0xa0, 0x01, 0x61, 0x1a, 0x10, 0xae, + 0x32, 0x88, 0x33, 0x2e, 0xfb, 0xcc, 0x64, 0x2b, 0x93, 0xad, 0xaa, 0x6c, 0xe9, 0xb3, 0xad, 0x90, 0x28, 0x4d, 0xb6, + 0xac, 0xb2, 0x41, 0x66, 0xc3, 0xd3, 0x54, 0xe1, 0x71, 0x2a, 0xad, 0xa8, 0x56, 0xcb, 0x56, 0x2f, 0x68, 0xa8, 0xcd, + 0x3d, 0x3a, 0x8a, 0x2b, 0x39, 0xc9, 0xa8, 0x89, 0x1f, 0xac, 0x78, 0x52, 0x1a, 0x19, 0x88, 0x27, 0x53, 0xf7, 0x77, + 0xbc, 0xd9, 0x96, 0x95, 0xca, 0xe9, 0xf8, 0x2b, 0x25, 0xd1, 0x1f, 0x5e, 0x89, 0xfa, 0x91, 0x9b, 0x28, 0x40, 0x57, + 0x24, 0xe9, 0x74, 0x4e, 0xbb, 0xa7, 0x9d, 0xcb, 0x01, 0x3f, 0xee, 0xf6, 0x92, 0x47, 0xbd, 0xd4, 0x28, 0x22, 0x16, + 0xf2, 0x16, 0x14, 0x30, 0x27, 0xbd, 0xe4, 0x0c, 0x1d, 0x77, 0x93, 0xce, 0xf9, 0x79, 0x1b, 0xfe, 0xc1, 0x4f, 0x74, + 0x55, 0xed, 0xac, 0x73, 0x76, 0x3e, 0xe0, 0x27, 0x5b, 0x95, 0x62, 0xde, 0x82, 0x82, 0xe8, 0xc4, 0x54, 0xc2, 0x50, + 0xbf, 0x5e, 0xde, 0xd7, 0x3b, 0x7a, 0x9e, 0x27, 0x3a, 0x96, 0x56, 0x15, 0x07, 0x50, 0xf5, 0x5f, 0x53, 0x03, 0x44, + 0xff, 0x35, 0xae, 0x22, 0xf5, 0xae, 0x4a, 0x10, 0xb5, 0x3f, 0xf2, 0x58, 0xb4, 0xd8, 0x71, 0x6c, 0xf3, 0x35, 0xd4, + 0x6d, 0x43, 0xf4, 0x3c, 0x3c, 0x75, 0xb9, 0x2a, 0xcc, 0x9d, 0x22, 0xd4, 0x56, 0x90, 0x3b, 0x76, 0xb9, 0x32, 0xcc, + 0x1d, 0x23, 0xd4, 0x96, 0x90, 0x4b, 0x53, 0x9e, 0x50, 0xc8, 0xd1, 0x09, 0x6d, 0x1b, 0x48, 0xd6, 0x8b, 0xf2, 0x92, + 0xf9, 0x61, 0xf3, 0x09, 0x2c, 0x8f, 0x21, 0x28, 0x4e, 0x90, 0x16, 0xf0, 0xc2, 0x4a, 0xa5, 0xcd, 0xe9, 0xe0, 0x4a, + 0x8d, 0x03, 0x19, 0x2d, 0xf8, 0xe7, 0x98, 0x99, 0x67, 0x37, 0x3a, 0x83, 0xd3, 0x8b, 0x4e, 0xda, 0x05, 0x57, 0x71, + 0x90, 0xb5, 0x85, 0x95, 0xb5, 0x85, 0x97, 0xb5, 0x85, 0x97, 0xb5, 0x41, 0x80, 0x0f, 0xfa, 0xfe, 0x97, 0x6c, 0x98, + 0xdf, 0xf0, 0xca, 0x96, 0xc7, 0x1a, 0x6b, 0xc4, 0x7a, 0xbd, 0x5e, 0x6d, 0xc0, 0xd2, 0xaa, 0xaa, 0x51, 0xaa, 0x5a, + 0xfd, 0xb9, 0x2a, 0xd3, 0x0e, 0x9e, 0xa6, 0xa0, 0xe5, 0xee, 0x60, 0x6a, 0x36, 0xb7, 0xa7, 0x0a, 0xdb, 0x51, 0x7c, + 0x06, 0x5e, 0x9d, 0x7c, 0x4d, 0x4e, 0x8d, 0xf6, 0x78, 0x55, 0xa6, 0xdc, 0xd2, 0x0c, 0x6e, 0x69, 0x06, 0xb7, 0x34, + 0x03, 0x1a, 0xc1, 0x55, 0x61, 0x53, 0x36, 0xa1, 0x04, 0xae, 0x04, 0x86, 0xa7, 0x23, 0x08, 0x62, 0x18, 0x6b, 0x62, + 0x46, 0xbd, 0xd5, 0x79, 0x17, 0x82, 0xb6, 0xd9, 0x92, 0x3a, 0xa1, 0xc6, 0x77, 0xbd, 0x1a, 0xf3, 0xdf, 0x0d, 0xb4, + 0x4f, 0xe0, 0x45, 0x9d, 0xc7, 0x3a, 0xee, 0x80, 0xe9, 0x4a, 0x54, 0x46, 0x03, 0x43, 0x16, 0x52, 0xa3, 0xb3, 0x71, + 0x26, 0xe9, 0x9f, 0xb7, 0x3c, 0x81, 0x2d, 0x25, 0x08, 0xdf, 0x91, 0xf8, 0xcc, 0xea, 0xd0, 0x04, 0x95, 0xc5, 0xad, + 0x33, 0x97, 0xb3, 0x47, 0x42, 0x1f, 0xcc, 0xe6, 0x7d, 0xcc, 0xab, 0x81, 0x20, 0x25, 0xc4, 0x7c, 0x4c, 0x4d, 0xa2, + 0x8b, 0xda, 0x0c, 0x4e, 0xcc, 0xe4, 0x0b, 0x35, 0x2e, 0x3d, 0xef, 0xed, 0x9f, 0xbf, 0x69, 0xe0, 0xf3, 0x58, 0x4e, + 0xc7, 0xde, 0x55, 0xf8, 0x93, 0x89, 0x6d, 0x44, 0x0e, 0x0f, 0xad, 0x45, 0xbb, 0xf9, 0xda, 0x36, 0x69, 0x37, 0x89, + 0x26, 0x1b, 0x76, 0xa8, 0x5f, 0xa3, 0x7f, 0x79, 0x8f, 0xbd, 0x72, 0x3a, 0x46, 0x01, 0xcd, 0x36, 0x60, 0x95, 0x35, + 0xb0, 0x94, 0xab, 0x57, 0x39, 0x72, 0x42, 0xef, 0x66, 0xcc, 0x9b, 0x72, 0x3a, 0xde, 0xfb, 0xf4, 0x8a, 0xed, 0x71, + 0xf0, 0x82, 0x06, 0x3d, 0x78, 0xd5, 0xf6, 0x8c, 0xdd, 0x7d, 0xab, 0xce, 0xe7, 0xbd, 0x75, 0x54, 0xf1, 0xad, 0x3a, + 0xaf, 0xf6, 0xd5, 0x99, 0xf3, 0xbb, 0xd8, 0xef, 0x1d, 0x1d, 0xa8, 0xb1, 0x8d, 0x99, 0xd4, 0x74, 0x0c, 0xb1, 0xf2, + 0xe1, 0xaf, 0x8d, 0x68, 0xd3, 0xf7, 0x24, 0x1c, 0x56, 0x41, 0x0e, 0x92, 0xf3, 0x94, 0x61, 0x4a, 0x7a, 0xc7, 0xa5, + 0x89, 0x69, 0x23, 0x12, 0xda, 0x56, 0x09, 0xc5, 0x05, 0x89, 0x63, 0x7a, 0x9c, 0x41, 0x64, 0x9e, 0xee, 0x80, 0xa6, + 0x31, 0x6d, 0x65, 0xe8, 0x24, 0xee, 0xb6, 0xe8, 0x71, 0x86, 0x50, 0xab, 0x0b, 0x3a, 0x53, 0x49, 0xba, 0xed, 0x02, + 0x62, 0x75, 0x1a, 0x52, 0x5c, 0x1c, 0x8b, 0xa4, 0x6c, 0xc9, 0x63, 0x95, 0x94, 0xad, 0xe4, 0x1c, 0x8b, 0x64, 0x5a, + 0x25, 0x4f, 0x4d, 0xf2, 0xd4, 0x26, 0x8f, 0xab, 0xe4, 0xb1, 0x49, 0x1e, 0xdb, 0x64, 0x4a, 0xca, 0x63, 0x91, 0xd0, + 0x56, 0xdc, 0x6d, 0x97, 0xe8, 0x18, 0x46, 0xe0, 0x47, 0x4f, 0x44, 0x18, 0x22, 0x7d, 0x63, 0x6c, 0x8c, 0x16, 0xb2, + 0x70, 0x41, 0x4b, 0x6b, 0x20, 0x55, 0x8e, 0x5f, 0x50, 0xe7, 0x75, 0x00, 0x26, 0xac, 0xed, 0x1f, 0x1f, 0x92, 0x6f, + 0x93, 0x15, 0x52, 0x04, 0x8e, 0x6d, 0x60, 0x8b, 0xff, 0xd9, 0xb9, 0xf3, 0x00, 0x54, 0x37, 0xb4, 0x58, 0xcc, 0xe8, + 0x8e, 0xf7, 0x70, 0x39, 0x1d, 0xbb, 0x9d, 0x55, 0x35, 0xc3, 0x68, 0x69, 0x43, 0x5d, 0x37, 0xfd, 0x3c, 0x01, 0xd4, + 0xde, 0xb7, 0x34, 0xa1, 0x46, 0x49, 0x6e, 0x6b, 0x4c, 0x4b, 0x76, 0xaf, 0x32, 0x5a, 0xb0, 0xb8, 0x3e, 0x80, 0xeb, + 0x61, 0x32, 0xf2, 0x0c, 0x3c, 0x02, 0xca, 0xe3, 0xe4, 0xb4, 0xa5, 0x93, 0xe9, 0x71, 0x72, 0xfe, 0xa8, 0xa5, 0x93, + 0xf1, 0x71, 0xd2, 0xed, 0xd6, 0x38, 0x9b, 0x94, 0x44, 0x27, 0x53, 0xa2, 0x41, 0x63, 0x68, 0x1b, 0x95, 0x0b, 0x0a, + 0x26, 0x6e, 0xff, 0xc1, 0x30, 0x5a, 0x6e, 0x18, 0x82, 0x4d, 0x6d, 0xd4, 0xcf, 0x9d, 0x31, 0x84, 0xdd, 0xf4, 0xce, + 0xcf, 0xdb, 0x3a, 0x29, 0xb1, 0xb6, 0x2b, 0xd9, 0xd6, 0xc9, 0x14, 0x6b, 0xbb, 0x7c, 0x6d, 0x9d, 0x8c, 0x6d, 0x53, + 0x46, 0x07, 0xc8, 0x44, 0x00, 0xac, 0x97, 0x2c, 0x80, 0x7c, 0xcf, 0x3b, 0xe9, 0x6c, 0x40, 0x6b, 0xf8, 0xad, 0x72, + 0x6d, 0x5f, 0x50, 0x51, 0x0d, 0xa6, 0x4e, 0xec, 0x6b, 0x45, 0xbb, 0x75, 0x93, 0xec, 0xdf, 0x97, 0xad, 0x9a, 0x2d, + 0xa5, 0x6e, 0x16, 0x7c, 0xde, 0xc0, 0x10, 0x57, 0xca, 0x1d, 0xdc, 0x7f, 0x52, 0x12, 0x43, 0x6c, 0x3f, 0x73, 0x0a, + 0x71, 0xe2, 0xf5, 0xc8, 0x90, 0xc4, 0x5b, 0xad, 0x0d, 0x8a, 0x83, 0xf3, 0xf6, 0x55, 0x48, 0x55, 0x77, 0x02, 0xfe, + 0x11, 0x12, 0x2d, 0x85, 0x35, 0x09, 0xcd, 0xa3, 0x9a, 0x16, 0xbf, 0x73, 0xda, 0xdd, 0xc6, 0x01, 0x71, 0x74, 0xb4, + 0x7d, 0x5e, 0xf8, 0xa7, 0x17, 0x76, 0x9e, 0x5b, 0xa8, 0xed, 0x09, 0xfd, 0x83, 0x50, 0xd6, 0xd2, 0x98, 0x07, 0x88, + 0xe2, 0x43, 0x6f, 0x3d, 0x34, 0x14, 0x7e, 0x58, 0xc7, 0x1d, 0x74, 0x39, 0xed, 0x0b, 0x93, 0x61, 0xfa, 0x1a, 0x05, + 0x63, 0x7b, 0x10, 0x4e, 0xa8, 0xb2, 0x95, 0xfc, 0xb7, 0x1d, 0x07, 0x9d, 0xb8, 0x07, 0x6b, 0xc2, 0x46, 0xff, 0x0c, + 0x2d, 0x93, 0x6b, 0xd8, 0x38, 0x9f, 0xf4, 0xf5, 0xba, 0xf1, 0x3c, 0x91, 0x7d, 0x04, 0x07, 0x1d, 0x1d, 0x71, 0xf5, + 0x02, 0x8c, 0xa9, 0x59, 0xdc, 0x0a, 0x0f, 0xdf, 0xbf, 0x1a, 0xa7, 0xf5, 0x9f, 0xe6, 0x5c, 0x4d, 0x83, 0x83, 0xee, + 0x71, 0x23, 0x7f, 0xef, 0x4a, 0x0c, 0x74, 0xca, 0xdd, 0x5a, 0x3f, 0xa9, 0x4d, 0xd5, 0x77, 0x1e, 0xca, 0x3a, 0x3a, + 0xe2, 0x75, 0xb8, 0xaa, 0xe8, 0xbb, 0x08, 0x0d, 0x8c, 0x0c, 0xf2, 0xa2, 0x90, 0x14, 0x6e, 0x44, 0xe1, 0x8a, 0x21, + 0x6d, 0xf1, 0x13, 0x8d, 0x7f, 0x90, 0xff, 0x8f, 0x1a, 0x39, 0xd6, 0x69, 0x8b, 0x07, 0x02, 0x58, 0xc8, 0x0a, 0xd5, + 0x81, 0x22, 0x0d, 0xa4, 0x43, 0xcb, 0x73, 0x54, 0x1d, 0xe6, 0x74, 0xb1, 0x28, 0xee, 0xcd, 0x5b, 0x61, 0x01, 0x47, + 0x55, 0x5f, 0x34, 0xb9, 0x28, 0x7d, 0xb8, 0x04, 0x9e, 0x1e, 0x70, 0x0f, 0x19, 0x2f, 0xdb, 0xea, 0x72, 0x5b, 0x20, + 0x90, 0xcc, 0x14, 0x91, 0xed, 0x6e, 0x5f, 0x5d, 0x83, 0x5c, 0xd6, 0x6e, 0x23, 0xed, 0x82, 0x97, 0x63, 0x0e, 0x32, + 0x99, 0xb2, 0x9e, 0xb4, 0x07, 0xb6, 0x20, 0x48, 0x6e, 0xd2, 0x88, 0x6c, 0xfb, 0x4b, 0xf1, 0x49, 0x0c, 0x68, 0x84, + 0xac, 0xc0, 0x17, 0x0a, 0x8b, 0x1c, 0xb8, 0xce, 0xd2, 0x77, 0xfc, 0x95, 0x96, 0xca, 0xa1, 0x1a, 0x8d, 0x70, 0x69, + 0x9e, 0xc7, 0xa8, 0xe6, 0x43, 0x55, 0xf0, 0xdc, 0x52, 0x20, 0xa2, 0xf0, 0xf5, 0xfa, 0x10, 0x5e, 0x33, 0x72, 0x6d, + 0x82, 0xeb, 0xad, 0xfb, 0x59, 0xbf, 0x5c, 0x02, 0xe3, 0x60, 0xa4, 0x63, 0x2e, 0x0a, 0x9d, 0xbc, 0xc9, 0xae, 0x44, + 0xbf, 0xd5, 0x62, 0x26, 0xd0, 0x14, 0x81, 0xa8, 0x72, 0xe0, 0x17, 0x09, 0x7f, 0x6c, 0xec, 0x28, 0xc5, 0x6c, 0x04, + 0x3e, 0x08, 0x0d, 0xde, 0x48, 0x58, 0xaf, 0x95, 0x8d, 0xf0, 0x62, 0x72, 0x6c, 0xac, 0x97, 0xaa, 0x9f, 0x2a, 0x94, + 0x6c, 0x6d, 0xc6, 0xc1, 0xdd, 0x56, 0x7f, 0x57, 0xef, 0xe7, 0x03, 0x6e, 0xaf, 0xf1, 0xb8, 0x89, 0x9b, 0x60, 0x00, + 0xb5, 0xda, 0xda, 0xe0, 0xd6, 0xce, 0x3f, 0xb6, 0x46, 0xc9, 0x6c, 0x1b, 0x82, 0xa2, 0x8a, 0x13, 0x60, 0x6f, 0x6e, + 0x7d, 0xdc, 0x44, 0x65, 0xe6, 0xa4, 0x90, 0x1e, 0x82, 0x1c, 0x3d, 0x22, 0xd0, 0xb9, 0xfd, 0x59, 0xd3, 0x85, 0x5a, + 0x26, 0xae, 0xc6, 0xf8, 0xcf, 0xe0, 0x36, 0x6f, 0x18, 0x7d, 0xfa, 0x64, 0x36, 0xf9, 0xa7, 0x4f, 0x11, 0x0e, 0x8d, + 0xeb, 0xa3, 0x80, 0x17, 0x8c, 0x46, 0x55, 0x68, 0x2d, 0xb3, 0xf1, 0xdb, 0xdd, 0xba, 0xb1, 0x8f, 0xb4, 0xc6, 0x3b, + 0x58, 0x1e, 0xd3, 0xf8, 0x8e, 0x33, 0xea, 0x90, 0x03, 0xbc, 0xd9, 0x90, 0x8f, 0xfa, 0x0f, 0x62, 0x85, 0x8e, 0x8e, + 0x1e, 0xc4, 0x12, 0x0d, 0x6e, 0x98, 0xb9, 0x73, 0x03, 0x6f, 0xf4, 0x21, 0x37, 0xc3, 0x97, 0x01, 0x02, 0xdc, 0xb0, + 0x6d, 0xc9, 0xe6, 0x9d, 0x89, 0xfd, 0x91, 0x42, 0x6c, 0x71, 0x88, 0x70, 0xec, 0x40, 0x02, 0xbd, 0x3e, 0x08, 0xa1, + 0xdd, 0x67, 0x84, 0x01, 0x0b, 0x5f, 0xf9, 0x0a, 0xb2, 0x64, 0xce, 0xca, 0x29, 0x2b, 0xd7, 0xeb, 0x8f, 0xd4, 0xfa, + 0xff, 0x6d, 0x85, 0xaa, 0x54, 0xfd, 0x56, 0x8b, 0x9a, 0xf1, 0x83, 0xf8, 0x40, 0x47, 0xf8, 0xf0, 0x41, 0x5c, 0x22, + 0x04, 0x16, 0x46, 0x5c, 0x2c, 0xbd, 0xaf, 0x5b, 0xd6, 0x58, 0x97, 0x12, 0x55, 0x8d, 0x14, 0xa4, 0x83, 0x67, 0x24, + 0xab, 0xd6, 0xe8, 0x6a, 0xd6, 0x6f, 0xb5, 0x0a, 0x24, 0xe3, 0x6c, 0x58, 0x8c, 0x30, 0xc7, 0x25, 0x5c, 0xa6, 0xee, + 0xae, 0xc3, 0x82, 0x35, 0x28, 0x97, 0x9b, 0xef, 0xca, 0x8e, 0x35, 0x7d, 0x49, 0x37, 0xe1, 0xee, 0xa6, 0x01, 0x91, + 0xd8, 0x07, 0x64, 0x61, 0x81, 0xac, 0x3c, 0x90, 0x85, 0x01, 0xb2, 0x42, 0x83, 0x05, 0x04, 0x6d, 0x52, 0x28, 0xdd, + 0xa1, 0xe8, 0xcd, 0xf0, 0xa2, 0xce, 0x75, 0x05, 0x73, 0x13, 0xe1, 0xc2, 0x2d, 0x07, 0xb8, 0xb1, 0xb8, 0xb9, 0x2b, + 0xb2, 0x8a, 0x22, 0x13, 0x69, 0x17, 0xdf, 0x99, 0x3f, 0xc9, 0x1d, 0xbe, 0xb7, 0x3f, 0xee, 0x03, 0x65, 0xd2, 0x87, + 0x86, 0xb6, 0x81, 0xbb, 0xb8, 0x74, 0x51, 0x12, 0x01, 0x5a, 0xbb, 0x20, 0x8b, 0xa2, 0xf9, 0xee, 0x9c, 0xb2, 0xe1, + 0x30, 0x44, 0x8b, 0x28, 0x2c, 0x02, 0xd2, 0xf9, 0xfb, 0xef, 0x11, 0x1a, 0x08, 0x88, 0x66, 0xe4, 0x4e, 0xb6, 0x76, + 0x17, 0xb5, 0xa2, 0x24, 0x4a, 0x63, 0x1f, 0x2c, 0x03, 0x76, 0x46, 0x14, 0x05, 0x6f, 0xce, 0xd4, 0x51, 0xd6, 0x1a, + 0xc3, 0x30, 0x83, 0xaa, 0xc3, 0x7f, 0x5c, 0xaf, 0xb6, 0x83, 0x2d, 0x19, 0xa8, 0x0a, 0x13, 0xe9, 0x06, 0xd9, 0x87, + 0xd8, 0x18, 0x61, 0x47, 0x47, 0x6c, 0x28, 0x46, 0xc1, 0xcb, 0x6a, 0xb5, 0x05, 0x89, 0x0e, 0x17, 0x2e, 0xce, 0x20, + 0xda, 0xfd, 0x7a, 0x6d, 0xff, 0x92, 0x5f, 0x8c, 0x34, 0x03, 0x4f, 0xe4, 0x05, 0x67, 0xac, 0xd8, 0x2f, 0x8b, 0x25, + 0x5a, 0x7e, 0x00, 0xcb, 0x3e, 0x17, 0xbb, 0x90, 0xbb, 0xa9, 0x76, 0x2b, 0x17, 0x1c, 0xa3, 0x51, 0x08, 0x22, 0x07, + 0xd7, 0x47, 0x1a, 0x5e, 0xe8, 0x30, 0xaf, 0x11, 0x01, 0xb8, 0x50, 0x55, 0x20, 0x57, 0x38, 0x52, 0x12, 0xb0, 0xf4, + 0x36, 0x74, 0x12, 0x7e, 0x34, 0xa9, 0xa4, 0x63, 0x21, 0x01, 0x0a, 0x1c, 0x99, 0xcb, 0x79, 0x13, 0xa8, 0x9f, 0xa1, + 0x3d, 0x44, 0x2e, 0x30, 0xa1, 0x69, 0xca, 0x96, 0x2e, 0xa2, 0x56, 0x34, 0x97, 0x4b, 0xc5, 0x96, 0x0b, 0x38, 0xdf, + 0xab, 0xb4, 0xac, 0xe0, 0xd9, 0xe7, 0x66, 0x0a, 0x18, 0x44, 0xde, 0xe9, 0x39, 0x13, 0xcb, 0xc8, 0xcd, 0xf3, 0xb5, + 0x15, 0xf7, 0xdf, 0xbe, 0xc2, 0x1f, 0x49, 0xef, 0xf8, 0x35, 0xfe, 0x8b, 0x92, 0x8f, 0xad, 0xd7, 0x78, 0xca, 0x89, + 0xe5, 0x0d, 0x92, 0xb7, 0x6f, 0x6e, 0x5e, 0xbd, 0x7f, 0xf5, 0xf1, 0xf9, 0xa7, 0x57, 0xaf, 0x5f, 0xbc, 0x7a, 0xfd, + 0xea, 0xfd, 0xaf, 0xf8, 0x5f, 0x94, 0xbc, 0x3e, 0xe9, 0x5e, 0x76, 0xf0, 0x07, 0xf2, 0xfa, 0xa4, 0x87, 0xef, 0x34, + 0x79, 0x7d, 0x72, 0x86, 0x67, 0x8a, 0xbc, 0x3e, 0xee, 0x9d, 0x9c, 0xe2, 0xa5, 0xb6, 0x4d, 0x16, 0x72, 0xda, 0xed, + 0xe0, 0xbf, 0xdc, 0x17, 0x88, 0xf7, 0x81, 0x1b, 0x0e, 0xdb, 0x32, 0x7e, 0x30, 0x65, 0xe8, 0x58, 0x19, 0x43, 0x94, + 0xab, 0x00, 0x9d, 0x72, 0x15, 0xa2, 0x93, 0x0d, 0x25, 0x0d, 0x36, 0x8c, 0x80, 0x56, 0x9c, 0xb8, 0x76, 0xf8, 0x49, + 0x97, 0x9d, 0x02, 0x7d, 0xe2, 0x95, 0x70, 0x5c, 0xa9, 0x70, 0xba, 0x4e, 0x8b, 0x31, 0x29, 0xa4, 0x2c, 0xe3, 0x25, + 0x30, 0x02, 0x46, 0x6b, 0xc1, 0x4f, 0xaa, 0x98, 0x55, 0xe2, 0x8a, 0x74, 0x07, 0xdd, 0x54, 0x5c, 0x91, 0xde, 0xa0, + 0x07, 0x7f, 0xce, 0x07, 0xe7, 0x69, 0xb7, 0x83, 0x8e, 0x83, 0x71, 0xfc, 0xd0, 0x40, 0xeb, 0xe1, 0x08, 0xbb, 0x2e, + 0xd4, 0x5f, 0xa5, 0xf6, 0x2a, 0x3d, 0xe1, 0xd4, 0xb1, 0xdd, 0xbe, 0xb8, 0x62, 0x46, 0x0f, 0xcb, 0xbf, 0x03, 0xd4, + 0x36, 0x6e, 0x35, 0xd5, 0xc6, 0x71, 0xbf, 0xf8, 0x89, 0x40, 0x8d, 0xc0, 0x38, 0x31, 0x5b, 0x77, 0x10, 0x30, 0x8d, + 0x26, 0x1b, 0xcc, 0x81, 0x12, 0x25, 0x4b, 0xed, 0x83, 0xfb, 0xab, 0xb6, 0x44, 0xc9, 0x42, 0x2e, 0xe2, 0x86, 0xaa, + 0xe1, 0xa7, 0xc0, 0xcc, 0xf1, 0x90, 0xab, 0xd7, 0xf4, 0x75, 0xdc, 0xe0, 0x79, 0x42, 0xd6, 0x2e, 0xdc, 0x16, 0xff, + 0x74, 0x56, 0x14, 0x0d, 0x70, 0x55, 0x80, 0xf5, 0xa3, 0x6a, 0xeb, 0x2b, 0x78, 0xc5, 0x90, 0xb5, 0xf4, 0x35, 0x09, + 0xa8, 0xe7, 0xcf, 0x95, 0x19, 0x57, 0xa5, 0x8c, 0xf6, 0x8a, 0x68, 0x63, 0x16, 0xe4, 0x15, 0xd1, 0x57, 0xca, 0x00, + 0x41, 0x12, 0x3e, 0x14, 0x23, 0x38, 0xf0, 0xed, 0x00, 0xa5, 0xa1, 0x73, 0xa0, 0x56, 0xaa, 0xcd, 0x84, 0xcc, 0xa7, + 0x09, 0xd1, 0x00, 0x9a, 0xa7, 0x5a, 0x05, 0x65, 0x3e, 0xb1, 0x44, 0xc1, 0xd0, 0x7f, 0x0b, 0x37, 0xc0, 0x71, 0x6c, + 0x50, 0x31, 0xb4, 0xab, 0x11, 0xcd, 0xfc, 0xee, 0x65, 0xe7, 0xe4, 0x75, 0x90, 0xbf, 0x54, 0xde, 0xde, 0xe3, 0xcf, + 0x80, 0x92, 0xdb, 0xa0, 0x62, 0x5d, 0xec, 0xe3, 0xc1, 0xf5, 0x43, 0x80, 0x1c, 0x6b, 0x74, 0x62, 0x1e, 0x74, 0xec, + 0x23, 0x7d, 0x4c, 0xba, 0x1d, 0x08, 0xe2, 0xb6, 0x87, 0xf2, 0xfd, 0xbc, 0x05, 0x53, 0x9d, 0xdc, 0xb5, 0x81, 0x56, + 0xc3, 0x1b, 0x4f, 0xf7, 0x6d, 0x9e, 0xdc, 0x63, 0x15, 0xe0, 0x0c, 0x3b, 0x66, 0x2d, 0x71, 0x2c, 0x90, 0x0b, 0x7e, + 0x6b, 0x37, 0x80, 0xa6, 0xa2, 0x67, 0xdf, 0x1a, 0xf4, 0xc6, 0x51, 0x57, 0xed, 0xe4, 0xfc, 0xf8, 0xf5, 0xd1, 0x51, + 0x2c, 0x5b, 0xe4, 0x23, 0xc2, 0x2b, 0x0a, 0x36, 0xdb, 0xe0, 0x7b, 0xc7, 0x2d, 0x13, 0x9f, 0xaa, 0x80, 0x3a, 0x4e, + 0x54, 0xe3, 0x58, 0xab, 0x3b, 0xab, 0x76, 0x83, 0x1f, 0x53, 0x0f, 0xb5, 0x82, 0x34, 0x3b, 0xba, 0x5e, 0x03, 0xca, + 0x0d, 0x47, 0x39, 0xd8, 0x96, 0xad, 0xbf, 0x28, 0xfa, 0xee, 0x63, 0xfb, 0x75, 0x30, 0xe1, 0x86, 0x69, 0xd2, 0xc7, + 0xd6, 0x47, 0xf4, 0xdd, 0xc7, 0xc0, 0xd5, 0x91, 0xd7, 0xec, 0x89, 0xe7, 0x46, 0x7e, 0xb6, 0x5c, 0xe9, 0xcf, 0x20, + 0xd9, 0x97, 0xe4, 0x67, 0xc0, 0x72, 0x4a, 0x7e, 0x8e, 0x65, 0x1b, 0x42, 0x40, 0x92, 0x9f, 0xe3, 0x12, 0x7e, 0x14, + 0xe4, 0xe7, 0x18, 0xb0, 0x1d, 0xcf, 0xcc, 0x8f, 0xb2, 0x02, 0x06, 0xb8, 0xd7, 0x49, 0xeb, 0x65, 0x57, 0xae, 0xd7, + 0xe2, 0xe8, 0x48, 0xda, 0x5f, 0xf4, 0x3a, 0x3b, 0x3a, 0x2a, 0xae, 0x66, 0x75, 0xdf, 0x5c, 0xef, 0xa3, 0x2f, 0x06, + 0xa1, 0x70, 0x60, 0x9a, 0xc6, 0xc3, 0x19, 0x7f, 0xdf, 0xa0, 0xac, 0xd0, 0x40, 0xfb, 0xb4, 0xf7, 0xf0, 0xe2, 0x12, + 0xc3, 0xbf, 0x0f, 0x83, 0x82, 0x3a, 0xf3, 0x13, 0x23, 0x5d, 0xd6, 0xbe, 0xa8, 0xeb, 0x5c, 0x07, 0xf8, 0x8c, 0x19, + 0x6a, 0x8b, 0xa3, 0x23, 0x7e, 0x15, 0xe0, 0x32, 0x66, 0xa8, 0x15, 0x58, 0xec, 0x3d, 0xae, 0xec, 0xc9, 0x0c, 0xd7, + 0x04, 0x8f, 0xfb, 0xf2, 0x61, 0x39, 0xba, 0xd2, 0x8e, 0x9a, 0x84, 0x21, 0xc0, 0x15, 0xe9, 0xb8, 0x4d, 0xd6, 0x17, + 0x6d, 0x75, 0xdd, 0xed, 0x23, 0x49, 0x54, 0x4b, 0x5c, 0x5f, 0x77, 0x31, 0xa8, 0xe4, 0x07, 0x8a, 0xc8, 0x54, 0x10, + 0xef, 0xa6, 0xb8, 0x2a, 0x64, 0xaa, 0xf0, 0x8c, 0xa7, 0xc2, 0xcb, 0xd9, 0x6f, 0xbc, 0xf5, 0xb4, 0x71, 0x1c, 0x35, + 0x3d, 0x33, 0x2c, 0x06, 0xaa, 0x72, 0x78, 0x84, 0x4d, 0xaa, 0x46, 0xf0, 0x76, 0x62, 0x85, 0x79, 0xcc, 0x7a, 0xf9, + 0x31, 0x88, 0x4d, 0xad, 0x5a, 0x5d, 0xc8, 0x84, 0xcf, 0x4d, 0xaa, 0x60, 0xa0, 0xa6, 0xf0, 0x15, 0x84, 0x3d, 0xcc, + 0x6a, 0xc3, 0x6c, 0xdf, 0x30, 0x14, 0x10, 0x50, 0xe0, 0x9a, 0xb0, 0x40, 0x82, 0xe7, 0x59, 0x83, 0x70, 0x34, 0xc9, + 0x85, 0x9d, 0xdc, 0x95, 0x82, 0xee, 0xc4, 0xe8, 0x4a, 0xf7, 0x91, 0x68, 0xb5, 0x1c, 0xb7, 0x7d, 0x2d, 0xcc, 0x20, + 0xda, 0xdd, 0xd1, 0x35, 0xeb, 0x23, 0xd5, 0x6e, 0x57, 0x06, 0x90, 0xd7, 0x9d, 0xf5, 0x5a, 0x5d, 0xf9, 0x46, 0x06, + 0xfe, 0x1c, 0x37, 0x7c, 0x97, 0x17, 0x3c, 0x7f, 0x93, 0x64, 0x18, 0x01, 0x55, 0x05, 0x3e, 0x5b, 0x2e, 0x22, 0x1c, + 0x99, 0x67, 0xf5, 0xe0, 0xaf, 0x79, 0x0e, 0x2d, 0xc2, 0x91, 0x7b, 0x69, 0x2f, 0x1a, 0xd5, 0x83, 0x15, 0x59, 0x15, + 0x24, 0x9e, 0x27, 0x9f, 0x80, 0x71, 0xd0, 0x7f, 0x2a, 0xb4, 0xaa, 0x7f, 0x27, 0x85, 0x0b, 0x97, 0xa2, 0xfc, 0xe3, + 0x6f, 0x6e, 0x54, 0x9b, 0xfd, 0x0e, 0xaa, 0x1c, 0x47, 0xbe, 0x2a, 0x3c, 0xa2, 0xf0, 0x8d, 0xd7, 0x27, 0xbb, 0xee, + 0xd1, 0xf3, 0x55, 0xd5, 0x03, 0x70, 0xde, 0x9b, 0x0d, 0xc2, 0xbf, 0xc9, 0xbd, 0x2f, 0x20, 0x47, 0x9f, 0xa4, 0x78, + 0x46, 0x35, 0x8d, 0x5a, 0x0f, 0x8c, 0xe1, 0x9b, 0x95, 0xb3, 0xfa, 0x5f, 0x1b, 0x07, 0xfb, 0x8f, 0xba, 0x87, 0x00, + 0x16, 0x8d, 0xc7, 0x9a, 0xac, 0xec, 0x6b, 0xc2, 0x96, 0xc8, 0xc0, 0xf4, 0x6d, 0x0f, 0x3c, 0xfc, 0x18, 0x29, 0xb8, + 0x55, 0x5b, 0x3e, 0x89, 0x42, 0x64, 0xd8, 0x86, 0x33, 0x37, 0xa4, 0xd8, 0x3e, 0x8c, 0xe3, 0xef, 0x06, 0x85, 0x5c, + 0xf7, 0x42, 0x35, 0x89, 0x69, 0xdd, 0x8d, 0x91, 0x3a, 0xd8, 0x36, 0x0b, 0xce, 0xea, 0xde, 0x8d, 0x84, 0x52, 0xbf, + 0x6b, 0x67, 0xde, 0x26, 0x6d, 0x77, 0xcd, 0x63, 0xcf, 0xf6, 0xf5, 0x3b, 0x05, 0x86, 0xbc, 0x87, 0x55, 0xd0, 0xae, + 0x6b, 0x38, 0x76, 0xe3, 0x00, 0xb2, 0x92, 0x5c, 0xaf, 0xdc, 0xcb, 0x74, 0x7c, 0x28, 0x47, 0x9b, 0xea, 0x9d, 0xba, + 0x00, 0x0f, 0xea, 0x91, 0xaa, 0x2c, 0xe4, 0x0c, 0xfc, 0x23, 0x8f, 0x0d, 0xfd, 0x10, 0xff, 0x1b, 0x0e, 0xf8, 0x1a, + 0x49, 0x53, 0xab, 0x7e, 0x82, 0xf7, 0xa3, 0x40, 0xe1, 0x6d, 0xeb, 0x7e, 0x2f, 0x43, 0x47, 0xdd, 0xa6, 0x4e, 0xc5, + 0xfa, 0xc2, 0x36, 0x15, 0x2b, 0x55, 0xe1, 0x80, 0x6a, 0xc5, 0x68, 0x93, 0x3a, 0xbf, 0x59, 0xf7, 0xe8, 0xd4, 0x63, + 0x01, 0xbe, 0x31, 0x5c, 0x8a, 0x17, 0x25, 0x44, 0x11, 0x0b, 0xf5, 0x69, 0xba, 0x0c, 0x5f, 0x55, 0x1e, 0xc2, 0x3d, + 0x61, 0xc5, 0x73, 0x56, 0x2f, 0x81, 0xc3, 0x02, 0x29, 0xa0, 0x50, 0x0a, 0x8b, 0xf5, 0x3a, 0x16, 0x26, 0xb6, 0x84, + 0x0b, 0x2d, 0xec, 0xde, 0x10, 0x31, 0xfa, 0x3b, 0xa8, 0x8b, 0xbd, 0x7a, 0xc4, 0x98, 0xb0, 0xa2, 0xf0, 0xd2, 0x49, + 0x66, 0x41, 0x5f, 0xfb, 0xfa, 0x10, 0xf5, 0x94, 0x07, 0xb1, 0xd1, 0xf7, 0xbe, 0xe7, 0x73, 0x26, 0x97, 0xf0, 0x78, + 0x13, 0x66, 0x44, 0x31, 0xed, 0xbf, 0x81, 0x82, 0xc0, 0x0b, 0x40, 0x3c, 0xc4, 0x47, 0xe0, 0xab, 0x3c, 0xad, 0x2b, + 0x33, 0xff, 0x24, 0x48, 0x64, 0x42, 0x76, 0x46, 0x83, 0x08, 0xbc, 0x88, 0x40, 0x84, 0x22, 0x24, 0x62, 0x22, 0x8f, + 0x06, 0x91, 0x71, 0xc9, 0x8a, 0xc0, 0x6a, 0x0c, 0x94, 0xdc, 0x11, 0x9e, 0xaa, 0x9a, 0x88, 0x85, 0x35, 0x75, 0x50, + 0x89, 0xa5, 0xc6, 0x4c, 0xfb, 0xa4, 0x57, 0x83, 0x90, 0x66, 0xdb, 0x82, 0xb2, 0xde, 0x52, 0x17, 0x60, 0x49, 0x8c, + 0xe9, 0x2d, 0x4f, 0x3e, 0x01, 0x37, 0xc7, 0x72, 0x57, 0x74, 0xc5, 0x6f, 0x40, 0x3d, 0x9d, 0x96, 0xf8, 0x93, 0x61, + 0xd8, 0xf2, 0x94, 0x6e, 0x08, 0xc7, 0x19, 0x29, 0x13, 0x7a, 0x07, 0xb1, 0x35, 0xe6, 0x5c, 0xa4, 0x05, 0x9e, 0xd3, + 0xbb, 0x74, 0x86, 0xe7, 0x5c, 0x3c, 0xb3, 0xcb, 0x9e, 0xe6, 0x90, 0xe4, 0x3f, 0x96, 0x1b, 0x62, 0x9e, 0x06, 0xfb, + 0xa0, 0x58, 0xf9, 0x04, 0x78, 0x15, 0x15, 0xa3, 0x7e, 0x6e, 0x6c, 0xca, 0xb9, 0xae, 0x8d, 0xd7, 0xdf, 0xe8, 0x98, + 0xe2, 0x0c, 0x17, 0x28, 0x29, 0x24, 0x66, 0x03, 0x91, 0xbe, 0x81, 0xb8, 0xda, 0x19, 0xb6, 0xcf, 0x8a, 0xf1, 0x3b, + 0x56, 0xbc, 0x90, 0xe5, 0x47, 0xb3, 0xe5, 0x0b, 0x04, 0x85, 0xc0, 0x45, 0x45, 0xb4, 0xe1, 0x76, 0x6f, 0x39, 0x90, + 0x75, 0x53, 0xf4, 0xce, 0x36, 0xe5, 0x86, 0x38, 0x83, 0x80, 0xc4, 0xc9, 0x8c, 0xb7, 0xba, 0x98, 0x0d, 0x3a, 0xdf, + 0x68, 0x74, 0x86, 0xaa, 0x92, 0x08, 0xc3, 0x5a, 0xb5, 0x55, 0x2a, 0x89, 0x68, 0x2b, 0x27, 0xe1, 0xad, 0x0c, 0xb0, + 0x53, 0x85, 0x33, 0xb9, 0x14, 0x3a, 0x95, 0x01, 0xde, 0x64, 0xf5, 0xe6, 0x5a, 0xdd, 0x59, 0x88, 0x69, 0x7c, 0x6f, + 0x7f, 0x30, 0xfc, 0xc9, 0xa8, 0xf8, 0xdf, 0x81, 0x61, 0x8f, 0x4a, 0x05, 0xc0, 0x0f, 0x0c, 0x67, 0x01, 0x72, 0x96, + 0x9f, 0xbc, 0x03, 0xf0, 0x59, 0x16, 0xf2, 0x1e, 0x52, 0x99, 0x49, 0xbd, 0x87, 0x54, 0x06, 0xa9, 0xc6, 0xa3, 0xfe, + 0x50, 0xd4, 0xca, 0xa2, 0xb0, 0x41, 0xa2, 0x70, 0xa5, 0x0e, 0x96, 0x44, 0x24, 0xd0, 0xae, 0x11, 0xe5, 0xe6, 0x5c, + 0x40, 0x68, 0x45, 0x68, 0xdc, 0x7e, 0xd3, 0x3b, 0xf8, 0xbe, 0xb7, 0xf9, 0xcc, 0xe7, 0xdf, 0xdb, 0x7c, 0xd3, 0x91, + 0xc7, 0xf8, 0xe6, 0x6d, 0xa7, 0xb1, 0x8c, 0x97, 0x0e, 0x6b, 0x3f, 0x54, 0x0f, 0xd9, 0x74, 0xcc, 0x83, 0xe1, 0xa4, + 0x8b, 0xe7, 0x01, 0x52, 0xb6, 0x6b, 0x1e, 0xae, 0x87, 0xbb, 0x9d, 0xe3, 0x98, 0xb7, 0x49, 0x17, 0xa1, 0x63, 0x27, + 0x5c, 0x89, 0xd8, 0x48, 0x4e, 0xc7, 0x1f, 0x4f, 0xe0, 0xee, 0x65, 0xac, 0xb6, 0x7c, 0xa5, 0x6c, 0xb5, 0x76, 0xb7, + 0x73, 0xcc, 0xf7, 0x56, 0x69, 0x75, 0xf1, 0x9c, 0x91, 0x15, 0x78, 0xa0, 0xd1, 0xd2, 0xaa, 0x1a, 0xc0, 0x65, 0xf5, + 0x95, 0xf8, 0x79, 0x49, 0x73, 0xf3, 0x7d, 0x6c, 0x53, 0xde, 0x2c, 0xb5, 0x4f, 0x6a, 0x73, 0x18, 0x44, 0x0f, 0xb9, + 0x92, 0x41, 0x4e, 0xcc, 0x4f, 0x48, 0x72, 0x8e, 0xae, 0xba, 0x83, 0xe4, 0xfc, 0x98, 0x1f, 0xf3, 0x14, 0x78, 0xd8, + 0xb8, 0xed, 0x2b, 0xb4, 0xbb, 0xbe, 0xce, 0xd3, 0xe5, 0x98, 0x67, 0xae, 0xf9, 0xba, 0x83, 0x2a, 0xd5, 0xce, 0x11, + 0xb2, 0x00, 0xc5, 0x7c, 0x2f, 0x41, 0x76, 0xb3, 0x9b, 0x63, 0x9e, 0x42, 0x3f, 0x50, 0xab, 0x67, 0x6b, 0x55, 0x83, + 0xfb, 0x79, 0x09, 0x08, 0xe6, 0x3b, 0x6a, 0xcc, 0xc5, 0xa6, 0xb7, 0xe3, 0xba, 0xb3, 0x63, 0x5e, 0x8f, 0x30, 0x2c, + 0xb3, 0xdb, 0x9f, 0x9f, 0x5a, 0xdd, 0xe5, 0x71, 0x00, 0x91, 0x9f, 0x97, 0x5c, 0x84, 0x9d, 0x86, 0xdd, 0xba, 0x9c, + 0xb0, 0xd3, 0xe6, 0x2c, 0x83, 0x22, 0xbb, 0xbd, 0xee, 0xcc, 0xb4, 0x39, 0xdb, 0x1b, 0x70, 0x24, 0x84, 0x49, 0x99, + 0x95, 0xce, 0xa4, 0x8a, 0xf9, 0xf1, 0x07, 0xe4, 0x5a, 0x7f, 0xb3, 0xd4, 0x3e, 0xbf, 0x42, 0x04, 0xc8, 0xae, 0xbb, + 0xae, 0xaa, 0x43, 0x1f, 0x55, 0x13, 0xaf, 0x8f, 0x79, 0xb0, 0x72, 0xcf, 0xef, 0x16, 0x32, 0xf5, 0xf8, 0x3a, 0xe8, + 0xa4, 0x3b, 0xc8, 0x09, 0xc4, 0xc3, 0x75, 0x17, 0x96, 0x05, 0x39, 0xbb, 0xbd, 0x83, 0x92, 0xe1, 0xc4, 0x7d, 0xe9, + 0x0f, 0xcc, 0x5e, 0x37, 0xf0, 0xab, 0xe4, 0x1c, 0xa6, 0xbe, 0xdd, 0xc3, 0x71, 0x0f, 0xfa, 0x30, 0x70, 0xd8, 0x6e, + 0xd0, 0x67, 0xd6, 0x10, 0x79, 0xca, 0x4b, 0x8b, 0x67, 0xd7, 0xa4, 0x3b, 0xe0, 0xa9, 0xdb, 0x4c, 0x46, 0x34, 0xea, + 0xb6, 0x79, 0x30, 0x33, 0xc0, 0x2f, 0x57, 0x36, 0x2c, 0xe2, 0xd7, 0x29, 0x80, 0x92, 0x2f, 0x56, 0xaf, 0x4f, 0x0d, + 0xaf, 0x66, 0xc3, 0xe9, 0x76, 0xba, 0x5f, 0x37, 0xb8, 0xdd, 0xf5, 0xf0, 0x84, 0x87, 0x68, 0x2c, 0x5a, 0xfb, 0x89, + 0xcf, 0x81, 0x03, 0x4a, 0x3a, 0x0f, 0xcf, 0xc1, 0x85, 0xb2, 0x82, 0xe5, 0x6e, 0xb9, 0xf1, 0x4e, 0x39, 0x0b, 0x47, + 0x5b, 0x32, 0xe0, 0x0e, 0xb6, 0x21, 0x0a, 0x1d, 0x1c, 0xf7, 0x70, 0xd2, 0xed, 0xf6, 0xce, 0x71, 0x72, 0x76, 0x0e, + 0x03, 0x6d, 0x25, 0xe7, 0xc7, 0x63, 0x65, 0x01, 0x18, 0xe4, 0x6c, 0x5c, 0xbb, 0x4f, 0x20, 0x68, 0x55, 0x28, 0x5e, + 0xf3, 0xe3, 0x38, 0xee, 0x26, 0x0f, 0x3b, 0xdd, 0xf3, 0xcb, 0x16, 0x00, 0xa8, 0xed, 0x3e, 0x5c, 0x8d, 0x37, 0x4b, + 0xdd, 0xac, 0x52, 0x21, 0x7c, 0xb3, 0x5a, 0xcb, 0x57, 0x6b, 0x75, 0x37, 0xf5, 0x14, 0x7c, 0x55, 0x27, 0x9c, 0xdb, + 0x22, 0x5e, 0x69, 0x13, 0x6e, 0x8b, 0xd8, 0x0e, 0x24, 0x06, 0xe9, 0x3c, 0x39, 0xef, 0x9d, 0x23, 0x3b, 0x16, 0xed, + 0xf0, 0xa3, 0xda, 0x27, 0x3b, 0x45, 0x5a, 0x1a, 0x90, 0xa4, 0x9a, 0x9d, 0x5c, 0x82, 0x44, 0xcd, 0xc9, 0x75, 0xb7, + 0x3d, 0x67, 0x89, 0x9f, 0x80, 0x49, 0x85, 0xe5, 0xac, 0x56, 0xc1, 0x25, 0x05, 0x80, 0xb8, 0x02, 0xe3, 0xa2, 0x87, + 0xe7, 0x83, 0x87, 0xc9, 0xf9, 0x45, 0xcf, 0x12, 0x3d, 0x7e, 0xd5, 0x6b, 0xa4, 0x99, 0xa9, 0x27, 0xe7, 0x26, 0x0d, + 0xba, 0x4e, 0x1e, 0x9e, 0x43, 0x19, 0x97, 0x12, 0x96, 0x82, 0x60, 0x1b, 0x75, 0x31, 0x88, 0xb0, 0x91, 0x36, 0x72, + 0x2f, 0x1a, 0xd9, 0x97, 0x67, 0xa7, 0x0f, 0xcf, 0x43, 0xa8, 0x55, 0xb3, 0x30, 0x0b, 0xed, 0x26, 0xe2, 0x67, 0x07, + 0x4b, 0x8b, 0x8e, 0x93, 0xf3, 0x74, 0x67, 0x82, 0x76, 0xd3, 0x1c, 0x1b, 0x1c, 0x08, 0x14, 0x8e, 0xcf, 0x85, 0xd3, + 0x97, 0x04, 0xf7, 0x63, 0xb5, 0xa1, 0x49, 0xa8, 0x70, 0xf6, 0xf7, 0x94, 0xc1, 0x7b, 0x9a, 0xe1, 0x55, 0xe5, 0x53, + 0x2a, 0xbe, 0x50, 0xf5, 0x96, 0x42, 0x04, 0x11, 0x31, 0x8a, 0x5c, 0x7c, 0xf3, 0x66, 0xee, 0x3f, 0xc1, 0x45, 0x98, + 0x09, 0xb8, 0xd0, 0xf4, 0x4a, 0xd0, 0x9a, 0x17, 0xf8, 0x14, 0x3a, 0xd4, 0x9a, 0x61, 0x0d, 0x78, 0xea, 0x4c, 0x0a, + 0x42, 0xdd, 0xd6, 0x4b, 0xfe, 0xad, 0x72, 0x49, 0x75, 0x95, 0x9d, 0x9c, 0xa3, 0xc4, 0x5d, 0x96, 0x27, 0x5d, 0x94, + 0x04, 0x26, 0x24, 0xee, 0x48, 0x2e, 0x32, 0x32, 0x8c, 0xee, 0x22, 0x1c, 0xdd, 0x47, 0x38, 0xb2, 0x3e, 0xcc, 0xbf, + 0x80, 0x1f, 0x77, 0x84, 0x23, 0xeb, 0xca, 0x1c, 0xe1, 0x48, 0x33, 0x01, 0x81, 0xc5, 0xa2, 0x11, 0x9e, 0x41, 0x69, + 0xe3, 0x59, 0x5d, 0x95, 0x7e, 0xea, 0xbf, 0x2a, 0xd7, 0x6b, 0x9b, 0x12, 0x48, 0x99, 0xb9, 0xd9, 0xa1, 0xf6, 0x61, + 0xec, 0x88, 0x7a, 0x66, 0x3d, 0xc2, 0x20, 0x80, 0xd0, 0x7b, 0xff, 0xb0, 0x5e, 0x1d, 0x93, 0x84, 0x9d, 0xc2, 0x4a, + 0x83, 0x2b, 0x7a, 0x14, 0x9e, 0x61, 0x11, 0x9e, 0x08, 0x5f, 0x18, 0xc4, 0x0a, 0xff, 0xbb, 0x90, 0x72, 0xe1, 0x7f, + 0x6b, 0x59, 0xfd, 0x82, 0xe7, 0x58, 0x9c, 0x45, 0x0b, 0x58, 0x6e, 0xd9, 0x10, 0x48, 0x63, 0xd6, 0x1c, 0xc1, 0xa7, + 0x89, 0x0b, 0x53, 0x07, 0x12, 0xe1, 0x27, 0x23, 0x50, 0x79, 0xf9, 0xf0, 0x93, 0x0d, 0x99, 0x64, 0x3e, 0x21, 0x66, + 0x1a, 0x84, 0x45, 0x96, 0x70, 0xa1, 0x31, 0x2d, 0x99, 0x52, 0x91, 0x8d, 0x25, 0x18, 0x49, 0xe1, 0x1f, 0x87, 0xf4, + 0x29, 0x13, 0x11, 0x99, 0x0e, 0x9b, 0xb3, 0xb5, 0xe2, 0x70, 0x21, 0x4b, 0x95, 0xda, 0x97, 0x62, 0x3c, 0x18, 0x17, + 0xd5, 0x33, 0x8c, 0xe9, 0x2c, 0xdb, 0x60, 0x7b, 0x87, 0x5d, 0x15, 0x72, 0x57, 0xda, 0x61, 0xa9, 0x22, 0xdb, 0x7c, + 0x6d, 0x42, 0xaa, 0x31, 0xa3, 0x60, 0xa2, 0xf5, 0x80, 0xea, 0xc0, 0x1d, 0x50, 0xd8, 0x06, 0xa5, 0x49, 0x57, 0x55, + 0xc9, 0x74, 0x55, 0x2d, 0xc3, 0x59, 0xa7, 0xb3, 0xd9, 0xe0, 0x92, 0x99, 0x40, 0x2e, 0x7b, 0x4b, 0x40, 0xbe, 0x9a, + 0xc9, 0xdb, 0x20, 0x57, 0xa5, 0xd5, 0x2c, 0xcd, 0x12, 0x45, 0x81, 0x11, 0x6c, 0xb4, 0xc1, 0x5f, 0xb8, 0xe2, 0x00, + 0x4f, 0x37, 0xbb, 0xb1, 0x94, 0x05, 0xa3, 0x10, 0x43, 0x2d, 0x68, 0x72, 0x83, 0x67, 0x3c, 0x67, 0xfb, 0xdb, 0x04, + 0x33, 0xe6, 0xff, 0xac, 0x45, 0x8f, 0x40, 0x96, 0xdd, 0x33, 0xa8, 0x03, 0x8b, 0xb8, 0x86, 0x0e, 0x42, 0x19, 0x7c, + 0x19, 0xe2, 0x66, 0x41, 0xef, 0xe5, 0x52, 0x03, 0x5c, 0x96, 0x5a, 0xbe, 0x75, 0xe1, 0x10, 0x0e, 0x3b, 0xd8, 0x47, + 0x46, 0x58, 0x41, 0xc8, 0x80, 0x0e, 0xb6, 0x11, 0x31, 0x3a, 0xd8, 0x05, 0x2a, 0xe8, 0x60, 0x13, 0x9e, 0xa2, 0xb3, + 0xa9, 0x62, 0x9b, 0xdd, 0x57, 0x4f, 0x6a, 0xd6, 0x9b, 0x60, 0xe2, 0xa4, 0x43, 0x4d, 0x74, 0x70, 0x7b, 0xc8, 0x08, + 0x6f, 0x7d, 0x7f, 0xf3, 0xe6, 0xb5, 0x8b, 0x5c, 0xcd, 0x27, 0xe0, 0xb2, 0xe9, 0x54, 0x63, 0xf7, 0x36, 0xc4, 0x7c, + 0xad, 0x28, 0xb5, 0xc2, 0xa9, 0x09, 0xf6, 0x29, 0x74, 0x91, 0xd8, 0xcb, 0x8b, 0x17, 0xb2, 0x9c, 0x53, 0x7b, 0x63, + 0x84, 0xef, 0x95, 0x7b, 0x7c, 0xde, 0xbc, 0x6f, 0x53, 0x4f, 0xf2, 0xfd, 0xf6, 0x55, 0xc4, 0x24, 0x33, 0xf2, 0x2b, + 0x68, 0x03, 0x4c, 0x65, 0x3f, 0x70, 0x56, 0x12, 0x17, 0xff, 0x3f, 0x20, 0x2f, 0xef, 0x2c, 0x75, 0x89, 0xa2, 0x16, + 0x37, 0xf8, 0xc9, 0xca, 0x5a, 0xc7, 0xc5, 0xcd, 0xfb, 0x91, 0xa4, 0xe3, 0xc4, 0x8b, 0xa8, 0x13, 0x35, 0xdf, 0xde, + 0x35, 0xaa, 0x04, 0x1f, 0x3b, 0x36, 0x29, 0x24, 0x88, 0x1e, 0xd5, 0x33, 0x7f, 0x1c, 0x44, 0x13, 0x7f, 0xf7, 0x7c, + 0xdd, 0xf5, 0x74, 0xb6, 0xa8, 0xd5, 0x89, 0xd5, 0x95, 0x09, 0x78, 0x38, 0xda, 0x87, 0x74, 0x10, 0x0e, 0x12, 0x59, + 0xa5, 0x3d, 0xf4, 0xb9, 0xa8, 0x1f, 0x17, 0x57, 0x5d, 0xd6, 0x3e, 0x5b, 0xaf, 0x8b, 0xeb, 0x2e, 0xeb, 0x9e, 0xdb, + 0x67, 0xf7, 0x22, 0x95, 0x01, 0xcd, 0xe5, 0x13, 0x9e, 0x45, 0xa0, 0x9d, 0x5d, 0x64, 0x26, 0x9c, 0x82, 0x97, 0xa6, + 0xc9, 0x52, 0xd7, 0x7d, 0x49, 0x30, 0x2e, 0x25, 0x56, 0x8f, 0x5f, 0xa2, 0x41, 0x37, 0xdd, 0x75, 0x95, 0x6e, 0x77, + 0x8f, 0x83, 0x0b, 0x97, 0x12, 0xe1, 0x1e, 0x84, 0x3c, 0x00, 0xfd, 0xee, 0x4a, 0x80, 0x69, 0x10, 0xa0, 0xb2, 0x02, + 0x91, 0x96, 0xcf, 0x97, 0xf3, 0x17, 0x25, 0x35, 0xcb, 0xf0, 0x8c, 0x4f, 0xb9, 0x56, 0x29, 0x05, 0xe9, 0x76, 0x5f, + 0xfa, 0x66, 0xbf, 0x04, 0x95, 0x35, 0xe2, 0xef, 0x26, 0x9a, 0x67, 0x9f, 0x95, 0x5b, 0x38, 0x84, 0xcd, 0xca, 0x0a, + 0x9c, 0xa1, 0x0d, 0x2e, 0xe4, 0x94, 0x96, 0x5c, 0xcf, 0xe6, 0xff, 0xd1, 0xea, 0xb0, 0xa1, 0x1e, 0x99, 0x0b, 0x2b, + 0x00, 0x09, 0x15, 0xf9, 0x7a, 0xcd, 0x4f, 0xbe, 0x7d, 0x9f, 0xe4, 0x7d, 0xc2, 0xbb, 0xb8, 0x87, 0x4f, 0xf1, 0x39, + 0xee, 0x76, 0x70, 0xf7, 0x1c, 0xae, 0xee, 0xb3, 0x62, 0x99, 0x33, 0x15, 0xc3, 0xfb, 0x6b, 0xfa, 0x3a, 0xb9, 0x3c, + 0xae, 0x5f, 0x1d, 0x28, 0x13, 0x87, 0x2e, 0x41, 0xf0, 0x7b, 0x17, 0x35, 0x30, 0x8a, 0xc2, 0x90, 0x75, 0x8b, 0x50, + 0x75, 0x52, 0xe9, 0x17, 0xae, 0x4f, 0x07, 0x60, 0xcf, 0x6d, 0x57, 0xb6, 0x0d, 0x66, 0xdf, 0xf6, 0x67, 0x5a, 0xff, + 0x6c, 0xeb, 0x0a, 0x31, 0x3c, 0xf4, 0x6a, 0xf4, 0x40, 0xd7, 0xa4, 0x7b, 0x74, 0x04, 0x56, 0x47, 0xc1, 0x6c, 0xb8, + 0x8d, 0x7e, 0xc0, 0xdb, 0x8d, 0x34, 0x08, 0x56, 0x00, 0xc6, 0x9d, 0x0f, 0x38, 0x59, 0x59, 0xd8, 0x6a, 0xa0, 0xc2, + 0xac, 0x0c, 0xe3, 0xea, 0x85, 0xa4, 0xc2, 0x08, 0xd1, 0x70, 0x84, 0xb9, 0x60, 0x28, 0x87, 0x1d, 0x2c, 0x27, 0x13, + 0xc5, 0x34, 0x1c, 0x1d, 0x25, 0xfb, 0xc2, 0x4a, 0x65, 0x4e, 0x91, 0x31, 0x9b, 0x72, 0xf1, 0x58, 0xff, 0xc6, 0x4a, + 0x69, 0x3e, 0x8d, 0x06, 0x23, 0x8d, 0xcc, 0x2a, 0x46, 0x38, 0x2b, 0xf8, 0x02, 0xaa, 0x4e, 0x4b, 0x70, 0xfa, 0x81, + 0xbf, 0x3c, 0x4f, 0xc3, 0x36, 0x81, 0x7c, 0xfd, 0x62, 0x63, 0xba, 0xe0, 0xbc, 0xa4, 0xb7, 0x6f, 0xc4, 0x53, 0xd8, + 0x51, 0x8f, 0x4b, 0x46, 0x21, 0x1b, 0x92, 0xde, 0x43, 0x53, 0xf0, 0x01, 0x6d, 0xfe, 0x68, 0x00, 0x97, 0x5e, 0x9a, + 0x0f, 0x5b, 0xd1, 0xc7, 0x6e, 0x4c, 0xaa, 0xb6, 0x4c, 0xa6, 0x39, 0xa5, 0xeb, 0x4c, 0x1b, 0x85, 0xaa, 0x9a, 0xc2, + 0x06, 0xbb, 0xa8, 0x27, 0xe1, 0x60, 0x72, 0xaa, 0x66, 0xe9, 0x70, 0x64, 0xfe, 0xbe, 0xb1, 0x25, 0x3b, 0xd8, 0x45, + 0x9c, 0xd9, 0x60, 0xf3, 0x70, 0x6a, 0x50, 0xbe, 0x8b, 0xe1, 0x1e, 0x16, 0x5e, 0xef, 0x6c, 0x90, 0xcf, 0x33, 0x4f, + 0x36, 0xcf, 0x36, 0x1b, 0x33, 0x10, 0x95, 0x82, 0x1e, 0xe8, 0x9d, 0xdf, 0x36, 0x1d, 0xd8, 0x1e, 0xd5, 0xd7, 0x79, + 0x07, 0xcf, 0x39, 0x3c, 0x46, 0xea, 0xdb, 0xbb, 0xd1, 0xa5, 0xfc, 0xec, 0x40, 0xd2, 0x09, 0x52, 0xec, 0x74, 0x82, + 0xce, 0x4e, 0x71, 0x30, 0x72, 0xa0, 0xe7, 0x37, 0x9f, 0x2d, 0xac, 0xfd, 0xef, 0xb7, 0x55, 0x41, 0x13, 0x4f, 0xa7, + 0x9a, 0x50, 0xe6, 0xcf, 0xcf, 0x07, 0x3c, 0xa9, 0x51, 0xc1, 0xbd, 0xe2, 0x05, 0x7b, 0xda, 0x06, 0xfa, 0x9c, 0xd3, + 0x3f, 0xed, 0x0f, 0x1b, 0xc3, 0xa7, 0xd2, 0xb2, 0x65, 0xa5, 0x54, 0xea, 0xb1, 0x4d, 0xb3, 0x47, 0x0f, 0x1c, 0x91, + 0x3f, 0x42, 0x17, 0xc0, 0xeb, 0xe7, 0xa5, 0x5c, 0x18, 0x44, 0x70, 0xbf, 0xdd, 0xb8, 0x8d, 0xaf, 0x00, 0x78, 0x3b, + 0x1c, 0xd4, 0xff, 0x74, 0x80, 0xfd, 0x8d, 0xaa, 0x92, 0x7e, 0xbc, 0x3d, 0x7b, 0xfc, 0x97, 0x12, 0xa2, 0xc6, 0x5b, + 0x3c, 0x4c, 0x1c, 0x3a, 0x55, 0xac, 0x59, 0xf5, 0x73, 0xa7, 0x24, 0x60, 0x58, 0xb3, 0x60, 0xc8, 0xc6, 0xed, 0x14, + 0xb7, 0x99, 0xff, 0x83, 0x0a, 0x06, 0x0b, 0xbe, 0x36, 0x92, 0x9a, 0x65, 0xf1, 0xdb, 0xa7, 0xc9, 0x7f, 0x35, 0x39, + 0xae, 0x43, 0xdd, 0x78, 0x29, 0x74, 0x6c, 0xa2, 0x34, 0x47, 0xe8, 0xe8, 0x68, 0x2b, 0x83, 0x4e, 0x00, 0xf0, 0xc8, + 0xb1, 0x5f, 0x7e, 0xf9, 0x3c, 0x3b, 0x66, 0x34, 0x8f, 0x65, 0x14, 0x32, 0x77, 0x9e, 0x9b, 0xb3, 0x13, 0x79, 0x46, + 0xd5, 0xcc, 0x17, 0x06, 0x38, 0x3e, 0xd9, 0x49, 0x05, 0x7c, 0x8f, 0x36, 0x7b, 0x26, 0xb0, 0xc5, 0x6f, 0xd9, 0x49, + 0xed, 0x2b, 0xe8, 0x17, 0x68, 0xb5, 0x8f, 0xa9, 0xdc, 0x5a, 0xe0, 0x68, 0x7b, 0x22, 0x7b, 0x87, 0xbe, 0x55, 0xa7, + 0x62, 0x3d, 0x5e, 0xed, 0x37, 0xfa, 0x92, 0x62, 0x5f, 0x72, 0x4d, 0xdb, 0xc6, 0xac, 0x7e, 0x2d, 0x58, 0xd7, 0xa6, + 0x4e, 0xf5, 0x35, 0x6f, 0x6d, 0x69, 0x53, 0xd9, 0x25, 0xd9, 0xbb, 0x2d, 0x16, 0x5e, 0x85, 0xb7, 0x5a, 0xd5, 0x45, + 0x28, 0xd8, 0x63, 0x89, 0x51, 0x9f, 0x13, 0xb8, 0x5e, 0x58, 0xaf, 0x63, 0xf8, 0xb3, 0x6f, 0x0c, 0xfb, 0x4c, 0x97, + 0x3e, 0xf0, 0x2d, 0x7e, 0x25, 0x08, 0x58, 0xec, 0xec, 0x20, 0xc1, 0xba, 0xcb, 0x0d, 0x1a, 0x8e, 0x13, 0xff, 0x05, + 0xcf, 0x65, 0x6b, 0xef, 0x72, 0x30, 0xcf, 0xbe, 0xf2, 0xc4, 0x5e, 0xc5, 0x5a, 0x36, 0xa2, 0xdd, 0x6f, 0x49, 0x30, + 0xc4, 0x6e, 0x4a, 0xe7, 0xb8, 0x95, 0x74, 0x51, 0xe4, 0x8a, 0xd5, 0xe8, 0xff, 0xb5, 0x22, 0x99, 0xcd, 0xfc, 0xaf, + 0x8b, 0x8b, 0x0b, 0x97, 0xe2, 0x6c, 0xfe, 0x94, 0xf1, 0x80, 0x33, 0x09, 0xec, 0x0b, 0xcf, 0x98, 0xd1, 0x21, 0xbf, + 0x83, 0xa1, 0x10, 0x41, 0xae, 0x85, 0x63, 0x97, 0xe0, 0xb5, 0x47, 0xa0, 0x3c, 0xc0, 0xfe, 0x3d, 0xdb, 0x2a, 0xe7, + 0x9f, 0x8b, 0xf2, 0xe1, 0x94, 0xab, 0x06, 0xd9, 0x17, 0xf3, 0x39, 0xb4, 0x66, 0x32, 0xf0, 0x42, 0x42, 0x84, 0xed, + 0x6f, 0xc3, 0xd2, 0x3a, 0x4b, 0x19, 0x1c, 0x69, 0xb9, 0xcc, 0x66, 0x56, 0xf3, 0xef, 0x3e, 0x4c, 0x59, 0xf7, 0xd4, + 0x10, 0x44, 0xee, 0x22, 0x2b, 0x17, 0x15, 0x34, 0xfa, 0x47, 0x15, 0x00, 0xf4, 0xe0, 0x35, 0x5b, 0xb2, 0x7f, 0xe0, + 0x83, 0x3a, 0x05, 0x3e, 0x1e, 0x97, 0x9c, 0x16, 0xff, 0xc0, 0x07, 0x75, 0x20, 0x50, 0x70, 0x85, 0x34, 0xb1, 0x34, + 0xb1, 0x79, 0x56, 0x3b, 0x8d, 0x04, 0x50, 0xd0, 0x22, 0x32, 0x07, 0xd9, 0x4b, 0x17, 0xa3, 0x31, 0xe9, 0x61, 0x17, + 0x1c, 0xcc, 0x46, 0x84, 0xb5, 0x81, 0xd4, 0x21, 0x6e, 0x5d, 0x35, 0x1b, 0xf3, 0xf5, 0x64, 0x6b, 0x41, 0x8c, 0x32, + 0x99, 0x5c, 0xbf, 0xe4, 0xf1, 0xce, 0x62, 0xa1, 0xb0, 0x5a, 0xb0, 0x40, 0x8d, 0x2a, 0x75, 0x7a, 0x58, 0x7c, 0xb7, + 0x60, 0x16, 0x14, 0x31, 0x5b, 0xef, 0xf1, 0x1d, 0x57, 0x04, 0xa4, 0x64, 0x97, 0x04, 0x2f, 0xa3, 0x1b, 0x4c, 0x25, + 0xab, 0xb9, 0xcc, 0x99, 0x25, 0xf4, 0x4c, 0xe9, 0x08, 0x9b, 0x3c, 0x05, 0x91, 0xc4, 0x0e, 0x3b, 0xd8, 0xb1, 0x46, + 0xaf, 0x84, 0x17, 0x52, 0xe0, 0x5c, 0x35, 0x4d, 0xcc, 0x29, 0x37, 0xd1, 0xc5, 0x1e, 0xab, 0x05, 0xcb, 0xb4, 0x45, + 0x80, 0x43, 0x87, 0x86, 0x52, 0xbc, 0x34, 0xa0, 0x30, 0x4f, 0x7a, 0xbb, 0x94, 0xa7, 0xb0, 0x78, 0x41, 0x0a, 0x10, + 0x35, 0x2e, 0xa6, 0x55, 0x9d, 0x45, 0xb1, 0x9c, 0x72, 0x51, 0x23, 0x43, 0xc9, 0xd4, 0x42, 0x0a, 0x78, 0x51, 0xa3, + 0x2a, 0x62, 0xe8, 0x50, 0x03, 0xdf, 0x2d, 0x09, 0xab, 0xea, 0x98, 0x63, 0x8a, 0x8b, 0xba, 0x06, 0x30, 0x17, 0x8f, + 0x8d, 0x80, 0xe8, 0xc3, 0xcb, 0xbe, 0x11, 0xef, 0xe5, 0xa2, 0xce, 0xf7, 0x34, 0xce, 0x07, 0xae, 0x77, 0x76, 0xc3, + 0x68, 0x63, 0x1e, 0xbd, 0x0a, 0xb6, 0xef, 0x07, 0x5e, 0x3f, 0x04, 0xb7, 0x31, 0xcf, 0x66, 0x55, 0x59, 0x63, 0x56, + 0xbd, 0x11, 0x51, 0xb7, 0xd7, 0xac, 0x2a, 0x85, 0xad, 0x08, 0x50, 0x29, 0x79, 0xbe, 0x93, 0xff, 0x4a, 0xdb, 0x7c, + 0x7b, 0x0e, 0x55, 0xe1, 0x81, 0x3c, 0x19, 0xaa, 0x7b, 0xc0, 0x65, 0xf5, 0x21, 0x80, 0xc5, 0x8f, 0x4c, 0xfc, 0xe0, + 0x7d, 0x17, 0xc8, 0x9c, 0xa9, 0x58, 0xe2, 0xd5, 0x90, 0x8e, 0x52, 0x2b, 0x0f, 0xa5, 0x12, 0x6c, 0x7b, 0x6e, 0x4b, + 0xae, 0x7d, 0xa0, 0x62, 0x3c, 0x64, 0xa3, 0x74, 0xd5, 0x0c, 0x66, 0x6c, 0xc3, 0x29, 0x7b, 0x73, 0x4e, 0x13, 0xfd, + 0x97, 0x8e, 0x70, 0x41, 0xc0, 0xf6, 0xd8, 0xb3, 0xa7, 0x0f, 0xe2, 0x0c, 0x0d, 0x9a, 0x1c, 0xfe, 0x6a, 0x83, 0x0b, + 0x9c, 0xa1, 0xf4, 0x71, 0x0c, 0x17, 0x58, 0x1b, 0x0c, 0xe0, 0xcb, 0x2c, 0xa9, 0x02, 0x8f, 0xd4, 0xcc, 0x48, 0xac, + 0xee, 0x22, 0x10, 0xad, 0x74, 0x78, 0x3b, 0xce, 0x7c, 0x38, 0x70, 0xc3, 0xbd, 0xbe, 0x30, 0xc2, 0xe1, 0x3c, 0x8b, + 0x1b, 0xe7, 0x0c, 0x27, 0xd7, 0x87, 0xbc, 0x71, 0x62, 0x82, 0xb5, 0x77, 0x78, 0xaa, 0x80, 0x1e, 0x0d, 0x4e, 0x15, + 0x4b, 0x43, 0x20, 0x66, 0x02, 0x78, 0x33, 0x87, 0x47, 0x5b, 0x80, 0xf3, 0xd1, 0x06, 0x07, 0x5f, 0x69, 0xa3, 0xab, + 0x6d, 0x25, 0xca, 0x66, 0x83, 0x87, 0x79, 0x86, 0x97, 0x19, 0x9e, 0x66, 0xa3, 0xf0, 0xb8, 0xc9, 0x42, 0x93, 0xae, + 0xf5, 0xfa, 0x95, 0x33, 0x23, 0x44, 0xf6, 0xa7, 0xa5, 0x3f, 0x68, 0x00, 0x08, 0x9f, 0x42, 0x16, 0xd0, 0x92, 0x81, + 0xfb, 0xdb, 0xb2, 0xaf, 0x85, 0xa3, 0x56, 0xcc, 0x13, 0x4b, 0x46, 0x06, 0xfe, 0x47, 0x95, 0x65, 0x5b, 0x6b, 0x45, + 0x8b, 0xbb, 0x83, 0xa8, 0xe5, 0xdb, 0xab, 0xcf, 0x97, 0x71, 0x65, 0xb6, 0x03, 0x88, 0x62, 0x8d, 0x93, 0x74, 0xb0, + 0x46, 0x72, 0xbd, 0x8e, 0x6d, 0x0a, 0xe1, 0xc9, 0x9c, 0x51, 0xb5, 0x2c, 0xcd, 0x03, 0x7a, 0xb1, 0x42, 0x89, 0xe1, + 0x77, 0xb1, 0xb3, 0x11, 0x85, 0xf7, 0xea, 0x24, 0x18, 0x6e, 0xc4, 0x82, 0xc8, 0x86, 0xc8, 0xfd, 0x29, 0xab, 0x2d, + 0x83, 0x04, 0x11, 0x46, 0xe4, 0xb7, 0xd7, 0xa5, 0xc2, 0x3e, 0xd1, 0x67, 0xff, 0x18, 0x5f, 0x40, 0xb8, 0x79, 0x9b, + 0xd2, 0x72, 0x4c, 0xa7, 0xc0, 0xc6, 0x42, 0x1c, 0xc2, 0x9d, 0x84, 0xf5, 0x7a, 0x38, 0xea, 0x0b, 0x43, 0x9e, 0xdd, + 0x03, 0x82, 0x55, 0x43, 0xfb, 0x1b, 0x80, 0xab, 0x6e, 0x4b, 0xcd, 0xb5, 0xd1, 0xfd, 0x50, 0xf3, 0xc6, 0x19, 0x77, + 0x49, 0xee, 0x99, 0x92, 0xfa, 0x25, 0xf2, 0x86, 0x05, 0xb8, 0x09, 0x5d, 0x85, 0x73, 0xbc, 0xb4, 0x36, 0x9c, 0xe6, + 0x41, 0x2b, 0x6a, 0xde, 0xb1, 0x82, 0xe7, 0xb3, 0x09, 0x1b, 0x66, 0x23, 0x9c, 0xfb, 0x70, 0xe7, 0x87, 0xef, 0xe2, + 0x1c, 0xa1, 0x92, 0x18, 0x98, 0x5a, 0x97, 0xed, 0xbc, 0xb6, 0xdb, 0x37, 0x99, 0x86, 0x61, 0x30, 0x46, 0xcc, 0x79, + 0x68, 0xc4, 0x5c, 0xb4, 0x5a, 0x68, 0x49, 0x72, 0x30, 0x62, 0x5e, 0x06, 0xad, 0x2d, 0xed, 0x63, 0xa7, 0x41, 0x7b, + 0x4b, 0x84, 0xfa, 0x1c, 0x68, 0x9a, 0x86, 0x67, 0x4d, 0xea, 0x67, 0xe5, 0xfd, 0x23, 0x5b, 0x27, 0x3d, 0x50, 0x24, + 0x4c, 0xae, 0xfd, 0x24, 0xac, 0x6b, 0xb8, 0x1d, 0xf7, 0xc4, 0x8c, 0xdb, 0xd9, 0x36, 0xa8, 0xa1, 0x1c, 0x66, 0xa3, + 0x51, 0x5f, 0x7a, 0x2b, 0x89, 0x0e, 0x9e, 0xd4, 0x0f, 0xa1, 0xd4, 0x8b, 0xf7, 0x45, 0x6f, 0x5f, 0x79, 0x73, 0xff, + 0xbe, 0xea, 0xf6, 0x79, 0x0c, 0x1c, 0xd0, 0x21, 0xdc, 0x0f, 0xd5, 0xf1, 0xc1, 0x4e, 0x7a, 0x10, 0x05, 0x2d, 0xed, + 0x34, 0x04, 0x52, 0x6b, 0x66, 0x17, 0xeb, 0xb6, 0x42, 0xc7, 0x02, 0xc2, 0x90, 0xa9, 0xba, 0xbb, 0x3b, 0x15, 0xa8, + 0x86, 0x38, 0x9c, 0xfa, 0x4f, 0xad, 0x11, 0x6b, 0x1c, 0xf5, 0xf2, 0xc8, 0x18, 0x49, 0xda, 0xe5, 0x83, 0xb7, 0x8f, + 0xc0, 0x4a, 0xc0, 0xc7, 0xa0, 0x36, 0x49, 0xc6, 0x90, 0xe0, 0x1d, 0xcb, 0xb4, 0xe1, 0x43, 0xb8, 0x43, 0x50, 0x9e, + 0xd8, 0xa0, 0xb4, 0xae, 0x92, 0x85, 0x5c, 0xdd, 0xe5, 0x7d, 0x80, 0x9e, 0x77, 0xd5, 0x6f, 0x6c, 0x38, 0xb2, 0x60, + 0x60, 0xd9, 0xce, 0x3e, 0x01, 0x8f, 0x7c, 0x5c, 0x23, 0x88, 0x5f, 0x0a, 0x9d, 0x98, 0x78, 0xdd, 0x37, 0xb0, 0x41, + 0xf1, 0x02, 0x1c, 0x04, 0x9d, 0x04, 0x87, 0xc1, 0xbb, 0xcc, 0x6a, 0x92, 0x0d, 0x6e, 0xcd, 0x49, 0xbc, 0x58, 0xaf, + 0x3b, 0xe8, 0xf8, 0x5f, 0xe6, 0x49, 0xea, 0x49, 0xa5, 0x70, 0x9f, 0xd4, 0x0a, 0x77, 0xb0, 0x04, 0x24, 0x93, 0x40, + 0xd7, 0x8e, 0x65, 0xa8, 0x46, 0x87, 0x68, 0xe9, 0xaf, 0x20, 0x76, 0xb6, 0x3b, 0x96, 0x40, 0xcf, 0xbe, 0x53, 0xc0, + 0xea, 0xda, 0xab, 0x12, 0xc8, 0x08, 0xee, 0x7e, 0x13, 0x18, 0x15, 0xa2, 0xf1, 0xf9, 0x33, 0xaf, 0x5a, 0xf0, 0xc4, + 0xf9, 0x73, 0xcd, 0x0d, 0xeb, 0x5e, 0xd2, 0x5b, 0xd3, 0x7c, 0x3c, 0xc1, 0xed, 0x89, 0x05, 0xe7, 0x49, 0x0f, 0x7e, + 0x5a, 0x88, 0x9e, 0xf4, 0xb0, 0x4b, 0xc5, 0x93, 0x0a, 0xc8, 0x21, 0x7a, 0x3a, 0x03, 0x29, 0x60, 0xa5, 0x63, 0xab, + 0x45, 0x9a, 0xa2, 0xf5, 0x7a, 0x7a, 0x45, 0x3a, 0x08, 0xad, 0xd4, 0x2d, 0xd7, 0xd9, 0x0c, 0x7c, 0xa4, 0x41, 0x31, + 0xf0, 0x96, 0xea, 0x59, 0x8c, 0xf0, 0x04, 0xad, 0x72, 0x36, 0xa1, 0xcb, 0x42, 0xa7, 0x6a, 0xc0, 0x13, 0x1b, 0xb8, + 0x97, 0xd9, 0x48, 0x70, 0x27, 0x3d, 0x3c, 0x35, 0xfc, 0xe5, 0x47, 0x63, 0x0e, 0x52, 0x66, 0x26, 0x79, 0x6a, 0x12, + 0x30, 0x4f, 0xb2, 0x42, 0x2a, 0x66, 0x9b, 0xe9, 0x5b, 0xdb, 0x72, 0x08, 0x49, 0x1e, 0xe9, 0x92, 0x1b, 0x2b, 0xca, + 0x28, 0x9d, 0x11, 0x35, 0x50, 0x27, 0xbd, 0x74, 0x8a, 0x79, 0x02, 0x9c, 0xde, 0x7b, 0x19, 0xb3, 0x56, 0x75, 0x2b, + 0x3a, 0x47, 0xc7, 0x33, 0x2c, 0xea, 0x4b, 0xd4, 0x39, 0x3a, 0x9e, 0x22, 0x3c, 0x6f, 0x91, 0x99, 0x02, 0x8f, 0x61, + 0x2e, 0xfe, 0x3f, 0x29, 0xff, 0xd5, 0x61, 0x43, 0x88, 0xe9, 0x77, 0xb0, 0x53, 0x58, 0x1e, 0xa5, 0x05, 0x01, 0xaf, + 0xc5, 0xee, 0x05, 0xce, 0xc8, 0xb4, 0x5d, 0xf8, 0x80, 0x7b, 0xa6, 0x95, 0xd6, 0x9d, 0x46, 0xc7, 0x19, 0xce, 0xb7, + 0x93, 0x62, 0x33, 0xd7, 0x76, 0x91, 0x66, 0x70, 0xbe, 0xd7, 0xa3, 0x70, 0xe5, 0x97, 0xdb, 0x49, 0x61, 0x79, 0x07, + 0xdc, 0x76, 0x8e, 0x45, 0x9b, 0xe2, 0x02, 0xcf, 0xdb, 0xaf, 0xf1, 0xbc, 0xfd, 0xa1, 0xca, 0x68, 0x2d, 0xb1, 0x80, + 0xe0, 0x7d, 0x90, 0x88, 0xe7, 0x75, 0x72, 0x8e, 0x45, 0xcb, 0x94, 0xc7, 0xf3, 0x56, 0x5d, 0xba, 0xbd, 0xc4, 0xa2, + 0x65, 0x4a, 0xb7, 0x3e, 0xe0, 0x79, 0xeb, 0xf5, 0xbf, 0x99, 0x74, 0x94, 0x02, 0xba, 0x2c, 0xd0, 0x2a, 0xb3, 0x43, + 0xbc, 0xf9, 0xf9, 0xdd, 0xfb, 0xee, 0xa7, 0xde, 0xf1, 0x14, 0xfb, 0xf5, 0xcb, 0x0c, 0x8e, 0x65, 0x3a, 0x66, 0x6d, + 0x80, 0x68, 0x86, 0x7b, 0xc7, 0x33, 0xdc, 0x3b, 0xce, 0x5c, 0x53, 0x9b, 0x79, 0x8b, 0xdc, 0xe9, 0x10, 0x8a, 0x3a, + 0x4a, 0x43, 0xf8, 0xf8, 0xc9, 0xa6, 0x53, 0xd4, 0x00, 0x25, 0x3a, 0x9e, 0x36, 0x40, 0x05, 0xdf, 0xcb, 0xc6, 0x77, + 0x5d, 0xaf, 0xc6, 0x20, 0x0b, 0x25, 0x14, 0xae, 0xb9, 0x01, 0x4f, 0x23, 0xc5, 0x40, 0x26, 0x4c, 0xb1, 0x40, 0xf9, + 0x06, 0x28, 0x8c, 0xf2, 0xc4, 0x0c, 0x3d, 0x98, 0x8e, 0x49, 0xfc, 0xff, 0x79, 0x32, 0xd5, 0xd0, 0xab, 0x2d, 0xb3, + 0x33, 0x3d, 0x37, 0x99, 0x70, 0xf8, 0xc0, 0x63, 0xfd, 0x6f, 0x3b, 0x50, 0x6c, 0x40, 0x8a, 0xff, 0x37, 0x1d, 0x5d, + 0x08, 0x46, 0xc8, 0x8a, 0xd2, 0xd2, 0x21, 0xfe, 0xb7, 0x87, 0x15, 0x74, 0x5f, 0xee, 0x74, 0x5f, 0x9a, 0xee, 0xc3, + 0xa6, 0x8d, 0x2a, 0x27, 0xad, 0x2b, 0x59, 0xf2, 0xdf, 0xa4, 0x5b, 0x3b, 0xa0, 0x11, 0x0d, 0x7a, 0x36, 0x0d, 0x1b, + 0x3c, 0xec, 0xa6, 0x7b, 0x90, 0x79, 0xc3, 0xed, 0x0b, 0xa9, 0x70, 0xf8, 0x06, 0x77, 0xaa, 0xd7, 0x1d, 0xf0, 0xde, + 0x54, 0x46, 0x5f, 0x19, 0x87, 0x96, 0x83, 0x74, 0xdb, 0x94, 0xdb, 0x18, 0x4b, 0x27, 0xe7, 0xd8, 0xb8, 0x22, 0x42, + 0xa5, 0xbb, 0x6b, 0x50, 0x8a, 0x4f, 0x74, 0x9b, 0x99, 0xaf, 0x2b, 0x9d, 0x98, 0x4b, 0xa8, 0x96, 0xf9, 0xbc, 0xbf, + 0xd6, 0x89, 0x96, 0x0b, 0x9b, 0x77, 0x7f, 0x05, 0x7d, 0x82, 0x86, 0xb5, 0x15, 0xd8, 0xed, 0x73, 0x67, 0x07, 0x19, + 0x1c, 0x82, 0xe1, 0x01, 0xe4, 0x48, 0x8b, 0xed, 0x03, 0x9b, 0xd6, 0xb0, 0xeb, 0xa2, 0x5d, 0x25, 0xda, 0x56, 0xdb, + 0x26, 0xd7, 0xee, 0x61, 0xbe, 0x08, 0x79, 0x0a, 0x51, 0x5a, 0xfd, 0xf8, 0x1e, 0x76, 0xe3, 0x4b, 0x83, 0x91, 0x68, + 0x2a, 0x99, 0x2a, 0xe8, 0x27, 0x77, 0x98, 0x25, 0xf7, 0xc6, 0x8b, 0x51, 0x19, 0x7f, 0x1f, 0x13, 0x97, 0x3f, 0xaa, + 0x25, 0x39, 0xb0, 0xec, 0x6f, 0xb1, 0xe4, 0x0e, 0xcc, 0x13, 0xab, 0x6a, 0x12, 0xeb, 0xe4, 0x3e, 0x58, 0x44, 0x69, + 0x1a, 0xd9, 0x18, 0x06, 0xd4, 0x34, 0x63, 0xd5, 0x83, 0x87, 0x10, 0xe8, 0x61, 0x50, 0x95, 0xd2, 0xae, 0xb3, 0xb4, + 0xd1, 0xbd, 0x36, 0xdd, 0x6f, 0x0f, 0xa8, 0x9e, 0xc6, 0x6d, 0xc0, 0x35, 0xfd, 0xbb, 0x49, 0x24, 0x63, 0xf6, 0x37, + 0x67, 0xe5, 0xd3, 0x65, 0x69, 0x30, 0x4d, 0x0c, 0x74, 0x92, 0x2d, 0xba, 0x60, 0xaa, 0x97, 0x2d, 0x7a, 0x77, 0xd8, + 0x7d, 0xdf, 0xdb, 0xef, 0x7b, 0x2c, 0x06, 0xcc, 0x64, 0xa4, 0xcc, 0x14, 0xf3, 0xdf, 0xf7, 0xf6, 0xfb, 0x1e, 0xef, + 0x0e, 0xe6, 0xb3, 0xbf, 0x50, 0xac, 0xd8, 0x19, 0x2e, 0xc1, 0x84, 0x3c, 0xe0, 0x6e, 0x1a, 0x59, 0x26, 0x08, 0x6c, + 0x23, 0x01, 0xe2, 0x7c, 0xbe, 0x8a, 0x6b, 0x5e, 0x0d, 0x01, 0xf7, 0xe9, 0x5d, 0xdb, 0xeb, 0x54, 0xe0, 0x31, 0x41, + 0x23, 0x62, 0x62, 0xdb, 0x98, 0xd7, 0xcd, 0x80, 0xcb, 0x23, 0xba, 0xd2, 0x93, 0x24, 0xc0, 0xab, 0x1a, 0x95, 0xb7, + 0x29, 0x52, 0x7d, 0x91, 0x20, 0xc7, 0x17, 0x7b, 0x42, 0x15, 0x03, 0x58, 0x55, 0x25, 0x7d, 0x02, 0x69, 0xe6, 0x87, + 0x9e, 0x9a, 0xdb, 0xc8, 0x63, 0xdf, 0xf9, 0xfd, 0xcc, 0xf4, 0xac, 0x94, 0xcb, 0xe9, 0x0c, 0x7c, 0x68, 0x81, 0x65, + 0x28, 0x4d, 0xbd, 0xda, 0xd6, 0xbf, 0x21, 0xb9, 0x09, 0xa0, 0x70, 0xba, 0x2d, 0x13, 0x9a, 0xe9, 0x25, 0x2d, 0x8c, + 0x25, 0x29, 0x17, 0xd3, 0x27, 0xf2, 0xee, 0x47, 0xc0, 0x6e, 0x4a, 0x74, 0x6b, 0x4f, 0xde, 0x3b, 0xd8, 0x01, 0x38, + 0x23, 0x6c, 0x5f, 0xc5, 0xc7, 0x0a, 0x74, 0xfe, 0xb8, 0x20, 0x6c, 0x5f, 0xd5, 0x67, 0xcc, 0x66, 0xcf, 0xc8, 0xd6, + 0x70, 0x07, 0x71, 0xd6, 0x2a, 0xd0, 0x49, 0x2f, 0x2d, 0xfa, 0x9e, 0x18, 0x58, 0x80, 0x06, 0xc0, 0xdd, 0xd9, 0x9e, + 0xd5, 0xdd, 0x0d, 0x01, 0xbd, 0x4b, 0x26, 0xed, 0x75, 0xb9, 0x49, 0x59, 0xaf, 0x7b, 0x35, 0x15, 0x2c, 0xf1, 0x2c, + 0xd8, 0x0b, 0xd4, 0x7e, 0xed, 0xa1, 0x38, 0x3f, 0x65, 0xdb, 0xa6, 0xe7, 0x55, 0xdf, 0xfd, 0x3d, 0x8b, 0x8c, 0x6d, + 0xda, 0xbb, 0x3d, 0x44, 0xc2, 0x72, 0xc2, 0x3a, 0xe0, 0x84, 0xeb, 0xda, 0x01, 0x01, 0xfa, 0x14, 0x88, 0xdc, 0x58, + 0x92, 0xd5, 0xa6, 0x36, 0xba, 0x0f, 0xfc, 0x6e, 0x29, 0x91, 0x6e, 0xb4, 0x15, 0xc1, 0xf4, 0x09, 0x46, 0x4d, 0x67, + 0x9e, 0xa6, 0x6e, 0xbc, 0xba, 0xbc, 0x2d, 0xda, 0xfa, 0x37, 0xa0, 0xb1, 0xd9, 0x1e, 0x26, 0x86, 0x32, 0x88, 0x81, + 0xde, 0x47, 0xbc, 0xdf, 0x6a, 0x65, 0x08, 0x14, 0x32, 0xd9, 0x08, 0xcb, 0xc4, 0x6b, 0xd1, 0x8f, 0x8e, 0x0c, 0x3c, + 0xea, 0x04, 0x84, 0x29, 0x08, 0x21, 0x61, 0xd7, 0x06, 0x61, 0xc3, 0xe5, 0x6a, 0xe4, 0xc2, 0x46, 0x6a, 0x0c, 0x1d, + 0xfc, 0xbf, 0xc2, 0x65, 0x6b, 0x66, 0x56, 0x8b, 0x62, 0x70, 0xb3, 0x30, 0x60, 0x91, 0x20, 0x3d, 0xda, 0x6c, 0x0f, + 0xc5, 0xfd, 0xb9, 0xd8, 0x6c, 0x08, 0x48, 0x2c, 0x60, 0x82, 0xa2, 0xe5, 0xdc, 0x18, 0x63, 0x95, 0xd4, 0x5a, 0xd6, + 0x86, 0xc4, 0x1c, 0x04, 0x8c, 0x0e, 0xd7, 0x7d, 0x75, 0x97, 0x32, 0x7c, 0x9f, 0x0a, 0x7c, 0x0b, 0x9e, 0x34, 0xa9, + 0xc4, 0xee, 0xf1, 0x82, 0x72, 0x43, 0x74, 0xdf, 0xb3, 0xb7, 0x25, 0xac, 0xb3, 0xd9, 0x23, 0x22, 0xf8, 0x5d, 0xff, + 0xea, 0x82, 0xef, 0x16, 0x7e, 0x0d, 0xd6, 0xcf, 0xc1, 0x49, 0x8a, 0x45, 0x4b, 0xb6, 0x4b, 0x77, 0x64, 0x40, 0xb9, + 0x9a, 0x5f, 0x0e, 0x53, 0x77, 0x8a, 0xe1, 0xc6, 0xc7, 0x6b, 0xfc, 0x61, 0xab, 0xdd, 0x96, 0xaa, 0x8a, 0xdb, 0xbd, + 0x29, 0x5a, 0xb2, 0x6e, 0x7a, 0x4f, 0xe6, 0x56, 0x4a, 0xf3, 0xeb, 0x03, 0xee, 0xec, 0xb4, 0xef, 0xa7, 0xf9, 0xce, + 0xa3, 0x73, 0xdd, 0xb4, 0x4f, 0x6d, 0x14, 0xc1, 0xc1, 0xcf, 0x0e, 0x6e, 0xef, 0x0c, 0x38, 0x80, 0x9f, 0xbf, 0xa3, + 0x79, 0x93, 0x41, 0x74, 0x7a, 0xab, 0x19, 0x5f, 0xc7, 0xbf, 0xe7, 0xad, 0x78, 0x90, 0xfe, 0x9e, 0xfc, 0x9e, 0xb7, + 0xd0, 0x00, 0xc5, 0x8b, 0xbb, 0x35, 0x9b, 0xaf, 0x21, 0xd8, 0xda, 0x83, 0x13, 0xfc, 0x20, 0x2c, 0xc9, 0x35, 0x2d, + 0x78, 0xb6, 0x76, 0x0f, 0x02, 0xae, 0xdd, 0xab, 0x44, 0x6b, 0xf3, 0xc6, 0xd5, 0x3a, 0x96, 0xe3, 0x02, 0x02, 0x0b, + 0xc7, 0x07, 0xed, 0xc1, 0xb0, 0xd3, 0x7e, 0x34, 0xb2, 0xff, 0x9a, 0x08, 0xf7, 0xa8, 0x11, 0xb1, 0xed, 0xed, 0xd6, + 0xd6, 0x8f, 0xc1, 0xb0, 0x03, 0x42, 0x81, 0x83, 0x5c, 0xfa, 0x26, 0x43, 0xd6, 0xf7, 0x64, 0xbd, 0x66, 0x2e, 0x9a, + 0xb5, 0xd3, 0xe0, 0x57, 0xb1, 0x99, 0x8e, 0xbb, 0x49, 0xaf, 0xef, 0xc5, 0x58, 0xd2, 0x82, 0x48, 0xd3, 0x98, 0x41, + 0x20, 0xa9, 0x95, 0xe1, 0xb0, 0x16, 0x77, 0x51, 0x5a, 0xdf, 0x1f, 0x41, 0xca, 0x77, 0x51, 0xca, 0x4f, 0x08, 0x04, + 0xd0, 0xb6, 0xcc, 0x51, 0xd5, 0x90, 0xf7, 0x5d, 0x7a, 0x6c, 0x9c, 0x19, 0x5a, 0x7c, 0xbd, 0xee, 0xd4, 0xc3, 0x54, + 0x65, 0x73, 0x98, 0xab, 0x0d, 0x16, 0xe4, 0x01, 0xe8, 0x9a, 0x15, 0x11, 0x83, 0xd0, 0x55, 0x1e, 0xde, 0x43, 0xc6, + 0x92, 0x80, 0x93, 0xfe, 0x40, 0x0c, 0x4a, 0x72, 0xfd, 0x38, 0x06, 0x1f, 0x33, 0xcc, 0x87, 0x7a, 0x58, 0x8e, 0x46, + 0x28, 0x75, 0x4e, 0x67, 0xa9, 0x89, 0xb8, 0x12, 0xf8, 0x25, 0x97, 0xe0, 0x97, 0xac, 0x10, 0x1b, 0x96, 0x23, 0xf2, + 0x38, 0x8b, 0x25, 0x38, 0xe5, 0xef, 0xf1, 0x79, 0x7c, 0x1e, 0x1a, 0x98, 0x9a, 0x61, 0x99, 0x8b, 0x6c, 0xb0, 0x98, + 0xb3, 0x96, 0x40, 0x70, 0x33, 0xe0, 0x2e, 0xb5, 0x21, 0xd1, 0x58, 0x03, 0x45, 0x77, 0x51, 0x68, 0x66, 0xf4, 0x6a, + 0xa7, 0x8d, 0x61, 0xe4, 0xf0, 0xc2, 0x5c, 0xc3, 0x58, 0x04, 0x32, 0x97, 0xab, 0x1e, 0xfb, 0xab, 0x0f, 0x9b, 0x15, + 0x06, 0xaf, 0xc8, 0x74, 0xe8, 0x8e, 0x63, 0xc6, 0x57, 0x7b, 0xe2, 0x18, 0x82, 0x4c, 0x2c, 0x95, 0x6e, 0x39, 0x26, + 0xae, 0xa2, 0xcf, 0xc4, 0x90, 0xed, 0x96, 0x67, 0xe6, 0x42, 0x37, 0xdb, 0x7f, 0x39, 0xb7, 0x73, 0x4e, 0xb8, 0xd1, + 0x4a, 0x1a, 0x6d, 0xd4, 0x0b, 0x43, 0x55, 0x5d, 0x30, 0xbf, 0xc7, 0x4e, 0x4b, 0x8b, 0x9d, 0xab, 0x77, 0x3f, 0x7c, + 0x9d, 0xaf, 0x8a, 0xbf, 0xc5, 0xea, 0xd0, 0x8a, 0x0c, 0x77, 0x3b, 0xc8, 0x9b, 0x33, 0x3d, 0xf6, 0x8a, 0x5c, 0xa8, + 0x0e, 0x7f, 0x51, 0x5f, 0x98, 0x07, 0x3b, 0xa3, 0x96, 0xf0, 0xe8, 0xf7, 0x20, 0x03, 0xe5, 0x1f, 0x4c, 0x4c, 0x16, + 0x2c, 0xb9, 0xa5, 0xa5, 0x88, 0xff, 0xf1, 0x4a, 0x98, 0x58, 0x55, 0x07, 0x30, 0x90, 0x03, 0x53, 0xf1, 0x00, 0x6e, + 0x4d, 0xf8, 0x84, 0xb3, 0x3c, 0x3d, 0x88, 0xfe, 0xd1, 0x12, 0xad, 0x7f, 0x44, 0xff, 0x00, 0x77, 0x67, 0xf7, 0x3a, + 0x64, 0x15, 0x17, 0xc2, 0xdf, 0x63, 0x3d, 0xae, 0x54, 0xca, 0x58, 0x7b, 0xdd, 0x72, 0x78, 0x21, 0xf5, 0x36, 0x8b, + 0x1f, 0x3b, 0x62, 0x6d, 0x53, 0xb0, 0x0e, 0x29, 0x29, 0x3c, 0xbb, 0x62, 0x6e, 0xb5, 0x98, 0xbb, 0xd4, 0x12, 0xfe, + 0xfa, 0xea, 0x71, 0xa5, 0x82, 0x86, 0x83, 0xd0, 0x95, 0xb6, 0x90, 0x00, 0x03, 0x97, 0xca, 0xa7, 0xd3, 0x9d, 0x49, + 0x64, 0x9c, 0xc5, 0xf0, 0xee, 0x41, 0x10, 0x48, 0x80, 0x6d, 0x85, 0x55, 0x81, 0xcb, 0x95, 0x3a, 0xea, 0xa5, 0x24, + 0x10, 0x80, 0xbe, 0xf2, 0x1e, 0x94, 0x57, 0x65, 0xbf, 0xd5, 0x92, 0xa0, 0x85, 0xa5, 0xe6, 0x5a, 0x15, 0xd3, 0xc3, + 0xf0, 0x55, 0xc3, 0xe0, 0xc3, 0x3b, 0xa4, 0x6d, 0x3d, 0x2d, 0x4a, 0x09, 0xb5, 0x3b, 0xe8, 0x10, 0xac, 0xb2, 0x83, + 0xf2, 0xef, 0x62, 0x8a, 0x6c, 0xfe, 0x90, 0x7d, 0x47, 0x5d, 0x87, 0x23, 0x57, 0xb0, 0xee, 0xa5, 0x8a, 0x82, 0x01, + 0x2b, 0xa7, 0x40, 0xed, 0x9d, 0x64, 0x34, 0x9b, 0x31, 0x50, 0xf7, 0xdb, 0xa2, 0xf5, 0xdc, 0x9e, 0x35, 0xfd, 0x86, + 0x8c, 0xb3, 0x8f, 0x30, 0xce, 0x3e, 0x0a, 0xbc, 0x58, 0x24, 0xf9, 0x41, 0xc6, 0x1a, 0xc7, 0xaa, 0x2d, 0xd0, 0x49, + 0x0f, 0xb8, 0x33, 0x70, 0xe0, 0x01, 0x5b, 0x94, 0xa3, 0x23, 0xea, 0x2c, 0xee, 0x69, 0x2b, 0xf3, 0xde, 0x9e, 0x50, + 0xbb, 0x8c, 0x05, 0x6e, 0x37, 0xcc, 0xb4, 0xa0, 0xb5, 0xd2, 0x38, 0x8f, 0x87, 0x11, 0x19, 0x1b, 0xf1, 0x13, 0xb6, + 0xac, 0xa9, 0x9a, 0x37, 0xd0, 0x1c, 0x35, 0x82, 0xdc, 0xbc, 0x32, 0xde, 0xaa, 0x64, 0x18, 0x45, 0x23, 0xcb, 0xa9, + 0x10, 0x43, 0x32, 0x86, 0x9d, 0x51, 0x70, 0xab, 0xbd, 0x5e, 0x73, 0x8f, 0xf8, 0xa2, 0xe1, 0xad, 0x66, 0x6e, 0x01, + 0xb2, 0x32, 0x8e, 0xaa, 0x7b, 0x93, 0x08, 0xbc, 0x6f, 0xab, 0x08, 0x69, 0xab, 0xa1, 0x7d, 0xba, 0xb2, 0x52, 0x6c, + 0xbe, 0xa7, 0xd3, 0x51, 0x1a, 0xd9, 0x11, 0x45, 0xf8, 0x53, 0x05, 0x49, 0xb8, 0x4a, 0xfa, 0xa4, 0x32, 0xb9, 0x60, + 0x2a, 0xe5, 0xf8, 0x53, 0x29, 0xa5, 0xbe, 0xb1, 0x5f, 0x12, 0xd7, 0x77, 0x32, 0x02, 0x7f, 0x9a, 0x32, 0xfd, 0x9e, + 0x96, 0x53, 0x06, 0x7e, 0x45, 0xfe, 0x76, 0x2c, 0xa5, 0xe4, 0xfa, 0x95, 0x88, 0x87, 0x14, 0xc3, 0xbb, 0xab, 0x23, + 0xac, 0x4d, 0x08, 0x94, 0x0a, 0x17, 0xe1, 0x82, 0xe8, 0x6d, 0x29, 0xef, 0xee, 0xe3, 0x12, 0x3b, 0x07, 0xc0, 0xca, + 0x69, 0x12, 0xe0, 0x5f, 0x3d, 0xe6, 0x63, 0x35, 0xe6, 0xd4, 0xe8, 0xfa, 0xdd, 0xef, 0xe4, 0x13, 0xd0, 0xdb, 0xca, + 0x51, 0x70, 0xd8, 0x19, 0x41, 0x2e, 0xdc, 0x85, 0xc1, 0xc5, 0x57, 0x58, 0xbb, 0x2c, 0x8d, 0x37, 0x16, 0x40, 0xef, + 0x49, 0x06, 0x16, 0x6c, 0x98, 0x63, 0x0a, 0x8f, 0xd6, 0x4e, 0x99, 0x0e, 0xa2, 0x82, 0x3c, 0xab, 0x9e, 0x25, 0x6d, + 0xd4, 0x7e, 0xc7, 0x26, 0x70, 0x87, 0x91, 0x7c, 0xbd, 0x70, 0xe2, 0xc0, 0x03, 0x32, 0x4d, 0x66, 0x9b, 0x7d, 0xeb, + 0x23, 0x8f, 0xbc, 0x99, 0xc4, 0xfb, 0x5a, 0x0a, 0xf3, 0xcd, 0x8a, 0x6e, 0x30, 0x84, 0xa2, 0x08, 0xfb, 0xbd, 0x55, + 0x31, 0x45, 0xb5, 0x41, 0x1b, 0x34, 0x2c, 0x6f, 0xc5, 0x0f, 0x70, 0xc6, 0xd0, 0x66, 0x21, 0x7b, 0x47, 0x67, 0x1d, + 0xce, 0x1c, 0x66, 0xcc, 0x08, 0x8c, 0x4a, 0xcb, 0x92, 0x4e, 0xc1, 0xd1, 0xb9, 0xfe, 0x20, 0x2a, 0xae, 0x8f, 0x15, + 0x80, 0x27, 0x99, 0xc1, 0x3f, 0xc5, 0x36, 0x58, 0x0f, 0x3b, 0x0d, 0xc3, 0xd4, 0x1f, 0xf4, 0xae, 0x6b, 0xf9, 0x2a, + 0xc4, 0x91, 0x2e, 0x86, 0xd0, 0x3a, 0x77, 0xf7, 0x80, 0x22, 0x2e, 0xe8, 0x45, 0xaa, 0xf1, 0x27, 0xb5, 0x1c, 0x9b, + 0xf5, 0x35, 0xae, 0x63, 0xda, 0x20, 0x8a, 0x75, 0xd7, 0xc4, 0x9f, 0xea, 0x57, 0x60, 0x55, 0x0a, 0xac, 0x33, 0x28, + 0x3f, 0x54, 0x75, 0xd9, 0x90, 0x4a, 0x72, 0x6d, 0x3a, 0x95, 0xa6, 0xd3, 0x1a, 0xa1, 0x5c, 0x7a, 0x52, 0xdd, 0xbf, + 0x42, 0x08, 0x03, 0x53, 0x66, 0x0f, 0x56, 0xa9, 0x1d, 0xac, 0x82, 0x57, 0x2f, 0xb6, 0xb0, 0x4a, 0xc2, 0xf1, 0x5c, + 0xa1, 0x51, 0x59, 0xe3, 0x90, 0x21, 0x7d, 0x21, 0x16, 0x41, 0x02, 0x60, 0xd1, 0x8f, 0x99, 0xcb, 0xfb, 0x16, 0x0e, + 0x85, 0x3d, 0xc9, 0x24, 0x9c, 0x6e, 0x42, 0x0b, 0x78, 0x1e, 0x58, 0x0d, 0x3c, 0x42, 0xcc, 0x4c, 0xfc, 0x27, 0x78, + 0x16, 0xfa, 0xdb, 0xcf, 0xd1, 0x3a, 0x0b, 0xf2, 0xf4, 0xdf, 0xa2, 0x24, 0x34, 0xf6, 0x3f, 0xc7, 0x43, 0x87, 0x84, + 0xe1, 0xc0, 0xb7, 0x47, 0x58, 0xe3, 0xe0, 0x4e, 0x11, 0x9f, 0xc1, 0x1d, 0x3e, 0x36, 0xa1, 0x07, 0x80, 0x25, 0x14, + 0x87, 0x20, 0xdf, 0x42, 0x31, 0x33, 0x6c, 0x4d, 0x56, 0xe1, 0x05, 0x2e, 0x58, 0x2d, 0x54, 0xf7, 0xb7, 0x1d, 0x2f, + 0xa5, 0x35, 0x2e, 0x79, 0x8d, 0x39, 0x50, 0xf5, 0x19, 0x5e, 0xf8, 0x0a, 0xf3, 0x5e, 0xb5, 0xfb, 0xc2, 0x9f, 0x1c, + 0xd0, 0x53, 0x08, 0x18, 0xe9, 0x7e, 0x6f, 0x08, 0xf7, 0x14, 0xbd, 0xca, 0xc5, 0x61, 0xdb, 0x41, 0xf7, 0x02, 0x73, + 0x75, 0x53, 0x67, 0x2d, 0xc0, 0x14, 0x1a, 0x1c, 0x54, 0xe1, 0x8c, 0xc0, 0x5c, 0xbd, 0xaa, 0x0a, 0x2e, 0x40, 0xbc, + 0x1f, 0x08, 0x93, 0x53, 0x45, 0x03, 0x78, 0x9f, 0x55, 0x8f, 0x4e, 0x0d, 0x38, 0xb8, 0x8c, 0x1b, 0x36, 0xf1, 0x99, + 0xf0, 0xa9, 0xc0, 0x4a, 0x5a, 0xe3, 0xd0, 0x88, 0xe6, 0x74, 0x01, 0x66, 0x1b, 0x40, 0xc1, 0xdd, 0xf9, 0xb0, 0xb5, + 0x50, 0xc1, 0x93, 0xbc, 0x8d, 0x17, 0xb4, 0x09, 0x71, 0x26, 0x4d, 0xc1, 0xdd, 0x76, 0x59, 0x06, 0xe6, 0xb7, 0xff, + 0x51, 0x58, 0x24, 0x18, 0x50, 0xa5, 0x49, 0x82, 0xf0, 0x04, 0x95, 0x91, 0x6e, 0xed, 0x66, 0x02, 0xe9, 0x44, 0x84, + 0x37, 0xcc, 0x3f, 0x6e, 0x9d, 0xaf, 0x8e, 0x1a, 0x88, 0x9a, 0x1a, 0xa8, 0x80, 0x1a, 0xc8, 0xe6, 0xf6, 0x2f, 0x61, + 0x21, 0x6c, 0x84, 0x2a, 0x11, 0x04, 0x44, 0x58, 0x68, 0xc3, 0x07, 0x94, 0x49, 0x08, 0x79, 0x03, 0xa8, 0x98, 0x92, + 0x77, 0x60, 0x34, 0x0e, 0xaf, 0xf7, 0x80, 0xfb, 0xa5, 0x65, 0x18, 0x3c, 0xa7, 0x60, 0xf2, 0x5f, 0xf8, 0x7c, 0xa8, + 0x5e, 0xad, 0x0e, 0x42, 0xf8, 0x19, 0xc4, 0x8a, 0x70, 0xfc, 0xc5, 0x0f, 0x40, 0x36, 0x15, 0x96, 0x47, 0x47, 0x12, + 0x04, 0x7e, 0x88, 0x22, 0x1c, 0xf0, 0x0c, 0xef, 0xb2, 0x2d, 0xa2, 0xe7, 0x67, 0xa5, 0xea, 0x59, 0xc9, 0x60, 0x56, + 0xa5, 0xa7, 0x71, 0x74, 0x43, 0x18, 0x08, 0x2e, 0xd4, 0xee, 0x1b, 0x84, 0x40, 0xd9, 0x72, 0x6b, 0xe8, 0xd2, 0x73, + 0x30, 0x1f, 0x8d, 0xa3, 0x77, 0x0c, 0x1e, 0x16, 0x36, 0xee, 0x28, 0x4c, 0xb3, 0x4c, 0x1b, 0xe6, 0xb1, 0x15, 0x38, + 0xa9, 0x53, 0x94, 0xfc, 0x29, 0xb9, 0x88, 0xa3, 0xf6, 0x75, 0x84, 0x5a, 0xf0, 0x6f, 0x8b, 0xa3, 0x3e, 0x4d, 0x68, + 0x9e, 0xfb, 0xe0, 0x37, 0x19, 0x31, 0x9b, 0x6c, 0xbd, 0x16, 0x35, 0x41, 0x4f, 0xec, 0x06, 0x03, 0x56, 0xe2, 0x19, + 0xb0, 0x0f, 0x96, 0x83, 0x25, 0xef, 0x45, 0xac, 0xfc, 0x29, 0x85, 0xc1, 0xea, 0x39, 0x43, 0x08, 0x67, 0x01, 0x93, + 0xf2, 0x3f, 0x9f, 0x69, 0xb8, 0x7e, 0x7e, 0xbe, 0x8e, 0x11, 0x91, 0x3e, 0x88, 0x5c, 0x83, 0x1d, 0x11, 0x41, 0xd8, + 0x32, 0x3d, 0x74, 0x65, 0xbe, 0xf3, 0xd6, 0xd5, 0x23, 0x1b, 0x2e, 0x0e, 0x0c, 0xa8, 0x51, 0x60, 0xb4, 0x82, 0x0b, + 0x52, 0x0d, 0x1c, 0x94, 0x10, 0x9a, 0x95, 0xf1, 0x8c, 0x5c, 0x43, 0x24, 0xbc, 0x0c, 0xf5, 0xc1, 0xb0, 0x20, 0x90, + 0xa0, 0x66, 0x20, 0x41, 0x65, 0xbe, 0x76, 0x0e, 0xb3, 0x2e, 0xcc, 0x6c, 0x67, 0xa8, 0xef, 0x82, 0xfc, 0xfc, 0xa0, + 0xe3, 0x1c, 0x58, 0xda, 0xa3, 0xa3, 0x12, 0x22, 0x88, 0x01, 0x05, 0xaf, 0x24, 0xc0, 0x40, 0x03, 0x5e, 0x6e, 0x69, + 0xc0, 0x17, 0xda, 0x78, 0x1d, 0x18, 0x5b, 0x9f, 0x2a, 0xc8, 0xc5, 0xeb, 0x7a, 0x4f, 0x13, 0x42, 0x0e, 0x3b, 0x03, + 0x9d, 0xee, 0x46, 0x48, 0x1c, 0xfc, 0xaa, 0x4d, 0xa0, 0x31, 0x47, 0xba, 0xeb, 0x8d, 0xf9, 0x77, 0x43, 0x8f, 0x58, + 0x4f, 0x42, 0xba, 0x20, 0x5d, 0x9e, 0x4f, 0x7b, 0x0d, 0x57, 0xac, 0xd2, 0xc8, 0xc1, 0x25, 0xe8, 0xb3, 0x01, 0x01, + 0x4a, 0x54, 0x99, 0x4a, 0xd0, 0x32, 0x2e, 0x93, 0x8a, 0x0d, 0xc3, 0x0c, 0xc2, 0x14, 0xd6, 0x2b, 0x41, 0xb7, 0xd6, + 0x00, 0x78, 0x67, 0x66, 0xff, 0x54, 0x3e, 0xd8, 0x74, 0xe3, 0xcd, 0x23, 0x80, 0x80, 0x1c, 0x76, 0x2b, 0x76, 0x5d, + 0x6c, 0x55, 0x66, 0x61, 0x2d, 0x63, 0x2b, 0xb7, 0xeb, 0x31, 0xf6, 0x5e, 0xec, 0xf2, 0x09, 0x10, 0xa2, 0xb6, 0x62, + 0x1a, 0xb1, 0x84, 0x21, 0xeb, 0xc6, 0x90, 0x8d, 0x36, 0x14, 0x9e, 0x4a, 0xe4, 0xc0, 0x25, 0x9a, 0x20, 0xf9, 0x8e, + 0x4b, 0x70, 0x08, 0x2f, 0x3c, 0xc2, 0x7f, 0x01, 0x16, 0xa9, 0xc4, 0x0c, 0xcb, 0xf5, 0x1a, 0xea, 0x79, 0xbc, 0xcf, + 0xb6, 0x83, 0x93, 0xca, 0xad, 0xb1, 0x4b, 0x3b, 0xf1, 0xb8, 0x6a, 0x42, 0xe2, 0x0c, 0xfa, 0xf5, 0x15, 0xd1, 0xe0, + 0xb0, 0x9b, 0xbe, 0xf2, 0xef, 0x95, 0xb9, 0x1d, 0x88, 0x0d, 0xeb, 0x0d, 0x56, 0x1f, 0x40, 0xcb, 0xff, 0xcc, 0xfc, + 0x43, 0x65, 0xc1, 0x4d, 0x82, 0xda, 0x5e, 0xc4, 0x3e, 0xeb, 0x23, 0x46, 0x1a, 0x8b, 0xbb, 0x47, 0x88, 0xff, 0x73, + 0x27, 0x8a, 0x01, 0x4f, 0x6a, 0xfe, 0x39, 0x46, 0x7d, 0x08, 0x45, 0x6d, 0x3d, 0x6c, 0x80, 0xd2, 0xae, 0x36, 0xb5, + 0x18, 0x19, 0x12, 0xc8, 0x77, 0x2e, 0xbc, 0xa0, 0x39, 0x89, 0x14, 0xc8, 0xc9, 0x75, 0x17, 0x4f, 0xb2, 0x2d, 0x61, + 0xae, 0xbf, 0x83, 0x63, 0xe6, 0x6a, 0x23, 0x2b, 0xe3, 0xf7, 0xc0, 0xce, 0x70, 0x23, 0x59, 0x3a, 0xf0, 0xa9, 0x06, + 0xf8, 0xfc, 0x9a, 0x1b, 0x8a, 0xa2, 0xd0, 0xe0, 0xbd, 0x7d, 0x64, 0x0e, 0x7e, 0xa7, 0x81, 0xf8, 0x98, 0x39, 0x1d, + 0xc9, 0x56, 0xa8, 0x35, 0x67, 0xc7, 0xcb, 0xb6, 0x23, 0x0c, 0x0a, 0x1b, 0xbd, 0xaf, 0x46, 0x56, 0xb1, 0xb7, 0x53, + 0x11, 0xcc, 0xe9, 0x56, 0xd5, 0xce, 0xa9, 0xdc, 0x32, 0xaa, 0x95, 0xa6, 0x01, 0x22, 0x5c, 0xf9, 0x44, 0xf2, 0x31, + 0x33, 0xe1, 0x1f, 0x0c, 0xc6, 0x35, 0x23, 0x85, 0x7f, 0xdc, 0x17, 0x3b, 0x64, 0x37, 0x3a, 0xdc, 0x56, 0xd0, 0xbc, + 0x50, 0xc1, 0x03, 0x8e, 0x4a, 0x96, 0x10, 0x29, 0x72, 0x7d, 0xa8, 0x1a, 0xa6, 0x6c, 0x9f, 0x22, 0x84, 0x90, 0xf6, + 0x38, 0xeb, 0x86, 0xd6, 0x0c, 0x3d, 0x52, 0x3b, 0x4d, 0xee, 0xd0, 0x5c, 0x17, 0xa0, 0xc2, 0x08, 0xa4, 0xab, 0xcf, + 0xec, 0x3e, 0x95, 0x10, 0xbd, 0x7c, 0xe3, 0x42, 0x18, 0x3b, 0x2b, 0x4b, 0x5c, 0x9a, 0x51, 0xdb, 0x30, 0xba, 0x6e, + 0x63, 0x38, 0x1b, 0x18, 0x33, 0x0d, 0x4a, 0x3a, 0x10, 0xea, 0xba, 0x4f, 0xaf, 0x32, 0x13, 0xe8, 0xb1, 0x20, 0xb4, + 0xc5, 0xf0, 0x8c, 0x68, 0xb0, 0x6c, 0x2a, 0xc1, 0x82, 0x6f, 0x55, 0xa6, 0xd6, 0x66, 0x93, 0xc5, 0xbf, 0xea, 0xd8, + 0x3c, 0xed, 0x57, 0xd4, 0xcc, 0x73, 0xe9, 0xa3, 0x23, 0x64, 0x3e, 0x1e, 0xdd, 0xf3, 0xb7, 0x37, 0xaf, 0x7e, 0x7c, + 0xf3, 0x7a, 0xbd, 0xee, 0xb2, 0x76, 0xf7, 0x0c, 0xff, 0x53, 0x57, 0xf1, 0x60, 0xab, 0x28, 0x40, 0x47, 0x47, 0x87, + 0xdc, 0xb8, 0xf0, 0x7c, 0xe6, 0x0b, 0x88, 0x1b, 0xa4, 0x47, 0xb8, 0x28, 0xab, 0x98, 0x20, 0x77, 0xd1, 0x20, 0xba, + 0x8f, 0x40, 0x09, 0x55, 0x93, 0xbf, 0x5f, 0xb6, 0x67, 0x7f, 0x00, 0x81, 0x89, 0xa0, 0x3e, 0x44, 0x00, 0x81, 0x78, + 0xa5, 0xb8, 0x24, 0xcc, 0x27, 0x40, 0x14, 0xef, 0x09, 0x70, 0xa6, 0x26, 0x6a, 0xd5, 0x44, 0xc5, 0x25, 0x90, 0x44, + 0x1b, 0x8e, 0x92, 0x9e, 0x98, 0x00, 0xde, 0x10, 0x94, 0xd2, 0xfe, 0xea, 0x17, 0xce, 0x5d, 0xaa, 0x40, 0x83, 0x4e, + 0x5a, 0xe0, 0x99, 0xfb, 0x9c, 0xc1, 0xe7, 0xac, 0xef, 0x4f, 0x07, 0x71, 0x5c, 0xe0, 0x25, 0x11, 0xc7, 0xfe, 0x59, + 0xc4, 0xd5, 0xa2, 0x64, 0x5f, 0xb8, 0x5c, 0xaa, 0x74, 0x75, 0x97, 0xca, 0xe4, 0xae, 0x9d, 0x1f, 0xc7, 0x65, 0x72, + 0xd7, 0x56, 0xc9, 0x1d, 0xc2, 0xf7, 0xa9, 0x4c, 0xee, 0x6d, 0xca, 0x7d, 0x5b, 0xc1, 0xcd, 0x17, 0x16, 0x70, 0x28, + 0xda, 0xa2, 0xad, 0xe5, 0x76, 0x51, 0x9b, 0xe2, 0x8a, 0x86, 0xd1, 0x14, 0xf7, 0x6c, 0xfc, 0x30, 0x7c, 0x09, 0xae, + 0x4c, 0x9a, 0xc8, 0x3f, 0x41, 0xfa, 0xe9, 0xd4, 0x06, 0xee, 0x33, 0xd2, 0xe9, 0xcf, 0xae, 0x44, 0xbb, 0xdb, 0x6f, + 0xb5, 0x66, 0xb0, 0x77, 0x33, 0x52, 0xf8, 0x62, 0xb3, 0x96, 0x89, 0xaf, 0x73, 0x98, 0xad, 0xd7, 0x87, 0x05, 0x32, + 0x1b, 0x6e, 0xca, 0x62, 0x3d, 0x9c, 0x8d, 0x70, 0x07, 0x7f, 0xc8, 0x10, 0x5a, 0xb1, 0xe1, 0x6c, 0x44, 0xd8, 0x70, + 0xd6, 0xea, 0x8e, 0xac, 0xa1, 0x9d, 0xd9, 0x8a, 0x1b, 0x08, 0xa1, 0x39, 0x1b, 0x9d, 0x98, 0x92, 0xd2, 0xe5, 0xdb, + 0x2f, 0x5a, 0x07, 0xf4, 0x53, 0x8d, 0xe0, 0x65, 0x12, 0xf7, 0xa0, 0x2f, 0x7a, 0x65, 0x9f, 0x6e, 0x2d, 0xc9, 0xe9, + 0x49, 0xed, 0x6a, 0x4f, 0x11, 0x36, 0x3d, 0xa9, 0xe3, 0xf2, 0xd8, 0x34, 0xe3, 0xba, 0x94, 0xee, 0x3b, 0xd4, 0x8c, + 0xfc, 0xe5, 0x60, 0x01, 0x08, 0x52, 0xc3, 0xa3, 0x28, 0x5d, 0x38, 0xa5, 0x10, 0x2e, 0x0e, 0x2a, 0x3b, 0x30, 0x29, + 0x48, 0xa7, 0x5f, 0x18, 0x4b, 0xff, 0xc2, 0x45, 0x34, 0xa5, 0x98, 0x92, 0xcc, 0x97, 0x2c, 0x0c, 0x58, 0xe8, 0x36, + 0xe5, 0x99, 0x81, 0x5e, 0x69, 0x84, 0x73, 0x02, 0xf1, 0x90, 0xfa, 0xa5, 0x31, 0xf0, 0x8a, 0x67, 0xed, 0x72, 0xc8, + 0x46, 0xe8, 0xe4, 0x14, 0xd3, 0xe1, 0x1f, 0xd9, 0xa2, 0x0b, 0x8f, 0x05, 0xfe, 0x31, 0x22, 0xb3, 0xb6, 0xac, 0x12, + 0x04, 0x24, 0xe4, 0x6d, 0x79, 0x0c, 0x7b, 0x09, 0xe1, 0xcc, 0x56, 0xcc, 0x86, 0x6c, 0xd4, 0x9e, 0x55, 0x15, 0x7b, + 0xbe, 0x62, 0x4b, 0x56, 0x09, 0xb6, 0x62, 0xcb, 0x55, 0x0c, 0x5f, 0x67, 0x30, 0x20, 0x08, 0x01, 0xc0, 0x00, 0x00, + 0x1a, 0x05, 0xd1, 0x7c, 0xb1, 0x22, 0x7e, 0xb3, 0xdb, 0x7b, 0xfc, 0x0e, 0x58, 0xa0, 0x35, 0xf6, 0xff, 0x3e, 0x94, + 0x01, 0x7b, 0xca, 0xd2, 0xc4, 0xcc, 0x2d, 0xad, 0x8a, 0x0e, 0xa0, 0x52, 0x21, 0x4c, 0x69, 0x20, 0x73, 0x98, 0x19, + 0xa8, 0x05, 0x5a, 0x83, 0x62, 0xa8, 0x47, 0xed, 0x0c, 0x8e, 0x18, 0x78, 0x87, 0x86, 0xcc, 0x8c, 0x31, 0x61, 0x5c, + 0xc0, 0x14, 0x33, 0x03, 0x9e, 0x59, 0xda, 0xd9, 0x48, 0x23, 0xcb, 0x0d, 0x8a, 0xc1, 0x5f, 0x3a, 0x56, 0xc3, 0xb2, + 0xdd, 0x1d, 0xa1, 0x43, 0x42, 0xec, 0xc7, 0x08, 0x36, 0x99, 0x4b, 0x6d, 0x99, 0xef, 0x93, 0x5e, 0x6a, 0x3f, 0xe1, + 0xcf, 0x68, 0x63, 0x76, 0x00, 0xe8, 0xc8, 0xb0, 0x59, 0x7f, 0xd9, 0x50, 0x79, 0xfd, 0xba, 0x37, 0x4a, 0xe5, 0xbe, + 0x77, 0xa7, 0x03, 0xd5, 0x44, 0xe8, 0xad, 0x87, 0xab, 0x87, 0x7a, 0x08, 0x98, 0x31, 0x98, 0x5b, 0x66, 0xf4, 0xad, + 0x10, 0xc9, 0x25, 0x91, 0xc0, 0x92, 0x60, 0x4a, 0x18, 0xec, 0xad, 0xa3, 0x23, 0x53, 0x8d, 0xb5, 0xe0, 0x79, 0x52, + 0x04, 0x82, 0x81, 0x8f, 0xa0, 0x0c, 0x68, 0xa2, 0xcc, 0x6d, 0x38, 0xf9, 0x95, 0xb9, 0x5f, 0xb8, 0xba, 0x7d, 0x2c, + 0x9d, 0xb6, 0xd5, 0x5c, 0x8f, 0x57, 0x05, 0xee, 0xab, 0x7b, 0x49, 0xab, 0xe0, 0x46, 0xf6, 0x26, 0x4f, 0x99, 0xbb, + 0x75, 0x5f, 0xaa, 0xb7, 0xbf, 0x99, 0x5e, 0xd5, 0x4c, 0x6f, 0xb7, 0x99, 0x30, 0xae, 0xe4, 0xd7, 0xac, 0x22, 0xcd, + 0xc9, 0x9a, 0xa8, 0x05, 0x15, 0xff, 0xa4, 0x0b, 0xd0, 0x8e, 0x72, 0x7b, 0xaf, 0x0a, 0x27, 0x57, 0x41, 0xae, 0x0f, + 0x0b, 0x43, 0x5c, 0x91, 0xb9, 0x50, 0x87, 0x00, 0x2f, 0xaf, 0xaa, 0xc7, 0x07, 0xb8, 0x14, 0x3f, 0xc9, 0xdc, 0x45, + 0x39, 0x15, 0x52, 0x4b, 0xc1, 0x22, 0x64, 0x50, 0xd5, 0xc5, 0xc0, 0x5e, 0xd9, 0xbd, 0x27, 0x06, 0x7c, 0x58, 0x47, + 0xcc, 0x1b, 0x99, 0xe7, 0x3e, 0xbe, 0xa5, 0x29, 0x76, 0x6a, 0xe2, 0x8c, 0xfc, 0x92, 0xc5, 0x05, 0xc8, 0x66, 0xc3, + 0xfa, 0xb5, 0xdf, 0x56, 0x17, 0x97, 0xed, 0x58, 0x0c, 0xcc, 0x13, 0x27, 0xdf, 0x95, 0xc6, 0x38, 0xc0, 0x3a, 0xfa, + 0x23, 0x4c, 0x2d, 0xd8, 0xb3, 0xc4, 0x53, 0xe8, 0xe4, 0xce, 0xa6, 0xdd, 0x87, 0x69, 0xf7, 0x26, 0xad, 0x07, 0xe5, + 0x80, 0x34, 0xbb, 0x32, 0xbd, 0x7b, 0xff, 0x7d, 0x0f, 0x2f, 0xdd, 0x6e, 0x20, 0x12, 0xf7, 0xe2, 0x89, 0x31, 0x86, + 0x78, 0x0b, 0x36, 0xa2, 0xea, 0xe8, 0xe8, 0x07, 0xe7, 0x7d, 0x5b, 0xcb, 0xb2, 0x5f, 0x0b, 0x07, 0xb6, 0xc5, 0x54, + 0xba, 0xbc, 0x5c, 0x66, 0x4b, 0xb0, 0xeb, 0x3c, 0xfc, 0x4a, 0x3c, 0x7c, 0x11, 0x32, 0x2d, 0xd6, 0x55, 0xfc, 0xb5, + 0xcc, 0x2b, 0x0f, 0x51, 0x0d, 0x11, 0x48, 0x6b, 0xeb, 0xd2, 0xd0, 0x74, 0xf4, 0x66, 0x46, 0x73, 0x79, 0xfb, 0x4e, + 0x4a, 0x3d, 0xb2, 0x2f, 0x72, 0xeb, 0x04, 0x1e, 0x2d, 0x6c, 0x30, 0x34, 0xf7, 0x95, 0x77, 0x92, 0x0d, 0x88, 0xda, + 0x1c, 0x77, 0x28, 0x89, 0xc4, 0xa2, 0xbe, 0x0b, 0xe1, 0x70, 0x17, 0x82, 0x79, 0x15, 0xb4, 0x0d, 0x62, 0xb7, 0xbb, + 0xa0, 0x6d, 0xe0, 0xd4, 0x6d, 0x03, 0xb7, 0x07, 0x83, 0x85, 0xbd, 0x0f, 0x2f, 0xc7, 0x72, 0x2c, 0x1c, 0x7f, 0xf0, + 0xd6, 0x3e, 0x00, 0x04, 0x6a, 0x1f, 0x56, 0x3e, 0x73, 0x20, 0x48, 0x9c, 0xe1, 0xe8, 0x47, 0xce, 0x6e, 0xad, 0xe5, + 0xf0, 0x7c, 0xb1, 0xd4, 0x2c, 0x37, 0x77, 0xd4, 0xa0, 0xe2, 0x6b, 0xfa, 0x79, 0xfd, 0x9c, 0x35, 0x74, 0xe3, 0x6f, + 0x21, 0x8c, 0x84, 0x53, 0x76, 0x18, 0x85, 0x84, 0x0d, 0x66, 0x55, 0xc5, 0x6b, 0xfb, 0x0d, 0xe2, 0x3d, 0x68, 0x13, + 0x4e, 0xb0, 0x6c, 0x5c, 0x50, 0x45, 0xd8, 0xc6, 0x1b, 0x0b, 0xa2, 0x3c, 0x3c, 0xd8, 0x31, 0x9a, 0x5e, 0x6d, 0x20, + 0xd0, 0xf1, 0x20, 0x6a, 0x47, 0x2d, 0x96, 0xba, 0xa0, 0xcc, 0x3e, 0xc2, 0xb8, 0xba, 0x3a, 0x33, 0x71, 0xda, 0x2b, + 0xbd, 0xfa, 0x6f, 0x19, 0x18, 0xe0, 0x0b, 0xf0, 0x12, 0x0b, 0xa3, 0xbb, 0x0e, 0x75, 0x0b, 0xea, 0xcb, 0x16, 0x1b, + 0xa1, 0xf5, 0xba, 0x53, 0x3d, 0x03, 0xe5, 0xae, 0xb9, 0x84, 0xbd, 0xe6, 0x12, 0xee, 0x9a, 0x4b, 0xf8, 0x6b, 0x2e, + 0x61, 0xae, 0xb9, 0x84, 0xbf, 0xe6, 0xf2, 0x20, 0xfc, 0x3e, 0x88, 0xe3, 0x18, 0x73, 0x88, 0xab, 0xa8, 0x6d, 0x64, + 0x3c, 0xb8, 0xf0, 0x3c, 0x64, 0x89, 0xaa, 0x96, 0x3f, 0x8c, 0x21, 0x57, 0x6c, 0xdb, 0x4a, 0x18, 0xb7, 0x29, 0xa6, + 0x20, 0x72, 0xfa, 0xd1, 0x51, 0xed, 0xee, 0x3c, 0xec, 0x8c, 0x52, 0x8e, 0x57, 0xd6, 0x89, 0xf6, 0x5f, 0xa0, 0x93, + 0x37, 0xbf, 0x7e, 0x4d, 0xe5, 0x86, 0x08, 0x67, 0x72, 0x7f, 0xd8, 0xf5, 0x94, 0xe2, 0xfb, 0xcc, 0x84, 0x27, 0xe7, + 0x89, 0x36, 0x22, 0x08, 0x42, 0x94, 0x28, 0xfc, 0x7f, 0xb3, 0xf7, 0xae, 0xcb, 0x6d, 0x23, 0x59, 0xba, 0xe8, 0xab, + 0x48, 0x0c, 0x9b, 0x05, 0x98, 0x49, 0x8a, 0xf2, 0xde, 0x33, 0x11, 0x07, 0x54, 0x9a, 0x61, 0xcb, 0xe5, 0x2e, 0x77, + 0xf9, 0xd6, 0xb6, 0xab, 0xba, 0xaa, 0x19, 0x3c, 0x2a, 0x08, 0x48, 0x12, 0x70, 0x81, 0x00, 0x0b, 0x00, 0x25, 0xd2, + 0x24, 0xde, 0x7d, 0xc7, 0x5a, 0x2b, 0xaf, 0x20, 0x28, 0xbb, 0x67, 0xf6, 0xfc, 0x3a, 0xe7, 0x8f, 0x2d, 0x26, 0x12, + 0x89, 0xbc, 0xe7, 0xca, 0x75, 0xf9, 0xbe, 0x88, 0x17, 0xb4, 0xde, 0x55, 0x28, 0x3c, 0xaa, 0xa2, 0x94, 0x5b, 0xc9, + 0x75, 0x06, 0x41, 0xec, 0xe8, 0x85, 0xe1, 0x4f, 0x20, 0x84, 0x20, 0xc2, 0x84, 0xdf, 0x86, 0x19, 0x6d, 0x67, 0x91, + 0x4e, 0xfa, 0x7d, 0x98, 0xe1, 0x06, 0x56, 0xf2, 0x73, 0xd5, 0x67, 0xfb, 0x6d, 0x10, 0xb2, 0x5d, 0x10, 0xb1, 0xdb, + 0x62, 0x1b, 0x94, 0xd6, 0x91, 0xf8, 0x49, 0x19, 0xfe, 0x16, 0x5e, 0x2f, 0x0f, 0x21, 0xde, 0xa7, 0x97, 0xe6, 0x67, + 0x69, 0x2b, 0x0a, 0x70, 0x1f, 0xa1, 0x47, 0x75, 0x20, 0xd8, 0x09, 0x4f, 0x78, 0x00, 0x27, 0xab, 0x59, 0xc5, 0x3f, + 0xa4, 0x20, 0x4e, 0x14, 0x1c, 0x02, 0xae, 0xb6, 0x9f, 0xd2, 0xaf, 0x60, 0xf8, 0xd2, 0xc1, 0x96, 0xc3, 0xdb, 0x62, + 0xdb, 0x63, 0x25, 0x7f, 0x04, 0xec, 0x5b, 0x3d, 0x19, 0xab, 0xdb, 0x03, 0x67, 0x5d, 0x4a, 0xd1, 0xf1, 0xa6, 0x38, + 0xbc, 0x3d, 0x9f, 0xed, 0xb7, 0x41, 0xc4, 0x76, 0x41, 0x86, 0xb5, 0x4e, 0x1a, 0x8e, 0x83, 0x21, 0x7c, 0x16, 0x23, + 0xec, 0xff, 0xa2, 0x1e, 0x78, 0x09, 0xa9, 0xa1, 0xc0, 0xc5, 0x60, 0xc3, 0xd1, 0xda, 0x2e, 0xd3, 0xc0, 0x4d, 0x0d, + 0x7a, 0x7d, 0x4f, 0x21, 0xca, 0x0b, 0x46, 0x73, 0x23, 0x58, 0x37, 0x86, 0x5c, 0x1c, 0x8e, 0x9b, 0xc5, 0x90, 0x97, + 0x34, 0x9d, 0x06, 0xa1, 0x74, 0x67, 0x59, 0x43, 0x12, 0x65, 0x1f, 0x84, 0xda, 0xb5, 0x65, 0xbf, 0x0d, 0x6c, 0x5f, + 0xfe, 0x68, 0x18, 0xfb, 0x17, 0x8b, 0x27, 0x42, 0xba, 0x88, 0xe7, 0x20, 0x88, 0xda, 0xcf, 0xb3, 0xe1, 0xc6, 0xbf, + 0x58, 0x3f, 0x11, 0xca, 0x6f, 0x3c, 0xb7, 0xe5, 0x10, 0x91, 0xb5, 0xf0, 0x85, 0xf1, 0xf0, 0xe0, 0xca, 0xd0, 0x76, + 0x38, 0x08, 0xfd, 0xb7, 0x59, 0x23, 0xb8, 0xb1, 0xa1, 0x7d, 0xbe, 0xf0, 0x61, 0x6b, 0xa3, 0xb1, 0xa6, 0x98, 0x6e, + 0xa1, 0x7f, 0x93, 0xd9, 0xd2, 0x9e, 0x46, 0x25, 0x2f, 0x4e, 0x4d, 0x23, 0x16, 0xc2, 0x80, 0xa1, 0x9f, 0xcc, 0x23, + 0x50, 0xcd, 0x1d, 0x8f, 0x40, 0x26, 0x1f, 0xe8, 0xc1, 0x9a, 0xd4, 0xaa, 0xbf, 0x86, 0x99, 0xfc, 0x3f, 0x52, 0x61, + 0x31, 0xba, 0xdb, 0x86, 0x99, 0xfa, 0x23, 0x92, 0x7f, 0xb0, 0x9c, 0xef, 0x52, 0x2f, 0xd4, 0x7e, 0x2c, 0xac, 0xc0, + 0xa0, 0x44, 0xd5, 0x80, 0x1e, 0x88, 0xa0, 0x2a, 0x83, 0x34, 0xc3, 0xea, 0x1c, 0xf4, 0xbb, 0xa7, 0x55, 0x47, 0x72, + 0x48, 0x6b, 0x35, 0xa4, 0x82, 0xa9, 0x52, 0x83, 0xfc, 0x70, 0x58, 0xa6, 0x4c, 0x97, 0x01, 0x97, 0xf4, 0x65, 0xaa, + 0x94, 0xc2, 0x7f, 0x21, 0x00, 0x9d, 0x83, 0x7b, 0x7c, 0x39, 0x06, 0xd2, 0x0c, 0x0b, 0xbf, 0x35, 0x3b, 0xbe, 0x26, + 0xe1, 0x36, 0x09, 0x2e, 0x06, 0x38, 0x47, 0x57, 0x61, 0xb9, 0x4c, 0x21, 0x82, 0xaa, 0x84, 0xfa, 0x56, 0xa6, 0x41, + 0x69, 0xab, 0x41, 0x58, 0x93, 0x50, 0x67, 0x92, 0x8d, 0x4a, 0xdb, 0x8d, 0xc2, 0x6c, 0x11, 0xd7, 0x33, 0xc2, 0x9a, + 0xb3, 0x99, 0x6a, 0x60, 0xd2, 0x70, 0xdc, 0x34, 0x5a, 0x8b, 0x0a, 0x35, 0x85, 0x79, 0x8d, 0xab, 0x4a, 0x55, 0x77, + 0x73, 0x6a, 0x29, 0x2d, 0xda, 0xab, 0x6e, 0x92, 0x0d, 0xb9, 0x0c, 0x65, 0x18, 0x6c, 0xe4, 0x08, 0x26, 0x90, 0x24, + 0x67, 0xfe, 0x46, 0xfe, 0xa1, 0x36, 0x5d, 0x0b, 0x98, 0x63, 0xcc, 0xb2, 0x61, 0x41, 0xaf, 0xc0, 0x3d, 0xd0, 0x4a, + 0xcf, 0xa7, 0xd9, 0x45, 0x1e, 0x24, 0xc3, 0x42, 0x2f, 0x9b, 0x8c, 0xff, 0x25, 0x8c, 0x34, 0x99, 0xb1, 0x92, 0x45, + 0xb6, 0xab, 0x53, 0xe2, 0x3c, 0x4e, 0x60, 0x7b, 0x34, 0xbd, 0xe5, 0xfb, 0x0c, 0xa2, 0x82, 0x40, 0xc1, 0x8c, 0xf9, + 0xb2, 0x8b, 0xa7, 0xbe, 0xcf, 0x2c, 0x53, 0xf7, 0xe1, 0x60, 0xcc, 0xd8, 0x7e, 0xbf, 0x9f, 0xf7, 0xfb, 0x6a, 0xbe, + 0xf5, 0xfb, 0xc9, 0x33, 0xf3, 0xb7, 0x07, 0x0c, 0x0a, 0x72, 0x22, 0x9a, 0x0a, 0x11, 0xfc, 0x43, 0xf2, 0x04, 0xc9, + 0xe8, 0x8e, 0xfb, 0xdc, 0x72, 0xb6, 0xac, 0x8e, 0x40, 0x30, 0x0f, 0x87, 0x4b, 0x05, 0x76, 0x2d, 0x51, 0x24, 0x64, + 0xf9, 0x4f, 0xc0, 0x78, 0xe6, 0x3e, 0xc0, 0x92, 0x01, 0x08, 0x5b, 0xe5, 0xe9, 0x7a, 0xcf, 0x57, 0xc1, 0x3b, 0x1d, + 0xef, 0x1a, 0x2b, 0x32, 0x10, 0xb7, 0xc0, 0x46, 0xac, 0xb5, 0x07, 0xe4, 0x4c, 0x01, 0x8e, 0x17, 0x87, 0xc3, 0xb9, + 0xfc, 0xa5, 0x9b, 0xad, 0x13, 0xa8, 0x14, 0xb8, 0x3d, 0x3a, 0x39, 0xf8, 0x1f, 0x40, 0x33, 0x28, 0x87, 0x79, 0xbd, + 0xfd, 0x83, 0x39, 0xf9, 0xe9, 0x29, 0xfe, 0x09, 0x0f, 0xd1, 0xe9, 0xb7, 0x7b, 0xf3, 0x07, 0x45, 0xe5, 0xe1, 0xa0, + 0x16, 0xff, 0x39, 0xe7, 0x15, 0xfc, 0xc2, 0x37, 0x81, 0xd9, 0x64, 0xea, 0x9d, 0x7c, 0x93, 0xe7, 0x4c, 0xbd, 0xc6, + 0x2b, 0x26, 0xdf, 0xe1, 0x70, 0x2e, 0x46, 0xf5, 0x76, 0xe4, 0x44, 0x3b, 0xe5, 0x18, 0x07, 0x83, 0xff, 0x22, 0xda, + 0x26, 0x04, 0x18, 0xca, 0xe1, 0xc8, 0x6c, 0x5c, 0x59, 0xe2, 0x59, 0x3a, 0xbf, 0x9c, 0xd4, 0xe5, 0x4e, 0x2b, 0x9e, + 0xf6, 0xc0, 0xe2, 0xb6, 0x06, 0x2f, 0x80, 0x3b, 0x8b, 0xad, 0x2b, 0x05, 0x87, 0x0b, 0x88, 0x53, 0x9c, 0x80, 0x08, + 0xda, 0xef, 0x4b, 0xbc, 0x57, 0xd0, 0x27, 0xfd, 0x08, 0xc1, 0x90, 0x6f, 0x24, 0xe0, 0xae, 0xd7, 0xab, 0x31, 0xbe, + 0x97, 0x42, 0x70, 0x7d, 0xa6, 0x01, 0x68, 0xc1, 0xef, 0xf2, 0xa1, 0x9c, 0x7e, 0x13, 0x81, 0x67, 0xcb, 0xde, 0x44, + 0xb9, 0xdb, 0xf0, 0xb4, 0x9f, 0x5a, 0x08, 0xc0, 0x52, 0x3c, 0x53, 0x82, 0x05, 0x39, 0xc5, 0x5c, 0xfc, 0xbf, 0xe0, + 0x23, 0xe6, 0x7b, 0xd2, 0x45, 0x6c, 0xbd, 0x7d, 0x74, 0x61, 0x20, 0x81, 0xa6, 0x03, 0xf0, 0xe3, 0x55, 0x40, 0x57, + 0xc6, 0xbf, 0xd3, 0xb2, 0x1e, 0xeb, 0xe3, 0x3f, 0x05, 0xf7, 0xe9, 0x27, 0x0a, 0x1f, 0x1d, 0x8e, 0xab, 0x74, 0xb4, + 0xa3, 0x14, 0x44, 0x47, 0xb7, 0xcf, 0xa7, 0x2a, 0xfb, 0xae, 0x02, 0x72, 0xcb, 0x51, 0x7b, 0x2a, 0x00, 0x8b, 0x2d, + 0x1d, 0x81, 0x4f, 0xb3, 0x7c, 0x42, 0xbe, 0xd7, 0x53, 0x71, 0x75, 0xa9, 0xd3, 0xc5, 0xb3, 0xf1, 0x14, 0xfe, 0x07, + 0x62, 0x0f, 0xcb, 0x14, 0xd9, 0xb1, 0xeb, 0xe2, 0x07, 0xf1, 0xb6, 0xb6, 0xa3, 0x3f, 0x76, 0x10, 0xe9, 0xb8, 0x27, + 0x17, 0xea, 0x4b, 0x48, 0x25, 0x17, 0xea, 0x06, 0x62, 0x17, 0x6a, 0xbc, 0xe3, 0x22, 0xd6, 0xfa, 0xdb, 0x1a, 0x05, + 0x2b, 0x01, 0x67, 0xda, 0x5b, 0x30, 0xd8, 0xc0, 0xba, 0x65, 0x19, 0xfc, 0x0d, 0xd7, 0x34, 0x81, 0x1b, 0x16, 0x59, + 0xef, 0x0d, 0xb6, 0xd2, 0x5b, 0x70, 0xb4, 0x4c, 0x9c, 0x4b, 0x49, 0x52, 0xb6, 0xc8, 0xb8, 0x7a, 0x14, 0x52, 0x35, + 0xdd, 0xdf, 0x8a, 0xfa, 0x5e, 0x88, 0x3c, 0x58, 0xa5, 0x2c, 0x2a, 0x56, 0x20, 0xb3, 0x07, 0xff, 0x0a, 0x19, 0x39, + 0xca, 0x81, 0xa3, 0xd0, 0x3f, 0x9a, 0x40, 0xe7, 0xa9, 0x23, 0x9d, 0x47, 0x82, 0xad, 0xd4, 0x43, 0x61, 0xe5, 0x05, + 0x44, 0x07, 0xdb, 0x31, 0xb7, 0xf2, 0x24, 0x54, 0x6c, 0xca, 0x44, 0x1e, 0x07, 0xb5, 0x04, 0x8c, 0x15, 0x04, 0x73, + 0x96, 0x4b, 0x17, 0xa4, 0xaa, 0xd1, 0xc3, 0x22, 0x73, 0x3f, 0x16, 0x94, 0xff, 0xb1, 0xca, 0x09, 0xd7, 0x97, 0x21, + 0xc0, 0xd1, 0x3e, 0x06, 0x51, 0x62, 0xac, 0x5f, 0xb4, 0x78, 0x27, 0x33, 0x67, 0x53, 0xdb, 0x4b, 0x90, 0xb1, 0x1d, + 0x7e, 0x85, 0xd0, 0x6a, 0xa1, 0xc8, 0xa2, 0xe1, 0x82, 0xe9, 0xf6, 0x94, 0x56, 0xdd, 0xc3, 0x86, 0x27, 0xa5, 0x87, + 0x4a, 0x7d, 0x1b, 0x13, 0x58, 0x56, 0x29, 0xc3, 0xb7, 0x13, 0xaa, 0x4e, 0x0c, 0x2a, 0xd6, 0x0d, 0x5b, 0xc0, 0x21, + 0x16, 0x93, 0xc6, 0x3a, 0x1b, 0xf0, 0x88, 0x25, 0xf0, 0xcf, 0x86, 0x8f, 0xd9, 0x82, 0x47, 0x93, 0xcd, 0xd5, 0xa2, + 0xdf, 0x2f, 0xbd, 0xd0, 0xab, 0x67, 0xd9, 0xe3, 0x68, 0x3e, 0xcb, 0xe7, 0x3e, 0x2a, 0x2e, 0x26, 0x83, 0xc1, 0xc6, + 0xcf, 0x86, 0x43, 0x96, 0x0c, 0x87, 0x93, 0xec, 0x31, 0xbc, 0xf6, 0x98, 0x47, 0x6a, 0x49, 0x25, 0x57, 0x19, 0xec, + 0xef, 0x03, 0x1e, 0xf9, 0xac, 0xf3, 0xd3, 0xb2, 0xe9, 0xd2, 0xfd, 0xcc, 0x8e, 0xbb, 0xd0, 0x1d, 0x60, 0xe3, 0x6d, + 0x83, 0x8e, 0xfc, 0xdb, 0x1d, 0x52, 0xea, 0x26, 0x03, 0xb0, 0x1b, 0x0d, 0x70, 0xc8, 0x54, 0x2f, 0x45, 0x56, 0x2f, + 0x65, 0xaa, 0x97, 0x64, 0xe5, 0x12, 0x2c, 0x24, 0xa6, 0xca, 0x6d, 0x64, 0xe5, 0x16, 0x0d, 0xd7, 0xc3, 0xc1, 0xd6, + 0x8a, 0xcb, 0x66, 0x09, 0xf7, 0x85, 0x15, 0x05, 0xfe, 0xdf, 0xb2, 0x1b, 0x76, 0x27, 0x8f, 0x81, 0x6b, 0x74, 0x4c, + 0x82, 0x0b, 0xc4, 0x1d, 0xbb, 0x05, 0x3b, 0x2c, 0xfc, 0x05, 0xd7, 0xc9, 0x31, 0xdb, 0xe1, 0xa3, 0xd0, 0x2b, 0xd8, + 0xad, 0x4f, 0x40, 0xbb, 0x60, 0x6b, 0x80, 0x6c, 0x6c, 0x8b, 0x8f, 0x96, 0x87, 0xc3, 0xb5, 0xe7, 0xb3, 0x7b, 0xfc, + 0x71, 0xbe, 0x3c, 0x1c, 0x76, 0x9e, 0x51, 0xef, 0xbd, 0xe5, 0x09, 0x7b, 0xcf, 0x93, 0xc9, 0xdb, 0x2b, 0x1e, 0x4f, + 0x06, 0x83, 0xb7, 0xfe, 0x0d, 0xaf, 0x67, 0x6f, 0x41, 0x3b, 0x70, 0x7e, 0x23, 0x75, 0xcd, 0xde, 0x2d, 0xcf, 0xbc, + 0x1b, 0x1c, 0x9b, 0x5b, 0x38, 0x7a, 0xfb, 0x7d, 0x6f, 0xc9, 0x23, 0xef, 0x96, 0x54, 0x4c, 0x2b, 0xae, 0x38, 0xde, + 0xb6, 0xb8, 0x9f, 0xae, 0x78, 0x08, 0x8f, 0xb0, 0x2a, 0xd3, 0xb7, 0xc1, 0x7b, 0x9f, 0xad, 0x34, 0x0b, 0xdc, 0x3d, + 0xe6, 0x58, 0x93, 0x9d, 0xd0, 0x4c, 0xfc, 0x15, 0xf6, 0xcf, 0x5b, 0xd5, 0x3f, 0x34, 0xff, 0x4b, 0xdd, 0x4f, 0xe0, + 0xf6, 0x45, 0x16, 0x24, 0xf6, 0x9e, 0xbf, 0x65, 0x77, 0xdc, 0xb0, 0xcd, 0x9e, 0x99, 0xb2, 0x4f, 0x94, 0x1a, 0x3f, + 0x50, 0xea, 0xda, 0x32, 0xac, 0xb4, 0xae, 0x7c, 0x08, 0x1c, 0x0e, 0xc8, 0x4f, 0x4b, 0xc4, 0x41, 0x68, 0xdd, 0x64, + 0x35, 0x57, 0x94, 0x73, 0xa1, 0x0d, 0x33, 0x2f, 0x07, 0x16, 0xb3, 0x94, 0x42, 0x63, 0x01, 0x80, 0x60, 0x52, 0x68, + 0xed, 0xbd, 0x0c, 0x20, 0x27, 0x68, 0xf8, 0x63, 0x73, 0x55, 0x96, 0xb5, 0x6c, 0x49, 0x88, 0xb2, 0x5d, 0x0f, 0x2f, + 0x11, 0x32, 0xad, 0xdf, 0x3f, 0x27, 0x92, 0xb5, 0x49, 0x75, 0x55, 0xa3, 0x25, 0xa0, 0x22, 0x4b, 0xc0, 0xc4, 0xaf, + 0x34, 0x9f, 0x00, 0x3c, 0xe9, 0x78, 0x50, 0x3d, 0xe6, 0x35, 0x13, 0x44, 0xb6, 0x51, 0xf9, 0x93, 0xe2, 0x19, 0x92, + 0x11, 0x14, 0x8f, 0x6b, 0x95, 0xb1, 0x30, 0xcc, 0x03, 0x05, 0xe4, 0xdd, 0xbb, 0x53, 0xdf, 0xda, 0x1f, 0x3b, 0xf6, + 0x6c, 0xad, 0x42, 0x2d, 0xd4, 0x14, 0x2e, 0x39, 0x44, 0x57, 0xa0, 0x81, 0x22, 0x92, 0xf1, 0xe4, 0xf5, 0xe0, 0x72, + 0x12, 0x5d, 0x71, 0x81, 0xce, 0xf8, 0xfa, 0xa6, 0x9b, 0xce, 0xa2, 0xc7, 0xd5, 0x7c, 0x42, 0x4a, 0xb2, 0xc3, 0x21, + 0x1b, 0x55, 0x75, 0xb1, 0x9e, 0x86, 0xf2, 0xa7, 0x87, 0xe0, 0xeb, 0x05, 0xf5, 0x9a, 0xac, 0x52, 0xfd, 0x98, 0x2a, + 0xe5, 0x45, 0xc3, 0x4b, 0xff, 0x71, 0x25, 0xf7, 0x3d, 0x20, 0xad, 0xe5, 0x25, 0x97, 0xef, 0x47, 0x88, 0x31, 0xe2, + 0x07, 0x5e, 0xc9, 0x23, 0x16, 0xaa, 0x29, 0x5c, 0xf3, 0x08, 0x41, 0xde, 0x32, 0x1d, 0xfc, 0xad, 0x27, 0x4e, 0xf7, + 0x27, 0x4a, 0xbb, 0xf8, 0xc2, 0xa2, 0xee, 0x39, 0xd2, 0x0d, 0xc8, 0xc1, 0x86, 0xe9, 0xa2, 0x20, 0xdb, 0x94, 0x46, + 0xd0, 0x46, 0xcb, 0x81, 0x0d, 0xa7, 0x52, 0x1b, 0xce, 0x5c, 0x43, 0x70, 0x9f, 0x9f, 0xa7, 0xa3, 0x1b, 0xf8, 0x90, + 0xea, 0xf6, 0x12, 0x3f, 0x1f, 0x36, 0x1c, 0xc9, 0xec, 0x88, 0xcf, 0x6c, 0x22, 0xe9, 0xa4, 0xce, 0x15, 0xb0, 0xdb, + 0xd9, 0x35, 0xc8, 0x11, 0x33, 0xf7, 0x15, 0xaa, 0x6f, 0xd1, 0x80, 0x2b, 0x63, 0xed, 0x6b, 0x92, 0xb1, 0xf0, 0xaa, + 0x9c, 0x86, 0x03, 0x80, 0xa1, 0xcb, 0xe8, 0x6b, 0x8b, 0x4d, 0x96, 0xbd, 0x29, 0x20, 0x08, 0xa2, 0x24, 0x1e, 0x1f, + 0xf0, 0xbe, 0xac, 0x86, 0x1a, 0x25, 0x1f, 0xcb, 0x4e, 0xe0, 0xeb, 0x25, 0xfa, 0xbb, 0x31, 0x97, 0x18, 0xf0, 0xba, + 0x6a, 0x0b, 0x0a, 0xe7, 0xf9, 0xe1, 0x70, 0x9e, 0x8f, 0x8c, 0x67, 0x19, 0xa8, 0x56, 0xa6, 0x75, 0xb0, 0x31, 0xf3, + 0xc5, 0xc2, 0x5f, 0xec, 0x9c, 0x44, 0x44, 0x41, 0x60, 0x47, 0xc2, 0x83, 0x48, 0xfd, 0xbe, 0xf2, 0x74, 0xa7, 0xfa, + 0x6c, 0x7f, 0x63, 0x13, 0xe9, 0x05, 0x25, 0x93, 0x4f, 0x82, 0xbd, 0xea, 0xef, 0x20, 0x6c, 0x08, 0x6f, 0x5e, 0xf5, + 0x3a, 0xcb, 0xd4, 0xac, 0x04, 0x09, 0x33, 0xe6, 0x08, 0x1e, 0x87, 0x9d, 0xc6, 0x36, 0x3c, 0xb6, 0xb0, 0x1a, 0xbd, + 0x35, 0x5b, 0xb2, 0x15, 0xbb, 0x55, 0x75, 0xba, 0xe1, 0xe1, 0x74, 0x78, 0x19, 0xe0, 0xea, 0x5b, 0x9f, 0x73, 0xbe, + 0xa4, 0x13, 0x6c, 0x3d, 0xe0, 0xd1, 0x44, 0xcc, 0xd6, 0x8f, 0x23, 0xb5, 0x78, 0xd6, 0x43, 0x7e, 0x43, 0xeb, 0x4f, + 0xcc, 0x96, 0x26, 0x79, 0x39, 0xe0, 0x37, 0x93, 0xf5, 0xe3, 0x08, 0x5e, 0x7d, 0x0c, 0x56, 0x8c, 0xcc, 0x99, 0x65, + 0xeb, 0xc7, 0x11, 0x8e, 0xd9, 0xf2, 0x71, 0x44, 0xa3, 0xb6, 0x92, 0xfb, 0xd2, 0x6d, 0x03, 0xc2, 0xca, 0x2d, 0x8b, + 0xe1, 0x35, 0x10, 0xcf, 0xb4, 0x91, 0x74, 0x2d, 0x0d, 0xbd, 0x31, 0x0f, 0xa7, 0x71, 0xb0, 0xa6, 0x56, 0xc8, 0x33, + 0x43, 0xcc, 0xe2, 0xc7, 0xd1, 0x9c, 0xad, 0xb0, 0x22, 0x1b, 0x1e, 0x0f, 0x2e, 0x27, 0x9b, 0x2b, 0xbe, 0x06, 0xf2, + 0xb3, 0xc9, 0xc6, 0x6c, 0x51, 0xb7, 0x5c, 0xcc, 0x36, 0x8f, 0xa3, 0xf9, 0x64, 0x05, 0x3d, 0x6b, 0x0f, 0x98, 0xf7, + 0x0a, 0x44, 0x28, 0x09, 0xa9, 0x29, 0x37, 0xbd, 0x1e, 0x5b, 0x8f, 0x83, 0x25, 0x5b, 0x5f, 0x06, 0xb7, 0x6c, 0x3d, + 0x06, 0x22, 0x0e, 0xea, 0x77, 0x6f, 0x03, 0x8b, 0x2f, 0x62, 0xeb, 0x4b, 0x93, 0xb6, 0x79, 0x1c, 0x31, 0x77, 0x70, + 0x1a, 0xb8, 0x60, 0x2d, 0x32, 0x6f, 0xc5, 0xe0, 0x12, 0xb2, 0xf0, 0x62, 0xb6, 0x19, 0x5e, 0xb2, 0xf5, 0x08, 0xa7, + 0x7a, 0xe2, 0xb3, 0x25, 0xbf, 0x65, 0x09, 0x5f, 0x35, 0xf1, 0xd5, 0x06, 0x34, 0xa2, 0x47, 0x19, 0xf4, 0x15, 0xd4, + 0xcc, 0x9c, 0xf7, 0x16, 0x46, 0xe5, 0xbe, 0x05, 0x07, 0x14, 0xa4, 0x6d, 0x80, 0x20, 0x89, 0x67, 0x77, 0x1d, 0xae, + 0x3f, 0x49, 0x61, 0xc0, 0x4d, 0x60, 0x06, 0x0c, 0x4c, 0x3f, 0x83, 0x1f, 0x56, 0xba, 0x44, 0x88, 0xb3, 0x9f, 0x52, + 0x92, 0xcc, 0xf3, 0xf7, 0x22, 0xcd, 0xdd, 0xc2, 0x75, 0x0a, 0xb3, 0xa2, 0x40, 0xf5, 0x53, 0x52, 0x1a, 0x58, 0xa8, + 0x44, 0xa6, 0x52, 0xf0, 0xcb, 0xe6, 0x3c, 0xca, 0x8e, 0xd1, 0xb9, 0xce, 0x2f, 0x27, 0xce, 0xe9, 0xa4, 0xef, 0x3f, + 0x70, 0x0c, 0x5b, 0xc8, 0xc0, 0x85, 0x3f, 0xf5, 0x84, 0x71, 0x6a, 0x05, 0x62, 0x2a, 0x79, 0xf6, 0x14, 0x3e, 0x13, + 0x5a, 0x1d, 0x5d, 0xf8, 0x7e, 0x50, 0x68, 0x93, 0x74, 0x0b, 0x92, 0x14, 0x3c, 0x45, 0xcf, 0x39, 0x6f, 0x03, 0x95, + 0x62, 0x44, 0x0b, 0x22, 0x6d, 0xad, 0x33, 0x07, 0x69, 0x4b, 0xf3, 0x5d, 0x13, 0x3f, 0x87, 0x05, 0x5c, 0x44, 0x0b, + 0x5b, 0xc3, 0xa3, 0x2a, 0x56, 0xee, 0x4d, 0x9e, 0x23, 0x9c, 0xd1, 0xa5, 0x4c, 0x00, 0x5c, 0xef, 0x97, 0x61, 0xad, + 0xf0, 0x8a, 0x9a, 0x9b, 0xbc, 0xa8, 0xe9, 0x93, 0x2d, 0x70, 0x1f, 0x8b, 0x12, 0x05, 0xce, 0x5a, 0x30, 0x60, 0x2b, + 0x2c, 0xd9, 0x49, 0x61, 0x53, 0xb4, 0x84, 0xde, 0x1e, 0x3f, 0x1d, 0xd4, 0x4c, 0x06, 0xd0, 0x04, 0xd0, 0x78, 0xfc, + 0x0b, 0x40, 0x4d, 0x3f, 0xd5, 0x62, 0x5d, 0x05, 0xa5, 0x52, 0x6e, 0xc2, 0xcf, 0xc0, 0x30, 0xc3, 0x0f, 0x85, 0xdc, + 0x26, 0x4a, 0xe4, 0xfc, 0xb8, 0x29, 0xc5, 0xa2, 0x14, 0x55, 0xd2, 0x6e, 0x28, 0x78, 0x44, 0xb8, 0x0d, 0x1a, 0x33, + 0xb7, 0x27, 0xba, 0x68, 0x45, 0x28, 0xc7, 0x66, 0x1d, 0x23, 0x8d, 0x32, 0x3b, 0xd9, 0x75, 0xb2, 0xd0, 0x7e, 0x5f, + 0xe5, 0x90, 0x75, 0xc0, 0x1a, 0xc9, 0xd7, 0x6b, 0x0e, 0xdd, 0x36, 0xca, 0x8b, 0x7b, 0xcf, 0x57, 0x70, 0x9a, 0xe3, + 0x89, 0xdd, 0xf5, 0xba, 0x53, 0x24, 0xe2, 0x15, 0x4e, 0xaa, 0x7c, 0x24, 0x0b, 0xc7, 0x9d, 0x3b, 0xad, 0xc5, 0xaa, + 0x72, 0x59, 0x4f, 0x2d, 0x8e, 0x08, 0x7c, 0x2a, 0x8f, 0xf6, 0x42, 0xdb, 0xa2, 0x58, 0x08, 0xa3, 0x47, 0x27, 0xfc, + 0xa4, 0x04, 0xd6, 0xd7, 0xe1, 0xb0, 0xf4, 0x23, 0x8e, 0x7e, 0xa7, 0xd1, 0xe8, 0x86, 0x90, 0x86, 0xa7, 0x5e, 0x34, + 0xba, 0xa9, 0x8b, 0x3a, 0xcc, 0x9e, 0xe5, 0x7a, 0xa0, 0x30, 0x8c, 0x40, 0xfd, 0xe0, 0x2a, 0x83, 0xcf, 0x22, 0x44, + 0xcd, 0x03, 0xd3, 0x6c, 0x08, 0x47, 0x5d, 0xe0, 0xa1, 0x15, 0xb4, 0x98, 0x99, 0x8f, 0x42, 0x0c, 0x1f, 0xd2, 0xc5, + 0xf9, 0x13, 0xb2, 0xf2, 0x01, 0x76, 0x87, 0xee, 0x42, 0x39, 0x67, 0x2a, 0x06, 0xf8, 0x51, 0x40, 0x3e, 0x4a, 0xc0, + 0xcd, 0x00, 0xd9, 0x23, 0x4b, 0x00, 0xb1, 0x62, 0x74, 0x34, 0xf9, 0xdc, 0xf7, 0x22, 0x05, 0xef, 0xec, 0xb3, 0x5c, + 0x4d, 0x18, 0x0a, 0x9f, 0x18, 0xe8, 0xe6, 0x37, 0x7e, 0x7b, 0xde, 0x82, 0x91, 0x5d, 0x92, 0xe2, 0xb5, 0x66, 0xb8, + 0xdf, 0x80, 0xdb, 0x11, 0x50, 0xd6, 0x54, 0xc7, 0x24, 0xdb, 0x34, 0x44, 0x32, 0x60, 0x46, 0x8c, 0x08, 0x2a, 0xcb, + 0x85, 0xff, 0xdd, 0xcb, 0xa2, 0xc0, 0x01, 0x5c, 0xcd, 0x64, 0xf0, 0xda, 0x85, 0x51, 0x01, 0x70, 0x4e, 0x43, 0xa7, + 0xb4, 0x57, 0x55, 0x87, 0x64, 0xd5, 0xfc, 0x60, 0x36, 0x6f, 0x1a, 0x26, 0x46, 0x04, 0xd1, 0x45, 0x38, 0xc1, 0xf4, + 0x8a, 0xf4, 0xb5, 0x92, 0xd3, 0xd1, 0xaa, 0xa3, 0xb5, 0xc4, 0xc4, 0x5c, 0x51, 0xfc, 0x35, 0xe0, 0x71, 0x83, 0x57, + 0x27, 0x69, 0x3a, 0x51, 0x3d, 0x7a, 0xfc, 0x3a, 0x4d, 0x27, 0x25, 0xee, 0x0a, 0xbf, 0x01, 0x17, 0xcd, 0x36, 0x1f, + 0xfa, 0xf1, 0x0b, 0x8a, 0xb8, 0xa8, 0xc1, 0x95, 0x77, 0xaa, 0xaf, 0x54, 0x1f, 0x41, 0x2d, 0x3c, 0x31, 0xb2, 0x16, + 0x9e, 0x5c, 0xb2, 0xd6, 0x82, 0x60, 0x66, 0x73, 0xe0, 0x42, 0x7e, 0xa5, 0x14, 0xf1, 0x26, 0x12, 0x6a, 0x31, 0x68, + 0x3d, 0x66, 0xce, 0xaa, 0xd1, 0x8d, 0xca, 0x8c, 0xd0, 0xbe, 0xad, 0x45, 0xe7, 0x37, 0xf2, 0x53, 0x9e, 0xda, 0x97, + 0xed, 0x71, 0x3e, 0xde, 0xa3, 0xbb, 0xea, 0x2c, 0x33, 0x29, 0xe3, 0x93, 0x59, 0x82, 0xc2, 0x5d, 0x82, 0x0d, 0x48, + 0xb2, 0xdf, 0xea, 0x00, 0x19, 0xb5, 0xd7, 0x7e, 0xd7, 0x59, 0xbe, 0xba, 0xd9, 0x1a, 0x8a, 0x4a, 0xad, 0x24, 0xc5, + 0x41, 0x86, 0xeb, 0xb6, 0xf2, 0xe1, 0xe2, 0x02, 0x7a, 0xc6, 0x48, 0x64, 0x9e, 0x3f, 0x91, 0x2f, 0xc1, 0x39, 0xe3, + 0xac, 0x10, 0x98, 0x30, 0x56, 0xef, 0x5a, 0x4b, 0xa5, 0x21, 0xc5, 0xd8, 0xd1, 0x28, 0xcb, 0x2a, 0x4b, 0x97, 0xd9, + 0x5a, 0xc2, 0x96, 0x55, 0xe4, 0x16, 0xb6, 0xce, 0x64, 0x35, 0x1f, 0x55, 0xdc, 0x41, 0xf9, 0x66, 0xcb, 0x8c, 0xef, + 0x25, 0xb2, 0x77, 0x1b, 0x28, 0xe1, 0xd9, 0xe8, 0x3f, 0x90, 0x7e, 0x9b, 0x61, 0x9c, 0x72, 0x5b, 0x49, 0x0b, 0x70, + 0xfa, 0x87, 0xc3, 0xa3, 0x0a, 0x83, 0x06, 0x47, 0x18, 0x47, 0xd6, 0xef, 0xdf, 0x54, 0x5e, 0x8d, 0x89, 0x3a, 0x3e, + 0xab, 0xdf, 0xaf, 0xe8, 0xe1, 0xb4, 0x1a, 0xad, 0xd2, 0x2d, 0xb2, 0x13, 0xda, 0x58, 0xf9, 0x41, 0xad, 0x80, 0xd9, + 0x5b, 0x9f, 0x4f, 0x07, 0xa0, 0x63, 0x01, 0x12, 0xcd, 0x66, 0x22, 0x31, 0x27, 0xdd, 0x93, 0xf0, 0xf8, 0xc0, 0x02, + 0x07, 0x98, 0x8a, 0xff, 0x53, 0x78, 0x33, 0xb0, 0x41, 0xa3, 0x44, 0x5f, 0xa3, 0xab, 0xda, 0xdc, 0xe8, 0x78, 0xe9, + 0x29, 0x24, 0xb2, 0x82, 0x55, 0x73, 0x5f, 0x6e, 0xe0, 0xb4, 0x87, 0x9a, 0x43, 0x65, 0x01, 0xfe, 0xf6, 0x0b, 0x30, + 0x78, 0x64, 0x50, 0xd8, 0x6e, 0x2d, 0xb4, 0x37, 0x66, 0xa9, 0x86, 0x8a, 0x70, 0xd0, 0xf9, 0x4a, 0xcc, 0xea, 0x11, + 0xfd, 0x3d, 0x3f, 0x1c, 0x56, 0x04, 0x06, 0x1c, 0x96, 0x32, 0x13, 0x2d, 0x14, 0x4b, 0xeb, 0x6c, 0x46, 0x75, 0xe0, + 0x81, 0x89, 0x39, 0x0b, 0x77, 0x00, 0xda, 0xa4, 0x56, 0x81, 0x5e, 0x45, 0xf4, 0x13, 0xf7, 0x6b, 0xfb, 0xf5, 0x7a, + 0x64, 0x96, 0x8e, 0xdc, 0x18, 0x0b, 0x00, 0x0e, 0x3c, 0xaf, 0x49, 0x9e, 0x93, 0xaf, 0xa1, 0xdd, 0x93, 0x0b, 0xf9, + 0x13, 0x94, 0x2d, 0x3c, 0x57, 0x4d, 0x2b, 0x8b, 0x15, 0x57, 0xd5, 0xab, 0x0b, 0x5e, 0x99, 0x4c, 0xab, 0xb4, 0x12, + 0x95, 0x12, 0x0c, 0xa8, 0x4b, 0xbc, 0xd6, 0x34, 0xa3, 0xd4, 0x46, 0x9d, 0x89, 0x1a, 0xb0, 0xc1, 0x7e, 0xaa, 0x36, + 0x3a, 0x39, 0x97, 0xcf, 0x2f, 0x8d, 0xc3, 0xa7, 0x5d, 0xbd, 0x99, 0xa9, 0x1c, 0xf8, 0x6b, 0xe5, 0x43, 0xab, 0xc7, + 0x40, 0x07, 0xe4, 0xf4, 0xc7, 0xb0, 0x98, 0xd8, 0x1d, 0x9a, 0xb7, 0xbb, 0xcb, 0xea, 0x22, 0xbd, 0xd3, 0x94, 0xcc, + 0xea, 0x2d, 0x9f, 0x59, 0x3d, 0x3a, 0xe0, 0xc5, 0x43, 0xbd, 0x57, 0x98, 0x49, 0x04, 0x17, 0x43, 0x35, 0x89, 0xec, + 0x0e, 0xb4, 0xe6, 0x51, 0xc5, 0x04, 0xf8, 0x41, 0xa9, 0x35, 0xbd, 0xb7, 0xbb, 0x42, 0x9d, 0x52, 0x78, 0xdc, 0x5a, + 0xf2, 0x03, 0x73, 0xa7, 0x5d, 0xeb, 0x7c, 0x3c, 0xbf, 0xf4, 0xfd, 0x46, 0x9e, 0xd0, 0x66, 0x67, 0x72, 0xfa, 0x27, + 0x6f, 0xf5, 0x0f, 0x53, 0x7d, 0x0b, 0xdd, 0x09, 0xfa, 0x0c, 0x5d, 0x55, 0xdd, 0x95, 0xd8, 0xc2, 0x50, 0x4f, 0x2c, + 0xf2, 0x42, 0x9e, 0xb4, 0xc6, 0x8e, 0x83, 0xbd, 0x01, 0x4e, 0xfc, 0xf2, 0x70, 0x10, 0x57, 0xb9, 0xcf, 0xce, 0xbb, + 0x46, 0x56, 0x0e, 0x60, 0x05, 0x51, 0x30, 0x6e, 0xcd, 0xc7, 0x36, 0x48, 0x97, 0xb8, 0x1a, 0x1f, 0xbf, 0xa1, 0x58, + 0x26, 0x9b, 0x88, 0x8b, 0x8b, 0xfc, 0xf1, 0x53, 0x20, 0x2d, 0xeb, 0xf7, 0xa3, 0x67, 0x97, 0xd3, 0xa7, 0xc3, 0x28, + 0x00, 0xc7, 0x2e, 0x7b, 0x79, 0x19, 0xf3, 0xd5, 0x25, 0xb3, 0x4c, 0x61, 0x91, 0x6f, 0x06, 0x54, 0x97, 0xac, 0x96, + 0xae, 0x57, 0x80, 0xa5, 0xcb, 0x6f, 0xee, 0xc3, 0xd4, 0x80, 0x46, 0xd6, 0xdc, 0x9d, 0xe6, 0x5a, 0xa0, 0xd4, 0xf3, + 0x7e, 0x66, 0xc8, 0xd7, 0x65, 0xd0, 0x15, 0xa4, 0x7b, 0x1e, 0x91, 0x5e, 0xee, 0xa5, 0xd3, 0xfd, 0xbe, 0x14, 0x60, + 0xa9, 0x2f, 0xc5, 0x17, 0x50, 0x58, 0x34, 0xbe, 0x11, 0xa0, 0xad, 0xa1, 0x9a, 0xf6, 0x4a, 0x51, 0xf5, 0x82, 0x5e, + 0x29, 0xbe, 0xf4, 0xf4, 0x50, 0x99, 0x2f, 0x4b, 0x47, 0xff, 0x13, 0x6a, 0x2e, 0x38, 0x21, 0x66, 0x62, 0x0e, 0xa0, + 0x12, 0xb4, 0xf1, 0xdd, 0x1e, 0x6d, 0x7c, 0xaa, 0x57, 0x71, 0xd3, 0xe7, 0xb5, 0xb5, 0xcc, 0x09, 0x61, 0xd3, 0xbd, + 0x04, 0xa8, 0xc8, 0x2b, 0xe1, 0x11, 0x2c, 0xbf, 0xfc, 0x21, 0x4f, 0x57, 0x88, 0xd6, 0x71, 0xcf, 0x32, 0x97, 0xc6, + 0xfe, 0x95, 0xc1, 0xf4, 0xf5, 0xed, 0xb6, 0xc8, 0x4f, 0x4d, 0x4c, 0x58, 0x8f, 0x15, 0x7d, 0xf3, 0x2e, 0x5c, 0x09, + 0x14, 0x38, 0x94, 0x48, 0x6c, 0x53, 0x85, 0x22, 0x1e, 0x24, 0x7d, 0xba, 0x68, 0x7d, 0x1a, 0x60, 0x6a, 0x2d, 0x07, + 0xe6, 0x10, 0xae, 0xe2, 0xc2, 0x47, 0x4f, 0xdf, 0x62, 0x16, 0xce, 0x27, 0xde, 0x47, 0xaf, 0x18, 0x99, 0x8f, 0xfb, + 0xa8, 0x54, 0xd2, 0x3f, 0x0f, 0x87, 0x59, 0x35, 0xf7, 0x1d, 0xfa, 0x48, 0x0f, 0x55, 0x2e, 0x28, 0x7b, 0x63, 0x4c, + 0x22, 0x50, 0x1a, 0xe3, 0x7d, 0x1c, 0x1c, 0xe7, 0x7d, 0x1a, 0x40, 0x6a, 0x9f, 0x78, 0x4f, 0x4a, 0x0e, 0xcf, 0x39, + 0xe6, 0x84, 0xd2, 0x8a, 0x80, 0x09, 0x3d, 0x43, 0xb9, 0xee, 0x94, 0x82, 0x49, 0x0e, 0x09, 0x86, 0xbf, 0x6a, 0xde, + 0xc4, 0x0a, 0x84, 0x5d, 0x33, 0xaf, 0x46, 0x8f, 0xaa, 0x24, 0x2c, 0x05, 0x1c, 0x95, 0x99, 0x67, 0xd8, 0x1b, 0x1e, + 0x19, 0x46, 0x0e, 0x96, 0xfb, 0xa3, 0x3a, 0x11, 0xb9, 0x47, 0x17, 0x18, 0x95, 0x85, 0xe7, 0x0d, 0x5d, 0x69, 0x50, + 0x49, 0x76, 0xfc, 0x15, 0xd7, 0x80, 0xda, 0x1a, 0x23, 0x86, 0x02, 0x46, 0xc1, 0x6b, 0xfb, 0x43, 0xc8, 0xa2, 0x6c, + 0xfd, 0x06, 0xc7, 0x7c, 0x56, 0x72, 0xd7, 0x3b, 0x9c, 0x85, 0x96, 0x90, 0x27, 0x77, 0x0c, 0xd2, 0x34, 0x96, 0x46, + 0xc0, 0x89, 0x48, 0xb6, 0xb1, 0x14, 0x8e, 0x00, 0x02, 0x02, 0xdd, 0x94, 0x19, 0xc6, 0x74, 0x30, 0xf2, 0x3c, 0xea, + 0x19, 0xef, 0x55, 0x78, 0x0a, 0x69, 0xb2, 0x7d, 0x3d, 0x7f, 0x6f, 0x04, 0x59, 0xb9, 0xe5, 0x1c, 0x0f, 0x8b, 0x6f, + 0x9c, 0x7d, 0x95, 0x93, 0xa7, 0x98, 0x65, 0xa4, 0x77, 0x8a, 0x79, 0x01, 0x7f, 0x2a, 0x4b, 0x7d, 0x8e, 0xd2, 0x5b, + 0xe6, 0x93, 0x55, 0x24, 0x5d, 0x78, 0x9b, 0x7e, 0x3f, 0x1e, 0xa9, 0x43, 0xcd, 0xdf, 0xc7, 0x23, 0x79, 0x86, 0x6d, + 0x58, 0xc2, 0x42, 0xab, 0x60, 0x0c, 0x20, 0x89, 0x8d, 0x88, 0x06, 0xa3, 0xbd, 0x39, 0x1c, 0xce, 0x37, 0xe6, 0x2c, + 0xd9, 0x83, 0xeb, 0x2b, 0x4f, 0xcc, 0x3b, 0xf0, 0x65, 0x1e, 0x13, 0x44, 0x6c, 0xe6, 0x6d, 0x58, 0x0d, 0x1e, 0xec, + 0xe0, 0xfa, 0x88, 0x2d, 0x8a, 0xb5, 0x8e, 0xa5, 0xb2, 0x0e, 0x4e, 0xeb, 0xd8, 0x34, 0x23, 0xa5, 0xc8, 0x3e, 0xc7, + 0xfe, 0xde, 0x0d, 0xae, 0xae, 0x8d, 0x41, 0xad, 0x71, 0x87, 0xb9, 0x73, 0x2a, 0xa0, 0x1e, 0xd3, 0x15, 0x54, 0xcf, + 0x2a, 0xf2, 0xe5, 0xb7, 0x76, 0x0e, 0x08, 0x1a, 0x81, 0xc0, 0x45, 0x03, 0x25, 0xd3, 0xa5, 0x9c, 0x77, 0x01, 0x21, + 0xbe, 0x4b, 0x41, 0x9f, 0xce, 0x60, 0x13, 0x9b, 0x4f, 0x20, 0x16, 0x4d, 0xf7, 0xb9, 0xd6, 0xcc, 0x17, 0x23, 0xda, + 0x99, 0x75, 0xb7, 0xc8, 0xad, 0x16, 0x22, 0x19, 0x3d, 0xdb, 0x4c, 0xb8, 0xeb, 0x50, 0xce, 0x48, 0xc0, 0x04, 0xad, + 0xad, 0x94, 0x7c, 0xae, 0x7b, 0x9d, 0xa0, 0x3d, 0x90, 0xb4, 0xee, 0xdf, 0x2c, 0x3a, 0xa3, 0xe4, 0xe4, 0x7a, 0x93, + 0x33, 0x48, 0xc1, 0x82, 0xed, 0x65, 0x4e, 0xb8, 0x01, 0x3e, 0xb2, 0x59, 0x72, 0x9a, 0x06, 0x79, 0x2c, 0x0c, 0xd2, + 0x47, 0x9b, 0x5f, 0x16, 0xd0, 0xa1, 0x64, 0xd1, 0x08, 0xf1, 0x00, 0x3b, 0x87, 0xe4, 0xaa, 0x40, 0xdd, 0x34, 0xd0, + 0x95, 0x2b, 0x67, 0x8a, 0x29, 0x70, 0x21, 0x14, 0x44, 0xed, 0xe8, 0x24, 0x2a, 0xe7, 0x7d, 0x52, 0x5d, 0xe6, 0xd3, + 0x42, 0x9a, 0x06, 0xf2, 0x69, 0xe5, 0x98, 0x07, 0xb6, 0xb6, 0x71, 0x4d, 0x60, 0xa0, 0x53, 0xfb, 0x5a, 0x94, 0x73, + 0xac, 0x22, 0x7a, 0x9f, 0x7f, 0xa8, 0xec, 0xe9, 0x83, 0x08, 0x1b, 0x15, 0x68, 0x2c, 0x25, 0xc6, 0x46, 0x8e, 0x7f, + 0x4b, 0x94, 0x0d, 0x19, 0x02, 0x42, 0x48, 0x1b, 0x39, 0xfd, 0xb0, 0xbe, 0x7c, 0x97, 0x69, 0xff, 0x4f, 0x12, 0xbf, + 0x0d, 0xf6, 0x72, 0xea, 0x4f, 0x3d, 0xe2, 0xf1, 0x5a, 0xa3, 0xc7, 0x94, 0x74, 0x1b, 0xe4, 0xa9, 0xf2, 0x14, 0x24, + 0x13, 0xc6, 0x02, 0x82, 0x45, 0xb9, 0xe0, 0x39, 0xaf, 0xb8, 0x84, 0xfb, 0xa8, 0x65, 0x45, 0x84, 0xaa, 0x44, 0x4e, + 0x9f, 0xaf, 0x80, 0x67, 0x02, 0x02, 0x1d, 0x63, 0xa4, 0x51, 0x05, 0x5f, 0x02, 0x63, 0x1d, 0x28, 0x3b, 0xcd, 0x48, + 0x70, 0xd9, 0xfd, 0x84, 0x44, 0xa9, 0x2f, 0x49, 0x49, 0xfa, 0x56, 0xd4, 0x78, 0x25, 0x56, 0x11, 0x09, 0x64, 0xa8, + 0x21, 0x62, 0x55, 0x3d, 0x75, 0xaf, 0x8a, 0xc9, 0x60, 0x50, 0xf9, 0x72, 0x7a, 0xe2, 0x0d, 0x0d, 0x95, 0x77, 0x5d, + 0xd1, 0x4e, 0xcf, 0xb5, 0x52, 0xde, 0x42, 0x5a, 0x82, 0xa6, 0x61, 0xa4, 0x39, 0x94, 0xba, 0x92, 0xee, 0xc6, 0x20, + 0xbe, 0x64, 0xa2, 0x67, 0x3b, 0xb5, 0xa3, 0xb4, 0x25, 0xed, 0x21, 0xa4, 0xe7, 0x2e, 0xf9, 0x98, 0x85, 0x5c, 0xdd, + 0x29, 0x27, 0xe5, 0x55, 0x88, 0x4e, 0xee, 0x7b, 0x0c, 0x89, 0x40, 0x9f, 0x73, 0x0c, 0xeb, 0xa2, 0xa1, 0xce, 0x61, + 0x85, 0x98, 0x2d, 0x94, 0x30, 0x5f, 0x32, 0x9e, 0x4a, 0x06, 0x0d, 0x80, 0x0c, 0xf8, 0xe2, 0x65, 0x60, 0xf9, 0x2b, + 0x88, 0x1f, 0x6d, 0x7c, 0x38, 0xfc, 0x55, 0x53, 0x88, 0xed, 0x5f, 0xb0, 0x19, 0xc2, 0xa3, 0x7a, 0xc0, 0x33, 0xdf, + 0xc4, 0x09, 0x5a, 0x01, 0x49, 0x99, 0x1d, 0x4d, 0x64, 0xaf, 0x7a, 0x08, 0xa7, 0xb2, 0x02, 0x75, 0x94, 0x75, 0x56, + 0xc2, 0x8f, 0x30, 0xd5, 0xad, 0xc4, 0x5a, 0xa0, 0xcd, 0xd5, 0x8a, 0xb5, 0x00, 0x0e, 0xfc, 0x1c, 0x82, 0x27, 0xf2, + 0x39, 0xb8, 0x18, 0x14, 0xe0, 0x73, 0x00, 0xbc, 0xc8, 0x5d, 0x78, 0x30, 0x0f, 0x2c, 0xab, 0x11, 0x86, 0xa3, 0x8a, + 0x58, 0xbf, 0x66, 0x3b, 0xf2, 0x81, 0xdb, 0x31, 0x3e, 0xd7, 0x1e, 0x4b, 0x96, 0x83, 0x51, 0xe6, 0x5e, 0x2d, 0xd1, + 0xf3, 0x26, 0x8d, 0x9b, 0xd1, 0xa3, 0x7d, 0x2d, 0xff, 0x17, 0xf4, 0x32, 0xe8, 0x6f, 0xe1, 0x96, 0xd7, 0xfc, 0x61, + 0xb9, 0x70, 0x9a, 0x5e, 0x41, 0xa4, 0x8c, 0x1a, 0x91, 0x31, 0x84, 0x4d, 0xaa, 0x9b, 0xdb, 0xa4, 0xba, 0x10, 0xf0, + 0x74, 0x44, 0xaa, 0x6b, 0x21, 0x6d, 0xe4, 0xd3, 0x3a, 0x90, 0xb1, 0x48, 0xef, 0x7e, 0xfc, 0xdb, 0xf3, 0xcf, 0xaf, + 0x7f, 0xfd, 0xf1, 0xe6, 0xf5, 0xbb, 0x57, 0xaf, 0xdf, 0xbd, 0xfe, 0xfc, 0x3b, 0x41, 0x78, 0x4c, 0x85, 0xca, 0xf0, + 0xe1, 0xfd, 0xa7, 0xd7, 0x4e, 0x06, 0xdb, 0x9b, 0x21, 0x6b, 0xdf, 0xc8, 0xc1, 0x10, 0x88, 0x6c, 0x10, 0x32, 0xc8, + 0x4e, 0xc9, 0x1c, 0x33, 0x31, 0xc7, 0xd8, 0x3b, 0x81, 0xc9, 0x16, 0x24, 0x87, 0x65, 0x5e, 0x32, 0x22, 0x57, 0x85, + 0xd6, 0x0f, 0x68, 0xc1, 0x5b, 0x70, 0x91, 0x49, 0xf3, 0xe5, 0xaf, 0x04, 0xb1, 0x4f, 0x2b, 0x29, 0xf7, 0xd5, 0xb6, + 0xe6, 0xf9, 0xf6, 0x7e, 0x2f, 0xe1, 0xfc, 0xe7, 0xd2, 0x88, 0x5a, 0x80, 0x03, 0xf0, 0x39, 0xfc, 0x71, 0xa5, 0x2d, + 0x69, 0x32, 0x8b, 0xf6, 0x33, 0x86, 0xa0, 0x4b, 0x03, 0x69, 0x62, 0x8f, 0xbc, 0xd4, 0x27, 0x0b, 0x09, 0xdc, 0x11, + 0xc3, 0xa7, 0x15, 0x41, 0xaf, 0x18, 0x51, 0x5c, 0x72, 0x85, 0x4a, 0x29, 0xf9, 0x37, 0xca, 0x2e, 0x2a, 0xe4, 0xac, + 0x60, 0x77, 0x8a, 0x1c, 0x19, 0x3f, 0x08, 0x26, 0xbe, 0x1c, 0xdc, 0x7f, 0x89, 0x77, 0x38, 0x53, 0x1c, 0xc9, 0x09, + 0xff, 0x33, 0xc3, 0xc0, 0xfe, 0x1c, 0x7c, 0x5e, 0x1d, 0xe6, 0xe5, 0x8d, 0x3e, 0xe5, 0x16, 0x7c, 0x3c, 0x59, 0x5c, + 0x81, 0xc1, 0x7e, 0xa1, 0x9a, 0xbb, 0xe6, 0xf5, 0x6c, 0x31, 0x67, 0xfb, 0x59, 0x34, 0x0f, 0x96, 0x6c, 0x96, 0xcd, + 0x83, 0x55, 0xc3, 0xd7, 0xec, 0x96, 0xaf, 0xad, 0xaa, 0xad, 0xed, 0xaa, 0x4d, 0x36, 0xfc, 0x16, 0x24, 0x84, 0xb7, + 0x99, 0x07, 0xbc, 0xc7, 0x4b, 0x9f, 0x6d, 0x40, 0xa2, 0x5d, 0xb1, 0x0d, 0x5c, 0xc4, 0xd6, 0xfc, 0x75, 0xe5, 0x6d, + 0x58, 0xc9, 0xce, 0xc7, 0x2c, 0xc7, 0xf9, 0xe7, 0xc3, 0x03, 0xda, 0x0b, 0xf5, 0xb3, 0x4b, 0xf5, 0x6c, 0xa2, 0xec, + 0x66, 0x9b, 0xd1, 0xcd, 0x5d, 0x5a, 0x6d, 0xc2, 0x0c, 0x3d, 0xcb, 0xe1, 0xa3, 0xad, 0x14, 0xfc, 0xf4, 0x0d, 0x7e, + 0xc9, 0x9a, 0x38, 0xff, 0x4c, 0xdb, 0x76, 0x55, 0x62, 0x2b, 0x68, 0x51, 0x64, 0xb5, 0xc2, 0x03, 0x73, 0xfe, 0x0c, + 0x16, 0x30, 0xf6, 0x1c, 0xe7, 0xbc, 0xf6, 0x47, 0xc8, 0x78, 0xef, 0x00, 0xa0, 0x65, 0x8e, 0x03, 0x3c, 0x62, 0xc5, + 0x28, 0x1a, 0xbc, 0xf3, 0x4b, 0x65, 0xb5, 0xd2, 0x9c, 0x84, 0xb6, 0x11, 0xab, 0x96, 0x23, 0x55, 0x33, 0x22, 0x7d, + 0x90, 0x9e, 0xf7, 0x3d, 0xa2, 0x1a, 0xec, 0xc9, 0xbc, 0x0e, 0xec, 0xd3, 0xfb, 0xd6, 0xaa, 0xee, 0xfc, 0x9e, 0x2a, + 0x5d, 0x72, 0x64, 0xcb, 0x4f, 0x97, 0xe1, 0xbd, 0xfa, 0x53, 0x72, 0x7d, 0x28, 0x70, 0x84, 0x87, 0x2a, 0xe0, 0x7c, + 0xbd, 0x12, 0xed, 0x4e, 0x84, 0x5d, 0xb9, 0x04, 0x84, 0xf8, 0x92, 0xa6, 0x39, 0x1e, 0x47, 0x34, 0x11, 0x61, 0x13, + 0xa3, 0xbf, 0xb0, 0xfb, 0x50, 0x62, 0x39, 0xcf, 0x35, 0x28, 0xb9, 0x64, 0xf0, 0x9e, 0xb4, 0xd7, 0xa0, 0x59, 0x5e, + 0x95, 0x9a, 0x4c, 0xe4, 0xa0, 0x7c, 0x38, 0x14, 0xb0, 0x97, 0x1a, 0x3f, 0x4d, 0xf8, 0x09, 0xcb, 0x5b, 0x7b, 0x6b, + 0x4a, 0x51, 0x49, 0x03, 0x54, 0xe0, 0x63, 0x06, 0xff, 0xbb, 0x33, 0xc4, 0x82, 0x29, 0x3a, 0x7e, 0x38, 0x13, 0x73, + 0xeb, 0xb9, 0x55, 0xd6, 0x51, 0xb6, 0x46, 0x39, 0x01, 0xff, 0x9e, 0xea, 0x38, 0x49, 0x84, 0x53, 0xef, 0x11, 0x17, + 0x75, 0x2f, 0x87, 0xa8, 0x1b, 0xf6, 0xb9, 0xd2, 0xc1, 0x96, 0xd3, 0x34, 0x38, 0x12, 0xbf, 0x52, 0x9f, 0x3d, 0xca, + 0x2c, 0x1e, 0x75, 0x64, 0x23, 0x4a, 0xd2, 0x38, 0x16, 0x39, 0x6c, 0xef, 0x37, 0x72, 0xff, 0xef, 0xf7, 0x21, 0x9c, + 0xb4, 0x0a, 0xe2, 0xd2, 0x13, 0x88, 0x08, 0x47, 0x87, 0x1f, 0x11, 0x9e, 0x48, 0x55, 0xe1, 0x87, 0xfa, 0xc4, 0x8d, + 0xd9, 0xbd, 0x30, 0x47, 0xf5, 0x16, 0x60, 0x18, 0xeb, 0xad, 0x45, 0x48, 0xa2, 0x95, 0x66, 0xb4, 0xf5, 0x80, 0x18, + 0xf1, 0x7e, 0x6d, 0x91, 0xc1, 0x58, 0x5b, 0x12, 0x09, 0xe0, 0x4b, 0x12, 0x32, 0xb4, 0x6d, 0x04, 0x66, 0x0c, 0x6f, + 0x67, 0xc5, 0xa5, 0xeb, 0xb0, 0xcd, 0x39, 0x7c, 0x21, 0x37, 0x9a, 0x75, 0x44, 0x69, 0x82, 0x90, 0x7f, 0xc0, 0xc9, + 0x42, 0x61, 0x34, 0x2f, 0x8f, 0xd2, 0x49, 0x62, 0x7d, 0xdf, 0x55, 0x2a, 0xd8, 0x6c, 0x3e, 0xa1, 0xbe, 0xec, 0x28, + 0xf9, 0x1a, 0x9c, 0x74, 0x9c, 0x64, 0x91, 0x83, 0xa8, 0x45, 0xe5, 0x7c, 0x4a, 0xc2, 0xd2, 0xae, 0x4e, 0xb5, 0x59, + 0xaf, 0x8b, 0xb2, 0xae, 0x5e, 0x8a, 0x48, 0xd1, 0xfb, 0xa8, 0x47, 0x8f, 0x24, 0xa4, 0x42, 0xab, 0x52, 0xbb, 0x3c, + 0x02, 0xb7, 0x4d, 0xad, 0xd8, 0x96, 0x4b, 0x58, 0xa2, 0xc6, 0x7f, 0x86, 0x3e, 0xca, 0xc5, 0xbd, 0x0c, 0xd0, 0xe8, + 0x78, 0x6a, 0xde, 0x7a, 0xe0, 0x95, 0xa3, 0xfc, 0xd2, 0x6a, 0x93, 0x7e, 0x05, 0x64, 0x46, 0xfb, 0x47, 0x4b, 0x09, + 0x64, 0x06, 0x66, 0xd2, 0xd2, 0x90, 0xc8, 0x51, 0xcc, 0xd2, 0xfc, 0x4f, 0x5c, 0xb1, 0x15, 0x22, 0x0d, 0xab, 0xb9, + 0xc7, 0x7f, 0xac, 0xbc, 0x5a, 0xae, 0x65, 0xa6, 0xb9, 0x59, 0xe2, 0x58, 0xb1, 0xb8, 0xa8, 0xd7, 0x95, 0xc8, 0x02, + 0x21, 0x8e, 0x30, 0x8d, 0xf5, 0xd4, 0x1b, 0xa5, 0xd5, 0x07, 0x24, 0x94, 0xf9, 0x11, 0x7b, 0x3b, 0xf6, 0x7a, 0x90, + 0x85, 0x38, 0xb6, 0x1c, 0x6c, 0xb6, 0xde, 0xe7, 0x32, 0x15, 0xf1, 0x59, 0x5d, 0x9c, 0x6d, 0x2a, 0x71, 0x56, 0x27, + 0xe2, 0xec, 0x07, 0xc8, 0xf9, 0xc3, 0x19, 0x15, 0x7d, 0x76, 0x9f, 0xd6, 0x49, 0xb1, 0xa9, 0xe9, 0xc9, 0x2b, 0x2c, + 0xe3, 0x87, 0x33, 0xe2, 0xaa, 0x39, 0xa3, 0x91, 0x8c, 0x47, 0x67, 0x1f, 0x32, 0x20, 0x79, 0x3d, 0x4b, 0x57, 0x30, + 0x78, 0x67, 0x61, 0x1e, 0x9f, 0x95, 0x62, 0x09, 0x16, 0xa7, 0xb2, 0xf3, 0x3d, 0xc8, 0xb0, 0x0a, 0xff, 0x14, 0x67, + 0x00, 0xed, 0x7a, 0x96, 0xd6, 0x67, 0x69, 0x75, 0x96, 0x17, 0xf5, 0x99, 0x92, 0xc2, 0x21, 0x8c, 0x1f, 0xde, 0xd3, + 0x57, 0x76, 0x79, 0x9b, 0xc5, 0x5d, 0x16, 0xf9, 0x53, 0xf4, 0x2a, 0x22, 0x26, 0x8d, 0x4a, 0x78, 0xed, 0xfe, 0xb6, + 0xb9, 0x7f, 0x78, 0xdd, 0xd8, 0xfd, 0xec, 0x8e, 0x11, 0x5d, 0x50, 0x8f, 0x57, 0x92, 0x52, 0x41, 0x01, 0x81, 0x13, + 0xcd, 0x1a, 0x0f, 0xee, 0x38, 0xe0, 0xd5, 0xc0, 0x16, 0x6c, 0xed, 0xf3, 0x67, 0xb1, 0x0c, 0xd3, 0xde, 0x04, 0xf8, + 0x57, 0xd9, 0x9b, 0xae, 0x83, 0x05, 0xde, 0xb7, 0x90, 0x6d, 0xe8, 0xf5, 0x4b, 0xfe, 0xdc, 0xcb, 0xd5, 0xdf, 0xec, + 0x9f, 0x00, 0x84, 0x01, 0x31, 0xab, 0x3e, 0x9a, 0xb8, 0x77, 0x56, 0x96, 0x9d, 0x93, 0x65, 0xd7, 0x43, 0xbf, 0x26, + 0x31, 0x2a, 0xad, 0x2c, 0xa5, 0x93, 0xa5, 0x84, 0x2c, 0xe0, 0x13, 0xa3, 0xa9, 0x8d, 0x00, 0xc2, 0x76, 0x94, 0xca, + 0x17, 0x2a, 0x2f, 0xa2, 0x70, 0x4e, 0xf0, 0x3c, 0x11, 0xa3, 0x3b, 0x2b, 0x19, 0x30, 0x1c, 0x42, 0x30, 0x07, 0x6d, + 0xb1, 0x37, 0x74, 0x13, 0xf1, 0xd7, 0xab, 0xa2, 0x7c, 0x1d, 0x93, 0x4f, 0xc1, 0xee, 0xe4, 0xe3, 0x12, 0x1e, 0x97, + 0x27, 0x1f, 0x87, 0xe8, 0x91, 0x70, 0xf2, 0x31, 0xf8, 0x1e, 0xc9, 0x79, 0xdd, 0xf5, 0x38, 0x41, 0x6e, 0x21, 0xdd, + 0xdf, 0x8e, 0x49, 0x80, 0xe6, 0x35, 0x2c, 0x47, 0x4d, 0xc5, 0x35, 0x33, 0x63, 0x3c, 0x6f, 0xf4, 0xfe, 0xd8, 0xf1, + 0x96, 0x29, 0x14, 0xb3, 0x98, 0xd7, 0xf0, 0x7b, 0x56, 0x05, 0xea, 0xae, 0xb7, 0x49, 0x6e, 0x99, 0xd5, 0x73, 0xb4, + 0xfb, 0xbe, 0xaf, 0x13, 0x41, 0xed, 0xef, 0xb0, 0xe7, 0x99, 0xf5, 0xae, 0x8a, 0x81, 0x4b, 0x95, 0xec, 0x90, 0xa9, + 0x6a, 0x7a, 0xa0, 0x52, 0x1a, 0x3c, 0xbd, 0xb4, 0x2e, 0x5f, 0x2a, 0x6d, 0xe4, 0x99, 0xe6, 0x37, 0x80, 0x17, 0x53, + 0x97, 0xc5, 0xee, 0x9b, 0xfb, 0x0a, 0x6e, 0xe3, 0xfd, 0xfe, 0xba, 0xf2, 0xcc, 0x4f, 0x5c, 0x00, 0xf6, 0xa6, 0x42, + 0xeb, 0x04, 0x4a, 0x0d, 0xeb, 0xf0, 0x3a, 0x11, 0xd1, 0x9f, 0xed, 0x72, 0x9d, 0xb9, 0x0e, 0x18, 0x51, 0xc4, 0x6f, + 0xe3, 0xd1, 0x1f, 0xa0, 0xb8, 0x36, 0xf6, 0x80, 0xb0, 0x0e, 0x09, 0x7d, 0x46, 0x00, 0x52, 0x8f, 0x3e, 0x4a, 0xee, + 0x41, 0xb3, 0xa2, 0xb9, 0x63, 0xf2, 0x73, 0x7d, 0xa5, 0xf4, 0xf7, 0xeb, 0xca, 0x23, 0x73, 0x4a, 0xdb, 0x4c, 0x63, + 0xb5, 0xa6, 0x12, 0x08, 0xaf, 0xa8, 0x64, 0x15, 0x3e, 0x9b, 0x37, 0xa2, 0xdf, 0x97, 0x47, 0x78, 0x5a, 0xfd, 0xb8, + 0xc5, 0xf8, 0x56, 0x40, 0x34, 0x12, 0xa0, 0x60, 0x05, 0x98, 0x17, 0xd9, 0xcc, 0xee, 0xe3, 0x80, 0x2a, 0x25, 0x9a, + 0xc6, 0xd9, 0x3c, 0xbf, 0xa7, 0x37, 0x65, 0x07, 0x9d, 0x3a, 0x55, 0xe0, 0x82, 0xab, 0x92, 0xf1, 0xca, 0x7a, 0x22, + 0x9f, 0xdf, 0xdc, 0x6e, 0xd2, 0x2c, 0x7e, 0x5f, 0xfe, 0x82, 0x63, 0xab, 0xeb, 0xf0, 0xc0, 0xd4, 0xe9, 0xda, 0x79, + 0xa4, 0xb5, 0x17, 0x02, 0x22, 0xda, 0x35, 0xd4, 0x7a, 0x61, 0xa1, 0x47, 0x7a, 0x22, 0x9c, 0x93, 0x44, 0x4d, 0x3b, + 0xd0, 0xd2, 0x08, 0x7d, 0x7d, 0xcd, 0xe9, 0x2f, 0x0c, 0xd6, 0x3e, 0x1f, 0x33, 0x20, 0x2b, 0xd1, 0x8f, 0xd5, 0x43, + 0x63, 0x33, 0x87, 0x9e, 0xb5, 0x2a, 0xcf, 0xbc, 0xea, 0x70, 0x40, 0x7c, 0x18, 0xfd, 0x25, 0xbf, 0xdf, 0x7f, 0x49, + 0xf3, 0x8f, 0x09, 0x35, 0x7e, 0xb6, 0x19, 0xa0, 0x6b, 0xdf, 0x95, 0x07, 0xa2, 0x9e, 0x6b, 0x95, 0x20, 0xc4, 0x1b, + 0xc4, 0x44, 0x33, 0x62, 0x0e, 0x4e, 0x3b, 0xd4, 0xfc, 0x93, 0xd4, 0x80, 0x10, 0x25, 0x5e, 0xc7, 0x94, 0x05, 0x39, + 0x6d, 0xe2, 0x48, 0x3f, 0x0a, 0x27, 0xf2, 0xa3, 0xa8, 0x8a, 0xec, 0x0e, 0x2e, 0x18, 0x4c, 0xbd, 0xa7, 0xfd, 0x12, + 0xfd, 0x96, 0x70, 0xe4, 0x1c, 0xad, 0x0a, 0x41, 0xe4, 0x84, 0xb0, 0xd6, 0x10, 0x26, 0x88, 0x0d, 0xe2, 0x65, 0xdf, + 0x25, 0x19, 0x8e, 0x14, 0x5c, 0xd6, 0xb1, 0x63, 0xcc, 0xd5, 0x51, 0xf5, 0x1a, 0xc0, 0x78, 0xe5, 0x08, 0x9a, 0x8d, + 0x22, 0xbb, 0x84, 0xa8, 0x22, 0xc7, 0x13, 0x50, 0x3b, 0x28, 0x8d, 0xcd, 0xf4, 0x7c, 0x1c, 0xe4, 0xa3, 0x9b, 0x0a, + 0x75, 0x4e, 0x2c, 0xe3, 0x35, 0x00, 0x6b, 0xe7, 0xaa, 0x9f, 0x67, 0x35, 0x78, 0xd2, 0x10, 0x9f, 0x8f, 0xd1, 0xf6, + 0xca, 0xe6, 0xa0, 0xda, 0x4e, 0x67, 0xe5, 0x15, 0xd3, 0xe5, 0xc0, 0xb8, 0x6f, 0x78, 0x45, 0x71, 0x86, 0x1f, 0x3d, + 0xd8, 0xe2, 0xfc, 0xe9, 0x86, 0xda, 0x8f, 0xb9, 0x51, 0x0f, 0x03, 0xad, 0x05, 0x6f, 0x0a, 0x62, 0xfd, 0x7d, 0xd4, + 0x91, 0xed, 0xbd, 0x16, 0x19, 0x4d, 0x3e, 0xfb, 0xf9, 0x87, 0x32, 0x5d, 0xa5, 0x70, 0x5f, 0x72, 0xb2, 0x68, 0xe6, + 0x21, 0xb0, 0x37, 0xc4, 0x70, 0x7d, 0x54, 0x78, 0x44, 0x59, 0xbf, 0x0f, 0xbf, 0xaf, 0x32, 0x30, 0xc5, 0xc0, 0x75, + 0x85, 0x60, 0x3c, 0x04, 0x82, 0x78, 0x98, 0x46, 0x27, 0x83, 0x1a, 0xb4, 0xe1, 0x1b, 0x80, 0xcc, 0x00, 0x8f, 0xcc, + 0x85, 0x47, 0xc0, 0x5d, 0xe0, 0xda, 0x93, 0xf1, 0xd8, 0x9f, 0x98, 0x86, 0x46, 0x4d, 0x69, 0xa6, 0xe7, 0xc6, 0x6f, + 0x3a, 0xaa, 0xe5, 0xda, 0xf9, 0x8f, 0x2f, 0xf9, 0x0d, 0x7a, 0x41, 0xcb, 0xcb, 0x7d, 0xa4, 0x2e, 0xf7, 0x19, 0xc5, + 0x65, 0x22, 0x39, 0x2c, 0x88, 0x65, 0x09, 0x07, 0x1e, 0xa3, 0x92, 0xc5, 0x96, 0x1e, 0xab, 0xa2, 0xe5, 0x8b, 0x72, + 0x83, 0x74, 0xe8, 0x84, 0x60, 0x89, 0x0a, 0x82, 0x25, 0x30, 0x2e, 0x62, 0xcd, 0x37, 0x83, 0x9c, 0xc5, 0xb3, 0xcd, + 0x9c, 0x23, 0x61, 0x5d, 0x72, 0x38, 0x14, 0x12, 0x6c, 0x26, 0x9b, 0xad, 0xe7, 0x6c, 0xed, 0x33, 0x50, 0x02, 0x94, + 0x32, 0x4d, 0x50, 0x9a, 0x56, 0x6c, 0xc5, 0x4d, 0x6b, 0xb0, 0x5a, 0x4d, 0xd9, 0xaa, 0xa6, 0xec, 0x9c, 0xa6, 0x1c, + 0x55, 0x50, 0x72, 0x42, 0x29, 0xca, 0x30, 0x80, 0x11, 0x9b, 0x44, 0x57, 0x19, 0xfa, 0x78, 0x27, 0x3c, 0x82, 0x2a, + 0x22, 0xf2, 0x09, 0x43, 0x08, 0x4c, 0x44, 0x71, 0xa1, 0x0a, 0xc5, 0x00, 0x19, 0x91, 0x40, 0x30, 0x51, 0xa9, 0x53, + 0x60, 0x3e, 0x9a, 0x2a, 0x86, 0x4d, 0x7b, 0xa2, 0x7c, 0x4f, 0x1d, 0xf7, 0x28, 0xdb, 0xfc, 0x2c, 0x76, 0x41, 0x88, + 0xdc, 0x8d, 0x3b, 0xf5, 0x33, 0xe2, 0xbd, 0xdd, 0x11, 0xc6, 0x4f, 0x76, 0xdc, 0x22, 0x5c, 0x11, 0x6c, 0xa1, 0xe6, + 0x10, 0x8b, 0x79, 0x35, 0x49, 0x50, 0xcb, 0x92, 0xf8, 0x1b, 0x9e, 0x0c, 0x72, 0xb6, 0x00, 0x0f, 0xda, 0x39, 0xcb, + 0x00, 0x7f, 0xc5, 0x6a, 0xd1, 0xef, 0xb5, 0xb7, 0x00, 0xf9, 0x69, 0x63, 0x37, 0x0a, 0x13, 0x23, 0x48, 0xd4, 0xed, + 0xca, 0x40, 0x7e, 0xf8, 0x80, 0xd3, 0xf1, 0xd8, 0x53, 0xc6, 0xdc, 0xca, 0xf4, 0x32, 0x9d, 0x2b, 0xf9, 0x46, 0xee, + 0xa5, 0x0f, 0xbd, 0x04, 0x3b, 0x07, 0xbc, 0x81, 0xb4, 0x81, 0x9f, 0x60, 0xbb, 0xf0, 0xda, 0x20, 0x61, 0x46, 0x80, + 0x2d, 0x8e, 0x8f, 0x91, 0x12, 0x18, 0xc2, 0x71, 0x96, 0x02, 0x30, 0x8d, 0xbe, 0xcc, 0x56, 0xf6, 0x65, 0x56, 0x6b, + 0xb6, 0x54, 0x4e, 0xf7, 0xce, 0xad, 0xdb, 0xf9, 0x5c, 0x02, 0x80, 0x49, 0x9d, 0x03, 0x71, 0x66, 0x82, 0x5d, 0x9a, + 0x44, 0x96, 0x8f, 0x61, 0xbe, 0x14, 0xaf, 0xca, 0x62, 0xa5, 0xba, 0xa2, 0xed, 0x33, 0x93, 0xcf, 0x48, 0x27, 0xa1, + 0x02, 0x0a, 0x0a, 0xb9, 0xd6, 0xa7, 0xef, 0xc2, 0x77, 0x41, 0xa1, 0x81, 0xd9, 0x2a, 0xdc, 0xd3, 0x64, 0x8d, 0xd4, + 0x1b, 0x55, 0xbf, 0x4f, 0xae, 0x81, 0x54, 0x67, 0x0e, 0x2d, 0x7b, 0x5e, 0x61, 0x80, 0xd8, 0x51, 0x9f, 0x91, 0x50, + 0x07, 0x52, 0x0f, 0x18, 0x42, 0xb4, 0x4d, 0x1f, 0x7f, 0x32, 0x24, 0xba, 0x00, 0x5b, 0x88, 0x36, 0xf0, 0xe3, 0x4f, + 0xb0, 0xcf, 0x82, 0xf0, 0x98, 0xe6, 0x6f, 0x21, 0xe9, 0xd8, 0xc0, 0x69, 0xf5, 0x29, 0xf8, 0x20, 0xc9, 0xc1, 0x44, + 0x1d, 0xbc, 0xdc, 0x5f, 0xfa, 0x7d, 0xd8, 0xb2, 0x73, 0x29, 0xd5, 0xb1, 0x52, 0x6f, 0xdb, 0xda, 0x0f, 0xa2, 0x2d, + 0x38, 0x42, 0xb0, 0x76, 0x86, 0x88, 0x60, 0x66, 0x10, 0x61, 0xd7, 0x42, 0xdd, 0xed, 0x29, 0xb5, 0x2c, 0xea, 0x6d, + 0x4f, 0x29, 0x75, 0x1b, 0x86, 0xef, 0x26, 0x98, 0x29, 0x6e, 0xf8, 0xa7, 0xcc, 0x0b, 0xf5, 0xc6, 0x63, 0xf1, 0xb4, + 0x7b, 0xfe, 0x7e, 0xc1, 0xab, 0xd9, 0x46, 0x99, 0x30, 0x97, 0x7c, 0x31, 0x0b, 0x65, 0x57, 0x4b, 0xe3, 0xce, 0x17, + 0x6f, 0xa1, 0xe6, 0x83, 0x7f, 0x38, 0x24, 0x10, 0x6f, 0x14, 0x5f, 0x2d, 0x1b, 0xb9, 0x75, 0x4d, 0x36, 0x57, 0x25, + 0xa0, 0x7e, 0x9f, 0xaf, 0x71, 0xbf, 0xc5, 0xfa, 0x77, 0x4f, 0x83, 0x8c, 0xd5, 0x0c, 0x57, 0x4c, 0xe1, 0x53, 0x00, + 0x18, 0x1c, 0x4e, 0x05, 0x69, 0x81, 0x37, 0xbc, 0x1c, 0x5e, 0x4e, 0x36, 0x64, 0xd2, 0xdd, 0xf8, 0xc8, 0x9d, 0x05, + 0xaa, 0xde, 0xef, 0x28, 0x4e, 0x1a, 0x24, 0x1a, 0x7b, 0x0d, 0x3e, 0xcf, 0x32, 0xca, 0x45, 0x13, 0xf7, 0x21, 0xf9, + 0x4a, 0x0f, 0x60, 0xae, 0x42, 0x09, 0x10, 0xfd, 0xc6, 0xb2, 0xd8, 0x88, 0xb6, 0xc5, 0x06, 0x96, 0x52, 0x35, 0xd7, + 0xab, 0xe9, 0x8b, 0x57, 0xa2, 0x79, 0x1f, 0xcd, 0x38, 0xa5, 0xd1, 0x80, 0xe3, 0x34, 0x0a, 0xb7, 0xef, 0xef, 0x44, + 0xb9, 0xc8, 0xc0, 0x92, 0xad, 0xc2, 0x29, 0x2e, 0x1b, 0x75, 0x46, 0x3c, 0xcf, 0x63, 0x05, 0xd0, 0xf1, 0x90, 0x00, + 0xa8, 0x2e, 0x08, 0xa8, 0x88, 0x96, 0xd2, 0x5b, 0xa1, 0xc5, 0x42, 0xbd, 0xe1, 0x28, 0x85, 0x3f, 0xd2, 0x9f, 0x07, + 0xf9, 0x14, 0x80, 0xd8, 0xf5, 0x71, 0xf4, 0xaa, 0x28, 0xe9, 0x53, 0xc5, 0x2c, 0x97, 0x83, 0x09, 0xec, 0xea, 0x44, + 0x86, 0x5a, 0x41, 0xde, 0xaa, 0x2b, 0x6f, 0x65, 0xf2, 0x36, 0xc6, 0x29, 0xf9, 0x81, 0x9b, 0x8e, 0x35, 0x62, 0xe0, + 0x95, 0xa7, 0x75, 0x9a, 0x20, 0x4d, 0xde, 0x00, 0xc3, 0x10, 0xbf, 0xcb, 0xbc, 0xe7, 0x9e, 0x23, 0x55, 0x41, 0x32, + 0xdb, 0x66, 0x9e, 0xba, 0x88, 0xea, 0x2b, 0xa7, 0x96, 0xce, 0x9c, 0x7e, 0x04, 0xf0, 0x1e, 0x53, 0x93, 0x86, 0x7c, + 0x84, 0xdb, 0x52, 0x7c, 0xbd, 0x55, 0xd7, 0x78, 0x69, 0x74, 0xee, 0x5e, 0xbe, 0x74, 0xa7, 0x41, 0x3f, 0x05, 0x41, + 0x39, 0x9f, 0x97, 0x02, 0xf6, 0x94, 0xd9, 0x5c, 0xaf, 0x56, 0xad, 0xd0, 0x3a, 0x1c, 0xc6, 0xda, 0x51, 0x48, 0xab, + 0xb3, 0x80, 0xad, 0x46, 0x3a, 0x25, 0x40, 0x08, 0x8e, 0xd3, 0xb0, 0x13, 0x8c, 0xbb, 0x74, 0x1a, 0x91, 0xf5, 0x4a, + 0x49, 0xba, 0x30, 0x83, 0xe4, 0x9f, 0xe4, 0xf5, 0x0c, 0x68, 0x09, 0xe0, 0x50, 0xc4, 0x12, 0x1e, 0x4e, 0x92, 0x2b, + 0x80, 0x4e, 0x87, 0x83, 0x4a, 0x43, 0x73, 0x56, 0xb3, 0x64, 0x3e, 0x89, 0xa5, 0xaa, 0xf2, 0x70, 0xf0, 0x94, 0x9b, + 0x41, 0xbf, 0x9f, 0x4d, 0x4b, 0xe5, 0x02, 0x10, 0xc4, 0xba, 0x30, 0x40, 0x3c, 0xd2, 0xc2, 0x93, 0x45, 0x9f, 0x92, + 0xf8, 0xe5, 0x2c, 0x99, 0x9b, 0x6c, 0x78, 0x07, 0x46, 0xb0, 0x19, 0xd7, 0x25, 0x65, 0xda, 0xa3, 0xf2, 0x7b, 0x46, + 0x4f, 0x6d, 0x5f, 0x6b, 0xb5, 0x45, 0xac, 0xeb, 0xe0, 0xaa, 0x44, 0x3d, 0xc5, 0x07, 0x25, 0x09, 0xde, 0x2f, 0x9d, + 0x9b, 0x91, 0xf2, 0xb5, 0xc8, 0xfd, 0xa0, 0x9d, 0xa9, 0x95, 0x03, 0x47, 0x20, 0xc7, 0x2a, 0x2a, 0x79, 0xbd, 0xeb, + 0x10, 0x3c, 0xba, 0x2b, 0x15, 0x28, 0x07, 0x3f, 0x03, 0x31, 0xba, 0xbe, 0xea, 0xac, 0xa1, 0x66, 0x1a, 0x55, 0x1e, + 0x41, 0xa7, 0x0e, 0xe0, 0x49, 0xc1, 0x4b, 0xad, 0x7e, 0x3c, 0x1c, 0x3c, 0xf3, 0x83, 0xbf, 0xcf, 0xf4, 0x2d, 0xc4, + 0x44, 0x39, 0xd5, 0x08, 0x89, 0x2b, 0x25, 0x89, 0xf8, 0x78, 0xd1, 0xb2, 0x62, 0x54, 0x86, 0xf7, 0xbc, 0x52, 0xe5, + 0xab, 0x53, 0x95, 0x17, 0x23, 0x6d, 0x4b, 0xe0, 0x35, 0xf9, 0x87, 0xc8, 0x35, 0x6f, 0x7d, 0xdd, 0x55, 0x86, 0x5e, + 0xcb, 0x0a, 0x74, 0x04, 0x5b, 0x59, 0x4a, 0x0e, 0xf8, 0xa4, 0xba, 0xab, 0x56, 0xad, 0xcf, 0x29, 0xdb, 0x08, 0x37, + 0xf9, 0x75, 0xec, 0xe0, 0x48, 0xf9, 0x0d, 0x9e, 0x0b, 0x60, 0xaf, 0x01, 0x7b, 0x73, 0xce, 0x8a, 0xe6, 0xc1, 0x21, + 0x6d, 0x0b, 0x34, 0x32, 0x73, 0x3b, 0x57, 0xf7, 0x6d, 0x79, 0x94, 0xc6, 0x10, 0x99, 0xf6, 0xc0, 0x74, 0xb0, 0x19, + 0xe5, 0xbf, 0xa7, 0xfc, 0x56, 0xe1, 0x18, 0xf8, 0x76, 0xea, 0x1d, 0x40, 0xd5, 0xd3, 0x06, 0x19, 0x6b, 0x86, 0xa1, + 0x95, 0x5d, 0x2e, 0x85, 0x96, 0xa0, 0xa5, 0x6e, 0x82, 0xe0, 0xfc, 0x88, 0x28, 0x47, 0x00, 0xba, 0x48, 0x01, 0x13, + 0xfc, 0x94, 0xb6, 0xbb, 0xdf, 0x5f, 0xa7, 0x1e, 0xb9, 0x77, 0x85, 0xca, 0x66, 0xf9, 0x99, 0x60, 0xec, 0x27, 0x1a, + 0x33, 0xe8, 0xe8, 0x8a, 0x9c, 0xf0, 0xac, 0xd5, 0x61, 0x5d, 0x37, 0x65, 0x50, 0x16, 0xc7, 0xbc, 0x9a, 0xce, 0xfe, + 0x78, 0xb4, 0xaf, 0x1b, 0x64, 0x21, 0xff, 0x83, 0xf5, 0x90, 0x0c, 0xba, 0x07, 0xa1, 0x10, 0xbd, 0x79, 0x30, 0xc3, + 0xff, 0xd8, 0x86, 0x67, 0xdf, 0x71, 0xa3, 0x4e, 0x00, 0x73, 0xc4, 0xf5, 0xd2, 0x53, 0xb4, 0xf5, 0x70, 0x0b, 0x64, + 0x6b, 0xbc, 0xbc, 0xb5, 0xd7, 0x40, 0x4e, 0x71, 0xfc, 0x4b, 0x9e, 0xa9, 0x95, 0x0d, 0x7e, 0x7a, 0xca, 0x76, 0xe0, + 0xe1, 0x45, 0x08, 0x28, 0x86, 0x65, 0xe3, 0x97, 0x96, 0xe3, 0x8c, 0xfe, 0x9b, 0x47, 0x0c, 0x83, 0x45, 0xe4, 0xc7, + 0x17, 0xa5, 0x10, 0x5f, 0x85, 0xf7, 0xb9, 0xf2, 0x96, 0xe4, 0x94, 0xb9, 0xd4, 0xc3, 0xe8, 0xba, 0x24, 0x7d, 0x97, + 0x7c, 0x6c, 0x0d, 0xdb, 0x1f, 0xda, 0xfd, 0x66, 0x88, 0x20, 0x84, 0x72, 0xfc, 0x9c, 0xd1, 0x09, 0x8d, 0x0f, 0xab, + 0xd9, 0xe9, 0xf5, 0x7b, 0xe7, 0x78, 0xc1, 0xd6, 0x68, 0x80, 0xc7, 0x43, 0x17, 0xf3, 0x44, 0x0d, 0x9d, 0xae, 0x6b, + 0xe7, 0xe0, 0x81, 0x41, 0x96, 0x27, 0xdf, 0x31, 0x2c, 0xb1, 0x3f, 0x89, 0x78, 0xd2, 0x56, 0x6d, 0x6c, 0x8e, 0x54, + 0x1b, 0x35, 0x03, 0x3f, 0x78, 0x05, 0x05, 0x46, 0x17, 0xa4, 0x5b, 0x30, 0x0e, 0x47, 0x00, 0xb2, 0x62, 0x1c, 0x8f, + 0x0c, 0x26, 0x30, 0xa4, 0x1b, 0x8a, 0x02, 0xf0, 0xf0, 0x38, 0x1e, 0x84, 0x0c, 0x20, 0x5d, 0xf0, 0xd0, 0xb0, 0x4d, + 0x42, 0xca, 0xcf, 0xf3, 0xbc, 0x56, 0x43, 0xe8, 0x3b, 0x0b, 0xd5, 0xb1, 0x1f, 0x69, 0xaf, 0x58, 0xd7, 0xaa, 0x74, + 0x64, 0xab, 0x03, 0xf4, 0x0d, 0x19, 0xf8, 0xd6, 0xb1, 0x05, 0x40, 0xb4, 0xc4, 0xef, 0xa9, 0x57, 0xfb, 0x32, 0x66, + 0x85, 0x7a, 0xfd, 0xc6, 0xb4, 0xeb, 0xa5, 0xb4, 0x28, 0xa0, 0xe2, 0xb6, 0x55, 0xdb, 0x23, 0x39, 0xff, 0xe1, 0x5d, + 0x47, 0x3b, 0x3e, 0x3b, 0x35, 0xb6, 0x84, 0x32, 0xb7, 0x78, 0x22, 0xab, 0xa3, 0x2d, 0xd5, 0xa9, 0x3e, 0xe0, 0x52, + 0x93, 0xea, 0xcc, 0xc0, 0xf0, 0x1a, 0x01, 0xca, 0x2d, 0x44, 0xd2, 0x38, 0xec, 0x9d, 0x4f, 0x06, 0x05, 0x73, 0x8b, + 0x04, 0x24, 0xb0, 0x8d, 0xad, 0x5d, 0x34, 0xd7, 0xaf, 0xdf, 0x53, 0xaf, 0x6a, 0x53, 0xd5, 0x83, 0x37, 0x5e, 0xe0, + 0xec, 0x9d, 0xd6, 0x02, 0x02, 0x28, 0x6c, 0x2d, 0xcb, 0xc1, 0xb9, 0xdb, 0x55, 0x2d, 0x15, 0x65, 0xd4, 0xef, 0x9f, + 0xff, 0x9e, 0xa2, 0x22, 0xf6, 0x54, 0x71, 0xca, 0xfa, 0xed, 0x96, 0x79, 0x53, 0x59, 0xf2, 0x06, 0x55, 0xb4, 0x56, + 0x47, 0x4d, 0xe5, 0xba, 0xb9, 0x6a, 0xc9, 0x04, 0x31, 0xba, 0x4f, 0xd7, 0x3a, 0x77, 0xea, 0xbd, 0x57, 0x71, 0xc4, + 0x40, 0x70, 0xd3, 0x3d, 0x3e, 0x38, 0x08, 0x8d, 0x8a, 0x72, 0xc1, 0x8d, 0xd2, 0xaa, 0x92, 0x52, 0xc8, 0x5b, 0x15, + 0xcd, 0x99, 0x3e, 0x02, 0x20, 0x02, 0xac, 0x12, 0xf5, 0xbf, 0xf9, 0xd2, 0x18, 0x0f, 0x1e, 0xf8, 0x9a, 0x5c, 0xc7, + 0xd6, 0xfb, 0xa7, 0x35, 0xd2, 0x6a, 0xe3, 0x98, 0xd4, 0xaa, 0x97, 0xad, 0xe2, 0x65, 0xf7, 0x3a, 0x15, 0x83, 0xe7, + 0xff, 0x73, 0x1f, 0xa0, 0x46, 0xb4, 0x94, 0xc1, 0xad, 0xab, 0x01, 0x1a, 0x1f, 0x8e, 0x85, 0x6f, 0xfc, 0x90, 0x71, + 0x3e, 0x98, 0xa1, 0xa3, 0xda, 0x1c, 0x1c, 0x10, 0x1c, 0xd5, 0x3d, 0x1a, 0x13, 0x66, 0xe1, 0xdc, 0x83, 0x40, 0xf5, + 0x89, 0xfb, 0x8c, 0x6b, 0x2f, 0x68, 0x13, 0xf8, 0x64, 0x5d, 0xd7, 0x14, 0x01, 0x2e, 0x62, 0x63, 0x22, 0x86, 0xb8, + 0x6c, 0x12, 0xa9, 0x6f, 0xc6, 0xa0, 0x00, 0x28, 0x9e, 0x55, 0x24, 0x97, 0xde, 0xa4, 0x79, 0x25, 0xca, 0x5a, 0x37, + 0xa3, 0x62, 0xc5, 0x10, 0x00, 0x1e, 0x82, 0xe2, 0xaa, 0x32, 0x13, 0x1a, 0xb1, 0x81, 0x54, 0x96, 0x82, 0x55, 0xc3, + 0xc2, 0x6f, 0xda, 0x6f, 0x92, 0x93, 0xde, 0xf9, 0xb8, 0x75, 0xee, 0xd8, 0xf7, 0x8e, 0x42, 0x4a, 0x7b, 0x28, 0x26, + 0x08, 0x82, 0x9f, 0xd6, 0xe1, 0xfc, 0x19, 0x7f, 0x46, 0x60, 0x2a, 0xb2, 0x19, 0x03, 0x0e, 0x42, 0x44, 0x66, 0xfc, + 0x9e, 0xc3, 0x67, 0xbc, 0x9c, 0x84, 0xc3, 0xa1, 0x0f, 0xfa, 0x50, 0x9e, 0xcd, 0xc2, 0xa1, 0x98, 0x4b, 0xef, 0x75, + 0xb0, 0xd6, 0x85, 0xbc, 0x9e, 0x84, 0x88, 0x16, 0x1a, 0xfa, 0xe0, 0xbc, 0xee, 0x9a, 0x23, 0x2c, 0x01, 0x68, 0xe2, + 0xe8, 0xcb, 0xfa, 0xfd, 0xc8, 0xd3, 0x86, 0x16, 0x29, 0x2e, 0x1a, 0x65, 0x36, 0xcb, 0x65, 0x27, 0x6c, 0x5c, 0xbb, + 0x05, 0x42, 0xf1, 0x30, 0x6d, 0xa1, 0x6a, 0x3d, 0xd5, 0xeb, 0xb9, 0x69, 0xf7, 0xdd, 0x83, 0x6a, 0x95, 0x23, 0x9d, + 0xb5, 0xe9, 0x4a, 0xad, 0x6e, 0x19, 0x55, 0xeb, 0x2c, 0x8d, 0xa8, 0x72, 0x93, 0xdc, 0x35, 0x6a, 0xc1, 0x27, 0x1b, + 0xba, 0x4c, 0xd9, 0xd9, 0x1a, 0x9c, 0x38, 0xf2, 0x5c, 0x72, 0xcb, 0x77, 0xe7, 0x15, 0xdd, 0x9d, 0x6a, 0xdf, 0x02, + 0xdc, 0x9b, 0x61, 0x43, 0xe6, 0xbc, 0xc6, 0x4e, 0x83, 0x30, 0x09, 0xfc, 0x88, 0x7d, 0xcc, 0x90, 0x0d, 0x06, 0x74, + 0x14, 0xd2, 0xff, 0xda, 0x32, 0x47, 0x02, 0x26, 0x7f, 0x3d, 0xf7, 0x9b, 0x9b, 0x22, 0x87, 0xc5, 0xf8, 0x61, 0x83, + 0x91, 0xc6, 0x6a, 0x0d, 0x86, 0xe5, 0x12, 0x91, 0x3f, 0xb5, 0x3b, 0xa6, 0xa9, 0x8e, 0x37, 0xeb, 0xb5, 0xe6, 0x57, + 0x4f, 0x9f, 0xea, 0xfa, 0xfc, 0xb7, 0xef, 0x2f, 0xc3, 0x9a, 0xd9, 0x1f, 0x82, 0x50, 0xda, 0xbd, 0x5b, 0x9c, 0x3b, + 0x12, 0xbd, 0x63, 0xa5, 0x99, 0x5d, 0xda, 0x25, 0xbb, 0x34, 0xa5, 0x7d, 0x22, 0xd7, 0xab, 0x6f, 0x94, 0x37, 0x76, + 0x5e, 0x31, 0xdd, 0xbf, 0x17, 0x7a, 0x47, 0x39, 0x55, 0x13, 0x88, 0x68, 0xd2, 0x8e, 0xc4, 0xed, 0x5e, 0x19, 0x3e, + 0x9d, 0xe4, 0xed, 0x12, 0x8e, 0xba, 0x86, 0xe5, 0xe6, 0xdb, 0xbf, 0xe4, 0x55, 0x67, 0x85, 0xdb, 0x2f, 0x8d, 0x59, + 0xfb, 0x53, 0x10, 0x57, 0xf5, 0xa7, 0xf7, 0xa1, 0x66, 0x4a, 0xfe, 0xaf, 0x7a, 0x0c, 0x5c, 0xfd, 0x64, 0xda, 0xd1, + 0x3d, 0x85, 0xb0, 0xc1, 0xec, 0xe7, 0xc7, 0x0f, 0x2d, 0xba, 0x46, 0x17, 0x28, 0x92, 0x03, 0xe8, 0xdc, 0x25, 0x23, + 0xbc, 0xdf, 0x31, 0xce, 0xfd, 0xab, 0x5f, 0xd5, 0xe4, 0x08, 0x11, 0xed, 0x22, 0x1c, 0x00, 0xc4, 0x9d, 0xa6, 0xb2, + 0x0e, 0x35, 0x40, 0x1f, 0x10, 0x58, 0x87, 0xbe, 0xcd, 0x00, 0x0e, 0xfa, 0x68, 0xf3, 0x2c, 0x02, 0x79, 0xdd, 0xbb, + 0x63, 0xd7, 0x6c, 0xe7, 0xf3, 0x67, 0xab, 0xd4, 0xbb, 0x43, 0x87, 0xe0, 0xf3, 0xb1, 0x3f, 0xbd, 0x0c, 0xb4, 0xda, + 0xf3, 0x9a, 0x5d, 0x3f, 0x11, 0x6c, 0xc7, 0x76, 0x4f, 0x10, 0xa9, 0xa8, 0x3b, 0xff, 0xf0, 0xd2, 0x44, 0xcf, 0x3b, + 0x2f, 0x2c, 0xf9, 0x02, 0xc0, 0x03, 0x59, 0x0c, 0x28, 0x3e, 0x0b, 0xef, 0x17, 0x96, 0x80, 0x9a, 0xfc, 0x96, 0xaf, + 0xbd, 0x77, 0x94, 0x7a, 0x03, 0x7f, 0x0e, 0x28, 0x7d, 0x92, 0x73, 0x6f, 0x39, 0xbc, 0xf5, 0x2f, 0x9e, 0x82, 0xf3, + 0xc4, 0x6a, 0x78, 0x03, 0x7f, 0x15, 0x7c, 0xe8, 0x2d, 0x07, 0x98, 0x58, 0xf2, 0xa1, 0xb7, 0x1a, 0x40, 0xaa, 0xc2, + 0x85, 0xc4, 0xd8, 0x87, 0xdf, 0x82, 0x9c, 0xe1, 0x1f, 0xbf, 0x6b, 0x0c, 0xd6, 0xdf, 0x82, 0x42, 0xa3, 0xb1, 0x96, + 0x2a, 0x64, 0x29, 0x16, 0x67, 0x02, 0x6c, 0xc2, 0x71, 0xb7, 0x2f, 0x56, 0xb5, 0x59, 0x0b, 0xfa, 0xf3, 0x01, 0xdf, + 0xa3, 0xb1, 0xba, 0x2a, 0xe7, 0xa2, 0xfc, 0x88, 0xf4, 0xa9, 0x8e, 0x8f, 0x51, 0xb1, 0xa9, 0xbb, 0xd3, 0xa9, 0x56, + 0x1d, 0x69, 0xbf, 0x2b, 0xd7, 0x60, 0xc7, 0xeb, 0xe4, 0xc8, 0x52, 0x78, 0xd6, 0x61, 0xe7, 0xa5, 0x53, 0xa2, 0xc3, + 0x30, 0xde, 0x6d, 0xd5, 0x33, 0x86, 0xf2, 0xdc, 0x60, 0x4c, 0x17, 0x3c, 0xe2, 0xcf, 0x06, 0xb9, 0x0c, 0x8d, 0x79, + 0x84, 0x6c, 0x18, 0xca, 0x87, 0x16, 0x19, 0x12, 0x22, 0xde, 0x43, 0x25, 0x60, 0xdb, 0x82, 0x32, 0x29, 0xe0, 0x2c, + 0x1a, 0xfc, 0x5e, 0x7b, 0x39, 0xf0, 0x1e, 0x44, 0x7e, 0x23, 0x5d, 0xca, 0x25, 0x36, 0x3a, 0x71, 0x2c, 0x0b, 0xed, + 0x3c, 0xae, 0xbf, 0x8e, 0x41, 0xfd, 0x5e, 0xe9, 0x37, 0x28, 0x67, 0x7f, 0x94, 0xac, 0xd3, 0xc6, 0x13, 0xe3, 0x5f, + 0xae, 0xf2, 0x4f, 0xd1, 0x52, 0x0f, 0xff, 0x9f, 0x31, 0x85, 0xd2, 0x5f, 0xa7, 0x65, 0xb4, 0x59, 0x2d, 0x44, 0x29, + 0xf2, 0x48, 0x9c, 0x7c, 0x2d, 0xb2, 0x73, 0xf9, 0xce, 0xa7, 0xd0, 0x2f, 0x00, 0x2d, 0xfb, 0x04, 0x19, 0xfd, 0x2b, + 0x13, 0x7c, 0xf8, 0xab, 0x76, 0xae, 0xcd, 0xf9, 0x78, 0x92, 0x5f, 0x59, 0x7b, 0xb7, 0xe3, 0x45, 0x62, 0x14, 0x63, + 0xb9, 0xaf, 0xba, 0x59, 0x39, 0x51, 0xc9, 0x81, 0x91, 0xae, 0xc9, 0x5e, 0xae, 0x64, 0xdd, 0x4e, 0xb7, 0x12, 0x88, + 0xa8, 0x02, 0xef, 0x31, 0xae, 0x62, 0x1f, 0xc1, 0x74, 0xdd, 0x71, 0x19, 0xed, 0x78, 0xcf, 0x78, 0x75, 0xa2, 0xac, + 0xe0, 0x76, 0x23, 0xda, 0x13, 0x3a, 0xfa, 0x69, 0x52, 0x5b, 0x16, 0x0e, 0x40, 0xee, 0x12, 0xc6, 0xb2, 0x21, 0x58, + 0x31, 0x28, 0x7d, 0xbd, 0xa6, 0x64, 0x59, 0x80, 0x45, 0x67, 0x97, 0x11, 0x88, 0x61, 0xdd, 0x34, 0x27, 0x74, 0xbc, + 0x74, 0x71, 0xde, 0x6b, 0x15, 0x29, 0x78, 0x46, 0x8b, 0x8e, 0xb9, 0xe9, 0x48, 0x37, 0x46, 0x7b, 0xfb, 0xc2, 0x20, + 0xa4, 0x78, 0xfe, 0xc0, 0x56, 0xeb, 0xe2, 0x22, 0xf1, 0x0a, 0x99, 0x68, 0x41, 0x2c, 0x45, 0x60, 0xc6, 0x0b, 0x4d, + 0x23, 0x4c, 0x50, 0xa6, 0x04, 0x8b, 0xd6, 0xe8, 0xd0, 0xfe, 0xb0, 0x84, 0xdd, 0x63, 0x8c, 0x00, 0x81, 0x2a, 0xd3, + 0x8b, 0xb0, 0x35, 0x61, 0x36, 0x75, 0xb1, 0x01, 0xda, 0x2a, 0x86, 0x06, 0x61, 0x6d, 0x88, 0xf9, 0x98, 0xe6, 0xcb, + 0x7f, 0x62, 0x31, 0xb6, 0x27, 0x10, 0xdb, 0xbb, 0x5d, 0x93, 0x30, 0xdd, 0x6b, 0x71, 0x63, 0xbd, 0xdc, 0x9e, 0x72, + 0x4c, 0xed, 0x58, 0x1b, 0xb5, 0x63, 0x2d, 0xf4, 0x8e, 0xb5, 0xd6, 0x3b, 0xd6, 0xb2, 0xe1, 0x1f, 0x32, 0x2f, 0x66, + 0x09, 0xe8, 0x77, 0x57, 0x5c, 0x35, 0x08, 0x9a, 0xb1, 0x61, 0xb7, 0xf0, 0x5b, 0x62, 0xed, 0x96, 0xfe, 0xc5, 0x82, + 0xdd, 0x98, 0x3e, 0xd0, 0xad, 0x03, 0x2c, 0x23, 0x6a, 0xf2, 0x1d, 0xf2, 0x6e, 0x3a, 0x2b, 0x0a, 0xb7, 0x27, 0x76, + 0xe3, 0xb3, 0x6b, 0xf3, 0xe6, 0xdd, 0x93, 0x08, 0x72, 0xef, 0xb8, 0x77, 0x37, 0xbc, 0xf6, 0x2f, 0x74, 0x0b, 0xe4, + 0x64, 0x96, 0x33, 0x90, 0x3a, 0xe2, 0x33, 0x44, 0x2b, 0x7b, 0xca, 0x77, 0x42, 0xee, 0x6c, 0xeb, 0x27, 0x77, 0xee, + 0xb6, 0xb6, 0x7c, 0x72, 0xc7, 0xaa, 0x11, 0xc5, 0x8a, 0xd3, 0x14, 0x09, 0xb3, 0x68, 0x03, 0x3c, 0xf5, 0xf2, 0xfd, + 0x8e, 0x1d, 0x73, 0xb8, 0x7b, 0xd2, 0xd1, 0xf1, 0x72, 0x0e, 0xd8, 0xdd, 0x7f, 0xb4, 0x09, 0x1b, 0x2b, 0x5d, 0xab, + 0xd0, 0xe1, 0xee, 0x49, 0xa6, 0xf1, 0x1c, 0x8e, 0xe4, 0xd3, 0xb1, 0xc6, 0x06, 0x41, 0x5d, 0x9f, 0x33, 0xa8, 0x1d, + 0xbb, 0xaf, 0x09, 0xbb, 0xec, 0x98, 0xd7, 0xba, 0xe6, 0xed, 0x95, 0xa7, 0x62, 0x43, 0x40, 0x87, 0xaf, 0xd5, 0x0d, + 0xf2, 0x2f, 0x81, 0x53, 0x04, 0x80, 0x1c, 0x8e, 0x97, 0x3c, 0xf6, 0x7d, 0x9a, 0xa5, 0xf5, 0x0e, 0xb5, 0x16, 0x95, + 0x65, 0x19, 0xd6, 0xde, 0x0f, 0x5a, 0x31, 0x2c, 0x35, 0xfd, 0xd3, 0x71, 0xe0, 0x76, 0xb6, 0x5b, 0x19, 0xbb, 0x8c, + 0x27, 0xc5, 0xc5, 0xaf, 0xa7, 0x85, 0x72, 0xed, 0xe6, 0x6d, 0xfc, 0xa6, 0xd5, 0x92, 0xa5, 0xb5, 0x1e, 0xf2, 0xd2, + 0xb2, 0x88, 0x40, 0x00, 0xc3, 0x91, 0xb2, 0x8b, 0x25, 0xdc, 0x23, 0xac, 0xee, 0x41, 0x28, 0x99, 0x17, 0x2e, 0x9e, + 0xb2, 0x18, 0x12, 0x01, 0xb6, 0x3b, 0x54, 0x6c, 0x0b, 0x17, 0x4f, 0xd9, 0x86, 0x17, 0xfd, 0x7e, 0xa6, 0x3a, 0x85, + 0xac, 0x3b, 0x0b, 0xbe, 0x51, 0xcd, 0xb1, 0x86, 0x9a, 0xad, 0x4d, 0xb2, 0x35, 0xce, 0x6d, 0xc5, 0xc7, 0xb2, 0xad, + 0xf8, 0x58, 0x59, 0xeb, 0xd2, 0xbd, 0xde, 0xa3, 0xba, 0x00, 0xb6, 0xfe, 0xdb, 0xe3, 0x95, 0xeb, 0xf9, 0x8c, 0x00, + 0xbe, 0x6e, 0xf8, 0x78, 0x72, 0x83, 0x5e, 0x25, 0x37, 0xfe, 0xed, 0x40, 0x8d, 0xbf, 0xd3, 0xb9, 0x37, 0x00, 0x5d, + 0x49, 0x79, 0x05, 0xe4, 0x1d, 0xe4, 0x98, 0x5b, 0x76, 0xe5, 0xdd, 0xc9, 0x77, 0xd8, 0x35, 0xaf, 0x67, 0x37, 0x73, + 0xb6, 0x03, 0xa7, 0x82, 0x64, 0x60, 0x2f, 0x2b, 0xb6, 0x0b, 0x62, 0x3b, 0xe1, 0x77, 0x02, 0xa6, 0x7c, 0x0e, 0x41, + 0x5c, 0xc1, 0x2d, 0xc4, 0xe1, 0xc9, 0x3f, 0x07, 0x77, 0xad, 0xcd, 0xfa, 0x8e, 0x59, 0x9d, 0x13, 0xac, 0x99, 0xd5, + 0x83, 0xc1, 0xa2, 0x99, 0xac, 0xfa, 0x7d, 0x6f, 0xa7, 0x1d, 0x9f, 0x96, 0x52, 0x27, 0x76, 0x5a, 0xab, 0x75, 0xc3, + 0xae, 0xa5, 0xd6, 0xc5, 0x18, 0x7a, 0x80, 0xf8, 0xe9, 0x76, 0xc0, 0xef, 0x3a, 0xd6, 0x96, 0x77, 0xcd, 0x6e, 0xd8, + 0x0e, 0x2e, 0x41, 0x4d, 0x7b, 0xd9, 0x9f, 0x54, 0x2e, 0x68, 0xc7, 0x2e, 0x89, 0x87, 0x33, 0x66, 0x95, 0x32, 0xb3, + 0x4e, 0xaa, 0x2b, 0xd1, 0x19, 0xd3, 0x59, 0xeb, 0xf9, 0x5c, 0xcd, 0x27, 0x85, 0x06, 0xf5, 0x3b, 0x27, 0x3e, 0xa2, + 0xa2, 0xf3, 0x04, 0xb6, 0x96, 0x15, 0xc4, 0x6a, 0x9f, 0x83, 0xb5, 0x56, 0xbb, 0xf4, 0x7b, 0xf9, 0x80, 0xdb, 0x94, + 0xc3, 0x3a, 0x30, 0xa8, 0x39, 0xb1, 0xa2, 0x1e, 0xb2, 0x1d, 0xe3, 0xe6, 0xa7, 0x97, 0x3f, 0x38, 0x61, 0xc9, 0x8a, + 0xd5, 0xfe, 0xf4, 0xd7, 0x27, 0x9e, 0xfe, 0x4e, 0xed, 0x5f, 0x08, 0x3f, 0x18, 0xff, 0xbb, 0x76, 0x5f, 0x6b, 0x31, + 0x2a, 0x5b, 0xe5, 0x08, 0x8d, 0xbb, 0x95, 0x34, 0x59, 0x7e, 0x16, 0x9e, 0xb0, 0x16, 0x3c, 0xcb, 0xf5, 0x12, 0xcd, + 0x0a, 0x58, 0x61, 0x2d, 0x93, 0x70, 0x85, 0xb1, 0x5a, 0xda, 0xea, 0x5b, 0x34, 0xcd, 0xf1, 0xe1, 0x5c, 0x1b, 0x94, + 0x29, 0x67, 0x67, 0xc4, 0x6a, 0xb8, 0x0c, 0x4b, 0x13, 0x8a, 0x90, 0xdd, 0xdb, 0xc1, 0x8d, 0x9d, 0xb2, 0x94, 0x32, + 0x9c, 0x63, 0x30, 0xe1, 0x91, 0x18, 0x55, 0xf9, 0xfe, 0xbe, 0xa4, 0xc8, 0x69, 0x5b, 0x0e, 0xaa, 0x10, 0xf6, 0x91, + 0x44, 0x09, 0xdc, 0x8a, 0xb4, 0x50, 0xa4, 0x2c, 0xfe, 0x76, 0x80, 0x2e, 0xf0, 0x02, 0xea, 0x6a, 0xd4, 0xed, 0x0f, + 0x47, 0x3c, 0x7c, 0x60, 0xea, 0x03, 0x23, 0x96, 0x04, 0x6a, 0x7b, 0x9e, 0xa5, 0x4b, 0x50, 0xe1, 0xf7, 0x70, 0x35, + 0x11, 0xfb, 0xb9, 0x25, 0x45, 0x45, 0x36, 0xd2, 0x1b, 0x5a, 0x83, 0x47, 0x68, 0x4d, 0x79, 0xe1, 0xa4, 0xda, 0xa4, + 0xf3, 0x8e, 0x90, 0x63, 0xf5, 0xad, 0x25, 0x8c, 0x76, 0x45, 0x2f, 0xee, 0x1d, 0xbd, 0xe7, 0xe9, 0xaa, 0xe7, 0xfe, + 0xc4, 0x15, 0xf3, 0xe4, 0x36, 0x02, 0x75, 0x2b, 0xa8, 0x6e, 0xef, 0x55, 0x82, 0x05, 0x4b, 0xda, 0x7d, 0xfc, 0x76, + 0xd6, 0x0e, 0x44, 0x65, 0xac, 0xd2, 0xb7, 0x24, 0x61, 0x4f, 0x0c, 0x3a, 0x85, 0xaa, 0xdc, 0xee, 0x8e, 0xb6, 0xc0, + 0x75, 0xcc, 0x52, 0xf4, 0xdc, 0x16, 0xb9, 0x5b, 0xfe, 0xdd, 0x73, 0x45, 0xce, 0x7e, 0x09, 0x08, 0x4e, 0xcd, 0x37, + 0xc4, 0x97, 0x23, 0x3c, 0xaa, 0x6e, 0x81, 0xe3, 0xf4, 0x1d, 0xc0, 0x3f, 0x1c, 0x2e, 0x41, 0x13, 0x10, 0x0b, 0xd6, + 0x4b, 0xe3, 0x1e, 0xeb, 0xc5, 0xc5, 0x66, 0x99, 0xe4, 0x1b, 0x70, 0x66, 0xa0, 0x54, 0x4b, 0x3f, 0x70, 0xac, 0x16, + 0x50, 0xe1, 0x60, 0x76, 0x52, 0x2f, 0x2c, 0xa3, 0x1e, 0xd3, 0xe7, 0x67, 0xb0, 0x77, 0x84, 0x04, 0xc0, 0xfd, 0xb2, + 0x0f, 0x48, 0xc0, 0x43, 0x67, 0x76, 0x40, 0x38, 0x61, 0x16, 0x55, 0x81, 0x44, 0x72, 0xa4, 0x9f, 0x3d, 0x66, 0x22, + 0xf9, 0x83, 0x59, 0xcf, 0x39, 0x25, 0x7a, 0xac, 0xa7, 0x8e, 0x90, 0x1e, 0xeb, 0x59, 0x47, 0x44, 0x8f, 0xf5, 0xac, + 0xe3, 0xa3, 0xc7, 0x7a, 0xe6, 0xd8, 0xe9, 0x41, 0x60, 0x02, 0x44, 0x1e, 0xb0, 0x1e, 0x4d, 0xa6, 0x9e, 0xe2, 0x1e, + 0x20, 0x1a, 0x04, 0xd6, 0x93, 0xc2, 0x79, 0x0f, 0x90, 0xc7, 0x48, 0xac, 0x0e, 0x7a, 0xff, 0x31, 0x7e, 0xdc, 0x33, + 0x32, 0xf2, 0xb8, 0x75, 0x58, 0xfd, 0xaf, 0xff, 0x84, 0x00, 0x38, 0x3c, 0x9b, 0x7a, 0x97, 0x63, 0xc8, 0x2a, 0xcb, + 0x08, 0x24, 0x3f, 0x31, 0xf8, 0xf2, 0x05, 0x40, 0xd5, 0x67, 0xba, 0x56, 0x93, 0xa3, 0xf6, 0x98, 0x43, 0x57, 0x0c, + 0x00, 0xdb, 0xb0, 0x44, 0x55, 0x2d, 0x6c, 0xc2, 0xe2, 0xf6, 0x33, 0x8c, 0xe6, 0xb2, 0xe9, 0x05, 0x0d, 0xd4, 0x23, + 0x04, 0xbf, 0xb4, 0x1e, 0x5a, 0x6b, 0x99, 0x72, 0xe8, 0xda, 0x28, 0xaa, 0x6c, 0xa8, 0x4b, 0x58, 0xad, 0x45, 0x54, + 0x13, 0x45, 0xca, 0x25, 0xa3, 0x28, 0x96, 0x2a, 0xd8, 0x67, 0x62, 0x09, 0x51, 0xf3, 0xb4, 0xd5, 0x56, 0xc1, 0x7e, + 0x09, 0x08, 0x6b, 0x61, 0x2d, 0xa4, 0x33, 0xa8, 0xbd, 0xd3, 0x8f, 0x94, 0xbf, 0xbc, 0x90, 0xdb, 0xb9, 0x85, 0x22, + 0xdc, 0x9e, 0x83, 0xf2, 0xa6, 0xae, 0x4a, 0x45, 0x34, 0x5a, 0x02, 0xa5, 0xcc, 0x09, 0x22, 0x0b, 0x10, 0xc0, 0x71, + 0x03, 0x81, 0xcf, 0x6b, 0x7c, 0x02, 0x8d, 0x42, 0x20, 0x3f, 0xb0, 0x0a, 0xd7, 0x1e, 0xd2, 0x52, 0x6b, 0x44, 0x94, + 0x88, 0x1f, 0x5d, 0x3d, 0xc7, 0xf6, 0xd5, 0xd3, 0x58, 0x5b, 0x4a, 0x13, 0xc4, 0x4f, 0x2c, 0xb6, 0x10, 0x13, 0x44, + 0x75, 0x88, 0x8e, 0x60, 0x39, 0x21, 0x44, 0xe1, 0x4f, 0xa1, 0x9f, 0x1a, 0xf8, 0x4b, 0xb6, 0x28, 0xf2, 0x9a, 0x60, + 0x31, 0x2b, 0x06, 0x68, 0x55, 0x04, 0x9e, 0xe9, 0x6c, 0xa9, 0xcc, 0x69, 0x1e, 0x1d, 0xd9, 0xc1, 0x79, 0xd7, 0xc1, + 0x5e, 0xfa, 0x32, 0x76, 0xb2, 0x6c, 0x1a, 0xb5, 0xb1, 0x21, 0x12, 0x5e, 0x91, 0x5f, 0x67, 0xa9, 0x71, 0x8e, 0xcc, + 0xe5, 0xfa, 0xae, 0x8b, 0xe5, 0x92, 0xb6, 0x09, 0xab, 0x10, 0xa1, 0x6e, 0x1b, 0x2a, 0x97, 0xc2, 0x6c, 0x6c, 0x9a, + 0x06, 0xf8, 0x42, 0x51, 0xa9, 0x54, 0xa5, 0xb6, 0x52, 0xc9, 0x09, 0xef, 0xfa, 0xa6, 0x16, 0xa9, 0x2b, 0x82, 0x6d, + 0xcc, 0x50, 0x0f, 0xe5, 0x46, 0x8d, 0x7d, 0xdb, 0xb1, 0x4a, 0xef, 0x30, 0x41, 0xce, 0xc8, 0x8b, 0x1c, 0x5c, 0x94, + 0x14, 0x64, 0xae, 0x86, 0x30, 0x7f, 0xd0, 0xf0, 0x69, 0x61, 0xb9, 0x87, 0x12, 0x30, 0x3b, 0x6a, 0x78, 0x18, 0x21, + 0x10, 0x71, 0xa9, 0xec, 0x2b, 0x26, 0x7e, 0x4f, 0xc1, 0x2c, 0x99, 0xd0, 0xbd, 0x88, 0x45, 0x11, 0xda, 0xf8, 0x24, + 0x49, 0xa6, 0x9e, 0xa6, 0xe0, 0x46, 0x2e, 0xc3, 0x1c, 0x8d, 0xd0, 0x92, 0x8f, 0x1c, 0x48, 0x5f, 0xcb, 0xa9, 0x04, + 0x1f, 0x51, 0xa7, 0x80, 0xe3, 0xf9, 0x79, 0x61, 0xfd, 0x64, 0xb9, 0xc4, 0x5c, 0xd6, 0xe6, 0xbf, 0xec, 0xe8, 0x18, + 0xec, 0xf2, 0x34, 0x71, 0x5c, 0xfd, 0x47, 0x55, 0x52, 0xdc, 0xbf, 0x49, 0x73, 0x40, 0x11, 0xcc, 0xec, 0x29, 0xc6, + 0xc7, 0x3e, 0xcb, 0x14, 0xf0, 0xb7, 0xeb, 0xad, 0x25, 0x13, 0xbb, 0xa4, 0xdd, 0x5c, 0x19, 0xbf, 0xd4, 0x86, 0x1d, + 0x07, 0xe7, 0x06, 0xa0, 0x38, 0x6b, 0x74, 0x58, 0x5e, 0xeb, 0xb6, 0x55, 0xa1, 0x02, 0xb5, 0xfe, 0xf7, 0x6e, 0x61, + 0xca, 0xdb, 0xbc, 0x54, 0xde, 0xe6, 0xa1, 0x09, 0x10, 0x88, 0xcc, 0x90, 0x67, 0x4d, 0xc7, 0x24, 0x71, 0xef, 0x48, + 0x49, 0xfb, 0x8e, 0x14, 0x3f, 0x78, 0x47, 0x42, 0xbe, 0x25, 0x74, 0x64, 0x5f, 0x70, 0x72, 0x02, 0x65, 0x06, 0x7b, + 0x79, 0xcd, 0x64, 0xff, 0x80, 0xf6, 0xc2, 0xb9, 0x2c, 0xaf, 0xf8, 0x5b, 0xe1, 0xad, 0xfd, 0xe9, 0xfa, 0xb4, 0xab, + 0xea, 0xed, 0x37, 0x66, 0xe6, 0xe1, 0x50, 0x1c, 0x0e, 0x95, 0x09, 0xda, 0xbd, 0xe1, 0x62, 0x90, 0xb3, 0x3b, 0x37, + 0x3e, 0xfe, 0x9a, 0xa3, 0x88, 0xad, 0x94, 0x47, 0xd2, 0x85, 0x4a, 0x0c, 0x2f, 0x0d, 0x3c, 0xcc, 0x8e, 0x8f, 0x27, + 0xbb, 0xab, 0xbb, 0xc9, 0x60, 0xb0, 0x53, 0x7d, 0xbb, 0xe5, 0xf5, 0x6c, 0x37, 0x67, 0xf7, 0xfc, 0x76, 0xba, 0x0d, + 0xf6, 0x0d, 0x6c, 0xbb, 0xbb, 0x2b, 0x71, 0x38, 0xec, 0x9e, 0xf1, 0x1b, 0x7f, 0x7f, 0x8f, 0x80, 0xce, 0xfc, 0x7c, + 0xdc, 0xc6, 0xf8, 0x79, 0xdb, 0x76, 0xd5, 0xda, 0x01, 0x3c, 0xfd, 0x6b, 0xef, 0xed, 0x6c, 0x31, 0xf7, 0xd9, 0x07, + 0x7e, 0x0f, 0xfe, 0xf9, 0xb8, 0x49, 0x22, 0xf5, 0x89, 0x76, 0x99, 0x7c, 0x0b, 0x0e, 0xe4, 0x3b, 0x9f, 0x7d, 0xe6, + 0xf7, 0xb3, 0xc5, 0x9c, 0x17, 0x87, 0xc3, 0xfb, 0x69, 0x88, 0x64, 0x4d, 0x61, 0x45, 0x2c, 0x29, 0x9e, 0x1f, 0x84, + 0xc7, 0xef, 0x45, 0x64, 0x88, 0xb4, 0xdc, 0xbb, 0x43, 0xf6, 0x96, 0x45, 0x7e, 0x00, 0x1f, 0x64, 0x3b, 0x7f, 0x22, + 0x6b, 0x4a, 0xf7, 0x8b, 0x0f, 0xfe, 0xe1, 0x40, 0x7f, 0x7d, 0xf6, 0x0f, 0x87, 0xf7, 0xec, 0x1e, 0xc1, 0xd1, 0xf9, + 0x0e, 0xfa, 0x47, 0xdf, 0x3a, 0xa0, 0x2a, 0xc3, 0xeb, 0xd9, 0x66, 0xee, 0x3f, 0x5b, 0xb1, 0x25, 0x70, 0xa1, 0x28, + 0x2f, 0xb4, 0xb7, 0xec, 0x1e, 0xbd, 0xce, 0xc8, 0x89, 0x68, 0xb6, 0x9b, 0xfb, 0x2c, 0xc6, 0xe7, 0xea, 0xbe, 0x98, + 0x7c, 0xf3, 0xbe, 0xb8, 0x63, 0xdb, 0xee, 0xfb, 0xa2, 0x7c, 0xd3, 0x5d, 0x3f, 0x5b, 0xb6, 0x63, 0xf7, 0x30, 0xc3, + 0xae, 0xf9, 0xdb, 0xe6, 0xd8, 0x31, 0xf6, 0x9b, 0x37, 0x46, 0x00, 0x65, 0xb6, 0x60, 0xb1, 0xe0, 0xa0, 0x54, 0xab, + 0xb6, 0x25, 0x91, 0x57, 0x3a, 0x50, 0x6d, 0x46, 0x70, 0x5f, 0x2d, 0xe4, 0xcc, 0x33, 0x03, 0x7d, 0x5b, 0x21, 0x5a, + 0x38, 0x6c, 0xc0, 0xdf, 0x68, 0xeb, 0x18, 0xc3, 0x34, 0xab, 0x99, 0xb6, 0x45, 0x5d, 0x7e, 0xdf, 0x7b, 0x26, 0xbf, + 0x91, 0x81, 0x2d, 0x44, 0x52, 0x38, 0x8e, 0x2f, 0x9e, 0x9e, 0xf0, 0x5f, 0xb5, 0x3c, 0x6a, 0xb5, 0x5f, 0x28, 0xf5, + 0xe9, 0x35, 0x1d, 0xd1, 0xc4, 0xbd, 0x68, 0xcb, 0xb0, 0x46, 0x59, 0x53, 0x4b, 0x87, 0x61, 0x5c, 0xc3, 0xbe, 0x3c, + 0x70, 0xe8, 0x3b, 0x20, 0xd0, 0x56, 0xa9, 0x14, 0x68, 0xe1, 0x18, 0x46, 0x61, 0x16, 0x52, 0x1e, 0x16, 0x66, 0x29, + 0xef, 0xb1, 0x40, 0x8b, 0x5b, 0x75, 0x8f, 0xa9, 0xed, 0x16, 0x44, 0x58, 0xbd, 0x65, 0x9c, 0x5f, 0x36, 0xaa, 0x70, + 0x5b, 0x80, 0xa2, 0x08, 0xca, 0x60, 0x4f, 0x72, 0xdb, 0x8d, 0x92, 0x66, 0xa3, 0xb0, 0x16, 0xcb, 0xa2, 0xdc, 0xf5, + 0x1a, 0x76, 0x83, 0x17, 0x54, 0xfd, 0x84, 0xb0, 0x2d, 0x7b, 0xd6, 0xa1, 0x5c, 0xa4, 0xff, 0x96, 0xa5, 0xe7, 0xfb, + 0xad, 0x39, 0xff, 0xd3, 0x57, 0xf4, 0x51, 0xf9, 0xef, 0x5f, 0xd2, 0x4f, 0x06, 0xcb, 0xc8, 0x29, 0xf5, 0x53, 0x34, + 0xba, 0x4d, 0x73, 0xc2, 0xd8, 0xf2, 0xf5, 0xd3, 0xef, 0x90, 0x29, 0x48, 0x0e, 0xa5, 0x54, 0xe5, 0x64, 0x0f, 0x7d, + 0xe1, 0x75, 0x1f, 0x66, 0x82, 0x01, 0x08, 0xaf, 0xd1, 0xa6, 0x9a, 0x30, 0x89, 0x07, 0x57, 0xf0, 0x7f, 0x23, 0x88, + 0x41, 0xfb, 0x44, 0x51, 0xc7, 0xb6, 0x91, 0xae, 0xdb, 0xce, 0x41, 0x72, 0xa7, 0xae, 0xfc, 0x51, 0x39, 0xf9, 0x77, + 0x34, 0x44, 0x5e, 0x71, 0x85, 0x58, 0x59, 0x70, 0x89, 0xc5, 0x50, 0x91, 0x02, 0x5c, 0x43, 0x10, 0x29, 0x8b, 0x92, + 0xc2, 0x2d, 0x07, 0x55, 0x11, 0x80, 0x71, 0xb5, 0x3a, 0xea, 0x44, 0xf8, 0xb8, 0xb5, 0x16, 0x21, 0x58, 0xd1, 0xa8, + 0x95, 0xb5, 0x02, 0x5f, 0x90, 0xbe, 0x74, 0x28, 0x88, 0xe9, 0x51, 0x48, 0x55, 0xe9, 0x50, 0x20, 0xcd, 0xa1, 0xe2, + 0x1b, 0x83, 0x8d, 0xa2, 0x22, 0x3d, 0x7f, 0x69, 0x52, 0x72, 0x69, 0xcc, 0xf8, 0x20, 0xca, 0x48, 0xe4, 0x75, 0xb8, + 0x14, 0xd3, 0x02, 0xf9, 0x46, 0x8f, 0x1f, 0x04, 0x97, 0xf0, 0x6e, 0xc8, 0xbd, 0x02, 0x6c, 0x09, 0xd8, 0x01, 0xee, + 0x95, 0x19, 0xe5, 0x3a, 0xad, 0xeb, 0xb7, 0xd6, 0x43, 0x31, 0x0c, 0x9f, 0x58, 0x02, 0xdb, 0xd1, 0x3a, 0x3a, 0xd2, + 0xc3, 0x87, 0xff, 0x75, 0x55, 0x73, 0xd4, 0xa9, 0x5c, 0xce, 0x8e, 0x27, 0x2c, 0x45, 0xcc, 0xa0, 0xfb, 0xeb, 0xf6, + 0x5a, 0x00, 0xdd, 0x2e, 0x8b, 0x79, 0x36, 0xda, 0xc9, 0xbf, 0xa5, 0x1b, 0x2b, 0x4a, 0x9b, 0x78, 0x97, 0xf5, 0xc6, + 0xfe, 0x70, 0xf4, 0x1f, 0x4f, 0xde, 0x4d, 0x08, 0x55, 0x67, 0xc3, 0xd6, 0x3a, 0xce, 0xe5, 0x7f, 0xfd, 0xe7, 0x98, + 0xac, 0x20, 0x28, 0x08, 0xcb, 0x4e, 0x31, 0x51, 0xc1, 0x28, 0x52, 0xac, 0xf9, 0x78, 0xb2, 0x46, 0x9d, 0xf0, 0xda, + 0x5f, 0x68, 0x9d, 0x30, 0x31, 0xb2, 0x52, 0xf9, 0x6b, 0x56, 0xb1, 0xa5, 0xca, 0x2c, 0x20, 0xf3, 0x20, 0x9f, 0xac, + 0x8d, 0x06, 0x73, 0xc5, 0xeb, 0xd9, 0x7a, 0x2e, 0x95, 0xcf, 0x60, 0xca, 0x59, 0x0c, 0x4e, 0x96, 0xc2, 0xee, 0x48, + 0xa0, 0x68, 0xcd, 0xd0, 0xb5, 0x3f, 0xc5, 0x56, 0xbd, 0x4c, 0xab, 0x1a, 0xe0, 0x01, 0x21, 0x06, 0x86, 0xda, 0xab, + 0x85, 0x87, 0xd6, 0x02, 0x58, 0xfb, 0xa3, 0xd2, 0x0f, 0xc6, 0x93, 0x05, 0xbf, 0x41, 0xfe, 0xe5, 0xc8, 0x51, 0xbb, + 0xf7, 0xfb, 0xde, 0x1d, 0x48, 0xc1, 0x91, 0x6b, 0xa1, 0x40, 0x22, 0xa0, 0x1b, 0xbe, 0xf1, 0x95, 0x0f, 0xc6, 0x35, + 0x6a, 0xab, 0x41, 0x41, 0xed, 0xe8, 0x96, 0xc7, 0x8e, 0xde, 0xf9, 0xee, 0x84, 0xbe, 0xfa, 0x46, 0x0b, 0xc7, 0xdf, + 0x38, 0x23, 0xd7, 0x6c, 0xd5, 0x21, 0x47, 0x34, 0x93, 0x0e, 0x21, 0x62, 0xc5, 0xd6, 0xec, 0x9a, 0x54, 0xce, 0x9d, + 0x43, 0x76, 0xfa, 0x08, 0x55, 0x7a, 0xad, 0x87, 0xb7, 0x13, 0xa5, 0xbb, 0x3d, 0xde, 0x4d, 0xbe, 0x67, 0x13, 0x11, + 0x83, 0x01, 0x6d, 0x10, 0xce, 0xc8, 0x3a, 0x44, 0x2a, 0x1d, 0x20, 0x04, 0x8e, 0x09, 0x68, 0xfa, 0xaf, 0x6f, 0x49, + 0x14, 0x70, 0xa4, 0x8d, 0x90, 0xb5, 0xec, 0x70, 0xc8, 0x41, 0xa3, 0xdc, 0xfc, 0xe9, 0x15, 0xea, 0x34, 0x07, 0xe6, + 0xe9, 0x12, 0xf6, 0x1c, 0x3c, 0xd2, 0x8b, 0xe3, 0x23, 0xfd, 0xbf, 0xa3, 0x89, 0x1a, 0xff, 0xfb, 0x9a, 0x28, 0xa5, + 0x45, 0x72, 0x54, 0x4b, 0xdf, 0xa5, 0x8e, 0x82, 0x8b, 0xbc, 0xa3, 0x16, 0xb2, 0x67, 0xd9, 0xb8, 0x51, 0xcd, 0xfb, + 0xff, 0xb5, 0x32, 0xff, 0x5f, 0xd3, 0xca, 0x30, 0x25, 0x3b, 0x96, 0x6a, 0xe6, 0x81, 0x56, 0x31, 0xcc, 0xde, 0x90, + 0x84, 0xc8, 0x70, 0x69, 0xc0, 0x8f, 0x2a, 0xd8, 0xc7, 0x69, 0xb5, 0xce, 0xc2, 0x1d, 0x2a, 0x51, 0x6f, 0xc5, 0x32, + 0xcd, 0x9f, 0xd7, 0xff, 0x12, 0x65, 0x01, 0x53, 0x7b, 0x59, 0xa6, 0x71, 0x40, 0x16, 0xfe, 0x2c, 0x2c, 0x71, 0x72, + 0x63, 0x1b, 0xdf, 0xc8, 0xf1, 0xb4, 0x5f, 0x75, 0x66, 0x1e, 0x48, 0xa0, 0x06, 0xba, 0x90, 0x9c, 0xcb, 0xca, 0xe2, + 0x1e, 0xa1, 0x9b, 0x7f, 0x2c, 0xcb, 0xa2, 0xf4, 0x7a, 0x9f, 0x93, 0xb4, 0x3a, 0x5b, 0x89, 0x3a, 0x29, 0x62, 0x05, + 0x65, 0x93, 0x02, 0x8c, 0x3e, 0xac, 0x3c, 0x11, 0x07, 0x67, 0x08, 0xd4, 0x70, 0x56, 0x27, 0x21, 0x00, 0x0d, 0x2b, + 0x84, 0xfd, 0x33, 0x68, 0xe1, 0x59, 0x18, 0x87, 0x6b, 0x80, 0xc9, 0x49, 0xab, 0xb3, 0x75, 0x59, 0xdc, 0xa5, 0xb1, + 0x88, 0x47, 0x3d, 0x45, 0xc9, 0xf2, 0x2a, 0x77, 0xe5, 0x5c, 0x7f, 0xff, 0x27, 0x05, 0xb0, 0x1b, 0x30, 0xdb, 0x16, + 0xd8, 0x01, 0x40, 0x82, 0x02, 0xd9, 0x42, 0x9d, 0x46, 0x67, 0x6a, 0xa9, 0xc0, 0x7b, 0xae, 0x07, 0xf8, 0xab, 0x1c, + 0xb0, 0x8c, 0xeb, 0x42, 0x06, 0x8c, 0x20, 0x80, 0x11, 0x38, 0x28, 0x01, 0x43, 0x67, 0x88, 0xdb, 0xaa, 0x9c, 0xb5, + 0xd0, 0x5c, 0xe9, 0xb6, 0xe4, 0xa6, 0x51, 0xce, 0x56, 0x22, 0x80, 0xbe, 0xba, 0x29, 0x71, 0xba, 0x58, 0xb4, 0x92, + 0xb0, 0x6f, 0xdf, 0xb7, 0x53, 0x45, 0x1e, 0x1f, 0xa5, 0x21, 0xaf, 0xc0, 0xf3, 0x8c, 0x23, 0x49, 0x94, 0x08, 0x5e, + 0xe5, 0x8d, 0x19, 0x87, 0x1f, 0xdb, 0x94, 0x53, 0x7b, 0xb3, 0x5e, 0x00, 0xce, 0x13, 0xb4, 0x65, 0x80, 0xb1, 0x80, + 0xc1, 0xb9, 0x10, 0x4b, 0x9e, 0x22, 0xf8, 0xa5, 0x13, 0x29, 0x8c, 0xbb, 0x1c, 0x86, 0x79, 0x50, 0xf4, 0x2e, 0xa9, + 0x3f, 0xfa, 0x7d, 0xd4, 0x26, 0x83, 0x21, 0xa8, 0x04, 0x50, 0x59, 0x37, 0x48, 0x0c, 0xac, 0x4a, 0x37, 0x12, 0x97, + 0x10, 0x2f, 0xf3, 0xd5, 0x54, 0x44, 0xc1, 0xfb, 0x7a, 0x42, 0x08, 0x27, 0x18, 0x1f, 0xe2, 0x06, 0x08, 0x18, 0xac, + 0xe2, 0x02, 0x83, 0xe4, 0xb9, 0x44, 0xf7, 0xc7, 0xf3, 0x1d, 0x03, 0x5c, 0x39, 0xef, 0xa9, 0x76, 0xf5, 0xc0, 0x5e, + 0xae, 0xd2, 0x25, 0x23, 0x84, 0x15, 0xff, 0x17, 0x91, 0xf7, 0xed, 0x30, 0x01, 0xb5, 0x8d, 0xfc, 0x31, 0x48, 0xcc, + 0x65, 0xa2, 0x08, 0xe2, 0x51, 0x56, 0xb0, 0x24, 0x0d, 0x36, 0xa3, 0x24, 0x05, 0x8d, 0x26, 0xc6, 0x90, 0xa9, 0xd0, + 0x0e, 0x49, 0xa3, 0xd9, 0x98, 0xec, 0x63, 0xc8, 0x6b, 0xb8, 0x58, 0x2c, 0xf0, 0xbe, 0x37, 0x42, 0x75, 0xb0, 0x2d, + 0xcd, 0x21, 0xe0, 0x24, 0xc1, 0x9e, 0xba, 0x22, 0x25, 0x61, 0x36, 0xfa, 0x14, 0x72, 0x6e, 0x40, 0xc7, 0x49, 0x63, + 0xa8, 0x3e, 0x30, 0x09, 0xaf, 0x22, 0x74, 0x52, 0x56, 0x08, 0x0b, 0xb8, 0x6f, 0x64, 0x34, 0x5a, 0x49, 0x83, 0xc0, + 0xdb, 0x0c, 0x5b, 0x81, 0x4d, 0x68, 0xf8, 0x8f, 0x99, 0x87, 0x69, 0x35, 0x2b, 0xc1, 0x9c, 0x6f, 0xa0, 0x12, 0xe3, + 0xc9, 0xe2, 0x8a, 0x6f, 0x5c, 0xac, 0xc4, 0x64, 0xb6, 0x98, 0x4f, 0xd6, 0x92, 0x6a, 0x2e, 0xf7, 0xd6, 0x2c, 0x63, + 0x0b, 0xd8, 0x3f, 0x0c, 0x0c, 0xa5, 0x03, 0x3b, 0x9a, 0x6a, 0xda, 0x24, 0xc0, 0x64, 0x3a, 0xe7, 0x7c, 0x78, 0x89, + 0x68, 0xb2, 0x3a, 0x75, 0x27, 0x53, 0xd5, 0x0e, 0xae, 0xc9, 0x99, 0x9c, 0x1e, 0xa9, 0xa7, 0x5a, 0xf7, 0x92, 0x8f, + 0xb6, 0xc3, 0x6a, 0xb4, 0xf5, 0x03, 0x70, 0xeb, 0x14, 0x76, 0xfa, 0x6e, 0x58, 0x8d, 0x76, 0xbe, 0x86, 0xdd, 0x25, + 0x85, 0x40, 0xf5, 0x57, 0x59, 0x93, 0xb9, 0x78, 0x5d, 0xdc, 0x7b, 0x05, 0x7b, 0xea, 0x0f, 0xf4, 0xaf, 0x92, 0x3d, + 0xf5, 0x6d, 0x26, 0xd7, 0xbf, 0xd2, 0xae, 0xd1, 0x98, 0xe9, 0x78, 0xed, 0x0a, 0xac, 0xd0, 0x00, 0xf9, 0x05, 0x3b, + 0xda, 0xeb, 0x1c, 0x04, 0x02, 0x74, 0x2f, 0xc1, 0x51, 0x14, 0x10, 0x35, 0xad, 0x2a, 0x8f, 0x4e, 0xf7, 0xfe, 0x1e, + 0xdf, 0x08, 0x01, 0x9b, 0x3c, 0xb5, 0xee, 0x2d, 0x63, 0xff, 0x70, 0x80, 0x10, 0x7a, 0x39, 0xfd, 0x46, 0x5b, 0x56, + 0x8f, 0x76, 0x2c, 0xf7, 0x0d, 0xa3, 0x9e, 0x82, 0x31, 0x0c, 0x5d, 0x58, 0xc5, 0x48, 0x9e, 0x01, 0x59, 0xe3, 0x37, + 0x88, 0x2e, 0x60, 0xd1, 0xeb, 0xbd, 0x3c, 0xa2, 0x41, 0x04, 0x54, 0x7a, 0x4d, 0x1a, 0x8b, 0x7c, 0xae, 0x0a, 0xd1, + 0x7b, 0x6f, 0xed, 0xbc, 0x99, 0x91, 0x2c, 0x93, 0x46, 0xaa, 0xdd, 0xca, 0x62, 0x5d, 0x79, 0xb3, 0x13, 0xd2, 0xc5, + 0x1c, 0x43, 0x65, 0xf0, 0x38, 0x00, 0xa5, 0xe7, 0x3f, 0x42, 0xaf, 0x64, 0xc8, 0x34, 0x4b, 0x34, 0xb3, 0xbb, 0xc6, + 0x9f, 0xac, 0x52, 0x2f, 0x46, 0xc4, 0x6c, 0x60, 0x0b, 0x71, 0x5b, 0x54, 0xba, 0x2d, 0x0a, 0x65, 0x8b, 0x22, 0x7d, + 0xa8, 0x9d, 0xe9, 0xce, 0x2c, 0x7c, 0x56, 0x99, 0xf6, 0x7d, 0xce, 0xcc, 0xd8, 0x00, 0x6d, 0x17, 0xe1, 0x1b, 0xe8, + 0x40, 0x85, 0x90, 0xbf, 0x46, 0x44, 0x24, 0x02, 0x76, 0x39, 0x75, 0x27, 0x36, 0x1d, 0x92, 0x79, 0x88, 0x59, 0xa1, + 0x46, 0x79, 0xc1, 0x93, 0xa3, 0x01, 0xa9, 0x08, 0x75, 0xbb, 0xdf, 0x3f, 0x5f, 0xb8, 0xa0, 0xf6, 0x6b, 0x8a, 0x1d, + 0xa3, 0x9b, 0x02, 0xce, 0x05, 0x8f, 0xf2, 0x9e, 0x7b, 0xe7, 0x80, 0xe6, 0xd8, 0x9e, 0x22, 0x6b, 0xc0, 0xe9, 0x6d, + 0x17, 0x02, 0x6c, 0x9f, 0x35, 0x5b, 0xfb, 0x93, 0xd5, 0x55, 0x34, 0xf5, 0x4a, 0x3e, 0xd3, 0x5d, 0x94, 0xb8, 0x5d, + 0x14, 0xcb, 0x2e, 0xda, 0x34, 0x10, 0xec, 0xb8, 0xf2, 0x03, 0xe0, 0x0d, 0x8d, 0xfa, 0xfd, 0xb2, 0xd5, 0xb3, 0x27, + 0x5f, 0x3b, 0xee, 0xd9, 0xcc, 0x67, 0xa5, 0xe9, 0xd9, 0xdf, 0x52, 0xb7, 0x67, 0xe5, 0x64, 0x2f, 0x3a, 0x27, 0xfb, + 0x74, 0x36, 0x0f, 0x04, 0x97, 0x3b, 0xf7, 0x79, 0x3e, 0xd5, 0xd3, 0xae, 0xf2, 0x83, 0xd6, 0x10, 0x59, 0xbb, 0x5c, + 0xd5, 0xbd, 0xae, 0x60, 0x01, 0x4b, 0x70, 0xb7, 0x5e, 0x9a, 0xff, 0x86, 0xdd, 0xdf, 0x0b, 0x7a, 0x69, 0xfe, 0x3b, + 0xfd, 0x49, 0x01, 0x1c, 0x80, 0xc6, 0xd4, 0x6e, 0x81, 0x87, 0x18, 0x2a, 0x28, 0xdc, 0xcd, 0xca, 0xb9, 0x57, 0x03, + 0x1c, 0x26, 0xe9, 0x1b, 0x5a, 0xbd, 0xd2, 0x62, 0xd7, 0xcb, 0x64, 0xaf, 0x00, 0x0f, 0x55, 0xc8, 0xc3, 0xc3, 0x21, + 0xea, 0x18, 0x76, 0x50, 0x47, 0xc0, 0xb0, 0x87, 0xd0, 0xd8, 0x02, 0xcf, 0xc7, 0x37, 0x19, 0xdf, 0x0b, 0x50, 0x1b, + 0x21, 0x3c, 0x5e, 0x2d, 0xca, 0x10, 0x5b, 0xf6, 0x1a, 0xa9, 0xa4, 0xde, 0x08, 0x44, 0x19, 0xad, 0x02, 0xda, 0x6a, + 0x8f, 0x59, 0x1a, 0x3f, 0x41, 0xa8, 0x58, 0xea, 0x63, 0x08, 0x0d, 0x1c, 0x7e, 0x87, 0x03, 0x48, 0xf0, 0x25, 0xd7, + 0x64, 0x73, 0xaf, 0xf3, 0x3b, 0xda, 0xe7, 0x0f, 0x87, 0xf3, 0x4b, 0x04, 0xa5, 0x4b, 0xe1, 0x23, 0x95, 0x88, 0xea, + 0x29, 0x6e, 0x4a, 0xc8, 0x66, 0xc9, 0x4a, 0x3f, 0xf8, 0x4d, 0xfd, 0x02, 0x00, 0x59, 0x08, 0xb4, 0x89, 0xcc, 0xfe, + 0x74, 0xa6, 0xa2, 0x0b, 0x80, 0x43, 0xfc, 0xe1, 0x13, 0x44, 0xdf, 0xd0, 0x32, 0x2d, 0x1f, 0x27, 0x3c, 0x04, 0xad, + 0x2d, 0xe9, 0x24, 0x62, 0xa5, 0xc0, 0x86, 0x48, 0xf8, 0x7e, 0xff, 0x3c, 0x96, 0x74, 0xa0, 0x51, 0xab, 0x7b, 0xe3, + 0x56, 0xf7, 0xca, 0xd7, 0x75, 0x27, 0x37, 0x3e, 0x28, 0xda, 0x67, 0xf3, 0x46, 0xe5, 0xfb, 0xbe, 0xce, 0xd9, 0x9d, + 0xee, 0x1d, 0x39, 0x27, 0xbe, 0xbf, 0x87, 0x50, 0xf4, 0xd0, 0x14, 0x59, 0x96, 0x84, 0x01, 0xad, 0xb5, 0x6b, 0xcf, + 0x32, 0x3a, 0x78, 0xed, 0x1b, 0x42, 0x44, 0x9e, 0xe2, 0x93, 0x90, 0x5b, 0x1c, 0x1f, 0x14, 0xe8, 0x9f, 0x19, 0x7f, + 0xe6, 0xc4, 0x0f, 0x5b, 0xfd, 0x02, 0x38, 0x37, 0xdd, 0x7b, 0x77, 0x62, 0xd6, 0x63, 0x28, 0x65, 0xe3, 0xff, 0x7e, + 0x9f, 0xc8, 0x02, 0x9d, 0x8e, 0x68, 0x18, 0x08, 0xee, 0xa2, 0xfa, 0xbf, 0x57, 0xbc, 0xee, 0x59, 0xab, 0xf3, 0xe5, + 0xa7, 0x4e, 0x4f, 0x7a, 0xbd, 0x74, 0x2b, 0x7c, 0x19, 0x26, 0xbe, 0xf3, 0xba, 0xdf, 0xb0, 0xdd, 0x77, 0xbf, 0xbc, + 0x3b, 0x7a, 0x19, 0xd8, 0xa4, 0xf0, 0x9d, 0x4d, 0xc9, 0x67, 0x3d, 0x50, 0xf8, 0xf5, 0x58, 0xaf, 0x2e, 0xd6, 0x3d, + 0xd6, 0x43, 0x2d, 0x20, 0x7a, 0x58, 0x80, 0xfa, 0xaf, 0x67, 0x9f, 0x86, 0xc2, 0x41, 0x36, 0x4e, 0x15, 0x28, 0xb2, + 0xe0, 0xcf, 0xc4, 0x68, 0x5d, 0x10, 0x20, 0xb2, 0xd9, 0xbe, 0x3e, 0x56, 0x27, 0xb3, 0x6f, 0x4a, 0x2d, 0xc9, 0xe0, + 0x9b, 0x80, 0xcc, 0x0e, 0xac, 0x9c, 0xa0, 0x74, 0xdc, 0x1a, 0x70, 0x65, 0x8b, 0x48, 0xbc, 0xfd, 0x69, 0x90, 0x9d, + 0x35, 0x27, 0x8d, 0xf6, 0x61, 0x9f, 0xe6, 0x01, 0x02, 0x91, 0x4c, 0x45, 0x90, 0x6b, 0xee, 0x2d, 0xe9, 0xa3, 0xc3, + 0x39, 0x2f, 0xe4, 0x9f, 0x53, 0xa9, 0x43, 0x1c, 0x4a, 0xac, 0x81, 0x40, 0xe5, 0x19, 0xaa, 0x1c, 0x36, 0xc8, 0xf1, + 0x47, 0x47, 0x32, 0x93, 0x98, 0x2c, 0x72, 0xb7, 0x66, 0x2a, 0xfc, 0x40, 0xf0, 0x31, 0xcb, 0x39, 0x70, 0x81, 0xcd, + 0xe6, 0xbe, 0x9a, 0xe2, 0xe2, 0x0a, 0xfc, 0x31, 0x85, 0x5f, 0xf1, 0x14, 0x76, 0xda, 0xfd, 0xba, 0xa8, 0x52, 0xd4, + 0x6d, 0x14, 0x16, 0x95, 0x2c, 0x98, 0xd6, 0x90, 0x26, 0x3a, 0x8c, 0xfe, 0x24, 0x67, 0xa0, 0x20, 0xe4, 0x97, 0x4d, + 0x03, 0x8c, 0x54, 0x72, 0x79, 0x50, 0x25, 0x81, 0x17, 0x60, 0x1b, 0x54, 0x6c, 0x5d, 0x40, 0x90, 0x6d, 0x52, 0x94, + 0xe9, 0xd7, 0x22, 0xaf, 0xc3, 0x2c, 0xa8, 0x46, 0x69, 0xf5, 0x93, 0xfe, 0x09, 0xcc, 0xdb, 0x54, 0x8c, 0x6a, 0x15, + 0x93, 0xdf, 0xe8, 0xf7, 0x8b, 0x41, 0xeb, 0x43, 0x06, 0x1f, 0xbd, 0x36, 0x0d, 0x7e, 0xe5, 0x34, 0xd8, 0x61, 0xa2, + 0x11, 0x00, 0xc9, 0x9c, 0x5a, 0xf2, 0x50, 0xf4, 0x67, 0x90, 0x63, 0x8d, 0x2a, 0xa7, 0x60, 0xb0, 0xfe, 0xe3, 0xd1, + 0x0e, 0x4c, 0xbd, 0x38, 0xda, 0x92, 0x1d, 0xb4, 0xf2, 0x0d, 0x70, 0xbf, 0x46, 0xb6, 0x98, 0xe5, 0x00, 0xcd, 0x5e, + 0x23, 0x32, 0x3e, 0x79, 0x01, 0x8c, 0xd9, 0x3a, 0x0b, 0x23, 0x11, 0x07, 0x63, 0xd5, 0x98, 0x31, 0x03, 0x03, 0x17, + 0xe8, 0x5a, 0x26, 0x25, 0x69, 0x48, 0x07, 0x03, 0x56, 0xca, 0x16, 0x0e, 0x78, 0xd1, 0x1c, 0xb7, 0xe3, 0x5d, 0x8b, + 0xc6, 0x03, 0xdb, 0xc5, 0xf6, 0x77, 0x2f, 0x8a, 0xed, 0xdb, 0x70, 0x4b, 0x7a, 0x85, 0x9c, 0x25, 0xf4, 0xf3, 0x27, + 0xd9, 0x67, 0x0d, 0x27, 0xa7, 0x42, 0x33, 0xb4, 0x14, 0x09, 0xa5, 0x78, 0xa7, 0x27, 0x05, 0xc6, 0x32, 0x16, 0xfe, + 0x1e, 0x38, 0xa7, 0x0b, 0x45, 0xe4, 0x0e, 0x1c, 0xc7, 0x9f, 0xa0, 0x82, 0xe0, 0xbf, 0x00, 0xb3, 0x18, 0x20, 0x4f, + 0x67, 0x21, 0xe1, 0x14, 0xc2, 0xc5, 0x2a, 0xeb, 0xf7, 0xe5, 0x2f, 0xea, 0xa2, 0x8b, 0x4c, 0xd6, 0x7d, 0x12, 0x8e, + 0xcc, 0x58, 0x4e, 0xbd, 0x90, 0x3c, 0xef, 0x79, 0x32, 0x4d, 0x9e, 0xe4, 0x41, 0x04, 0x90, 0xcf, 0xe1, 0x5d, 0x98, + 0x66, 0x60, 0x95, 0x26, 0xe5, 0x47, 0x28, 0x7d, 0xf1, 0x79, 0xe5, 0x07, 0x3a, 0x7b, 0x6e, 0x92, 0xe1, 0xcd, 0xaa, + 0xf5, 0x26, 0xb5, 0xae, 0x8b, 0x07, 0xfc, 0xab, 0x33, 0xd8, 0x38, 0xd7, 0x99, 0xe0, 0xc0, 0x8b, 0xa4, 0xd6, 0x6b, + 0xc6, 0x9f, 0x65, 0xb8, 0x2e, 0x55, 0x1b, 0x7d, 0x14, 0xa2, 0x73, 0xc8, 0x54, 0x80, 0x42, 0x91, 0xf6, 0x0f, 0x4a, + 0xad, 0x4c, 0x2a, 0x6d, 0x24, 0x80, 0xee, 0x61, 0xd2, 0x60, 0x8b, 0xa1, 0x8c, 0xa5, 0x49, 0x94, 0x3b, 0x0d, 0xe2, + 0xca, 0x7e, 0xac, 0x24, 0x0e, 0x2d, 0x8b, 0xe4, 0xdf, 0xbb, 0x9e, 0xbe, 0x42, 0xea, 0x4e, 0x16, 0xc8, 0x8c, 0xf1, + 0x3c, 0x8f, 0x3f, 0x03, 0x61, 0x36, 0x68, 0xa3, 0xa2, 0x10, 0x42, 0x36, 0x88, 0x41, 0xe3, 0x79, 0x1e, 0xbf, 0x50, + 0x34, 0x1e, 0xf2, 0x51, 0xe4, 0xab, 0xbf, 0x4a, 0xfd, 0x57, 0xe8, 0x33, 0x13, 0x3c, 0x42, 0x35, 0xd1, 0xbf, 0x7b, + 0x3e, 0xbb, 0x03, 0xb5, 0x61, 0x14, 0x66, 0xa6, 0xfc, 0xca, 0x37, 0xc5, 0xd9, 0xeb, 0xaf, 0xe8, 0x2a, 0xdb, 0xba, + 0x1f, 0xbd, 0x3a, 0x22, 0xb0, 0x36, 0x46, 0x57, 0xdc, 0x18, 0x40, 0x0e, 0x93, 0xf7, 0x2b, 0x4a, 0xcb, 0x21, 0x0d, + 0x42, 0x07, 0x0d, 0x41, 0xaf, 0x24, 0xfa, 0x40, 0x62, 0x11, 0x63, 0x78, 0x21, 0x9e, 0x91, 0x9a, 0x4c, 0x34, 0xc4, + 0x2b, 0x62, 0x3f, 0x44, 0x4b, 0x4e, 0x4d, 0x74, 0x23, 0x4c, 0x31, 0x90, 0xd8, 0x19, 0x24, 0x27, 0x49, 0xad, 0xfc, + 0xe2, 0x99, 0x24, 0x2c, 0xb1, 0xf3, 0x10, 0x83, 0x49, 0x2d, 0xdd, 0xe9, 0x4d, 0x95, 0xbe, 0x1c, 0x69, 0x39, 0x68, + 0x1f, 0x80, 0x5d, 0x4a, 0x7a, 0xff, 0xa4, 0x50, 0xc4, 0x87, 0x30, 0x8e, 0x21, 0x7c, 0x8b, 0xa8, 0xae, 0xc0, 0xb9, + 0x56, 0xa0, 0xb1, 0x1a, 0x78, 0x68, 0x66, 0xd5, 0x7c, 0xc8, 0xe9, 0xa7, 0xd2, 0xf2, 0xc7, 0x88, 0xc6, 0x46, 0xeb, + 0xe6, 0x70, 0xd8, 0xd3, 0xaa, 0x97, 0xce, 0x41, 0x97, 0xcd, 0x24, 0x26, 0x6e, 0x20, 0x5d, 0x3f, 0xfa, 0xcd, 0x84, + 0xbd, 0x88, 0x0a, 0xb9, 0x14, 0x82, 0x82, 0x56, 0x07, 0x02, 0x87, 0xc2, 0x5b, 0x94, 0xf9, 0x22, 0xa6, 0x0d, 0x84, + 0xc1, 0xe7, 0x07, 0xf2, 0xf3, 0x4d, 0x41, 0x2a, 0x76, 0xac, 0x6b, 0xbf, 0xbf, 0x28, 0x3d, 0xc0, 0x93, 0x33, 0x49, + 0x9e, 0x36, 0x43, 0x58, 0x11, 0x40, 0x63, 0x56, 0x93, 0xc5, 0x09, 0x57, 0xe6, 0xf0, 0x55, 0xe5, 0x95, 0x2c, 0x65, + 0xea, 0x3c, 0xd5, 0x0b, 0x20, 0xea, 0x78, 0x83, 0x56, 0xa4, 0x7e, 0x85, 0xce, 0x5e, 0xb3, 0x12, 0x32, 0x1e, 0x9e, + 0x73, 0x9e, 0x8e, 0xee, 0x59, 0xc2, 0x23, 0xfc, 0x2b, 0x99, 0xe8, 0xc3, 0xef, 0x9e, 0xc3, 0xcd, 0x38, 0xe1, 0x91, + 0xdb, 0xec, 0x7d, 0x15, 0xae, 0xe0, 0x66, 0x5a, 0x00, 0x92, 0x5b, 0x90, 0x34, 0x01, 0x25, 0x24, 0x32, 0x21, 0xb3, + 0xa6, 0xe4, 0x8b, 0x96, 0xb6, 0xc1, 0x1a, 0x26, 0x9d, 0x07, 0xbc, 0x68, 0xf5, 0xd1, 0x6a, 0xa2, 0x5d, 0x66, 0xf9, + 0x7c, 0x88, 0x33, 0x54, 0x73, 0xdc, 0x9d, 0xc1, 0xcf, 0x01, 0xaf, 0x58, 0xd5, 0xa4, 0xa3, 0xdd, 0x80, 0x0b, 0x4f, + 0xae, 0xf3, 0x74, 0xb4, 0xc5, 0x5f, 0x72, 0x7f, 0x00, 0xe8, 0x60, 0xea, 0x12, 0xf8, 0x53, 0xb5, 0xd5, 0x54, 0xea, + 0xd7, 0xd6, 0x7e, 0x5d, 0x77, 0x56, 0x2b, 0xf7, 0xac, 0xcb, 0xd0, 0x1e, 0x19, 0x72, 0xc6, 0x0c, 0xf8, 0x73, 0xc6, + 0x92, 0x3f, 0x67, 0xac, 0xf8, 0x73, 0xc6, 0x8d, 0x91, 0x01, 0x94, 0xe0, 0x5e, 0xf2, 0x67, 0x7b, 0xc4, 0x0c, 0xb1, + 0x1a, 0x54, 0x02, 0x2b, 0x4b, 0x39, 0xf7, 0x91, 0x53, 0x4c, 0x39, 0x65, 0x78, 0xe9, 0x74, 0xe6, 0x0e, 0xe4, 0x3c, + 0x98, 0xb9, 0xc3, 0x64, 0xaf, 0xcf, 0x8d, 0x38, 0x96, 0xc6, 0xa4, 0xa8, 0x20, 0x9d, 0xd3, 0xe1, 0xe6, 0xd5, 0x71, + 0x9e, 0xb0, 0x8c, 0x8f, 0xdb, 0x67, 0x0a, 0x84, 0xd8, 0xe2, 0x19, 0x12, 0x29, 0x55, 0xb3, 0xdc, 0xe6, 0x0f, 0x87, + 0x7a, 0x74, 0xaf, 0x77, 0x7a, 0xf8, 0x95, 0xb0, 0x5f, 0x33, 0xcf, 0x3e, 0x41, 0x00, 0x93, 0x44, 0x9e, 0x49, 0x38, + 0xfa, 0xb1, 0x1c, 0xfd, 0x4d, 0xc3, 0xbf, 0x64, 0xa8, 0xee, 0x0e, 0x81, 0x89, 0x2d, 0x3b, 0x70, 0x08, 0x4e, 0x57, + 0x95, 0x48, 0xc0, 0xc1, 0x66, 0xc3, 0x22, 0xbd, 0xc7, 0x43, 0x9c, 0x0f, 0x0a, 0x1f, 0xa1, 0x61, 0x46, 0xef, 0xf7, + 0x37, 0xc2, 0xab, 0x64, 0x2b, 0x0f, 0x87, 0xc4, 0xba, 0x0b, 0x3b, 0xfa, 0x38, 0xda, 0xa3, 0x84, 0xda, 0x8f, 0x6a, + 0xbd, 0xa9, 0xd4, 0x83, 0xdc, 0xec, 0x42, 0x62, 0x50, 0xb1, 0x54, 0x9f, 0x5e, 0xa9, 0x3e, 0xd4, 0xac, 0xf3, 0xbb, + 0x3a, 0xee, 0x53, 0x31, 0x5a, 0xcb, 0x09, 0x01, 0xae, 0x83, 0x44, 0xa3, 0x03, 0x60, 0x9c, 0x6d, 0xb6, 0xbc, 0xd4, + 0xd6, 0x89, 0xd2, 0x71, 0x9c, 0xeb, 0xe3, 0xf8, 0x70, 0x90, 0x62, 0xc6, 0xe5, 0x91, 0x98, 0x71, 0xd9, 0x00, 0xbc, + 0x59, 0xe7, 0x41, 0x7d, 0x38, 0x5c, 0xd2, 0xa5, 0xc8, 0x74, 0xb6, 0x51, 0x7e, 0xd6, 0xa3, 0xfb, 0x27, 0x09, 0x9a, + 0x7b, 0x2b, 0xec, 0xbd, 0x48, 0xb6, 0x67, 0xb2, 0x4e, 0xbd, 0x8c, 0x7c, 0x7a, 0xe1, 0x9e, 0x5d, 0x72, 0xf5, 0xc3, + 0xea, 0xeb, 0xe9, 0x6f, 0xc2, 0x8b, 0x58, 0x45, 0xbb, 0x75, 0xc9, 0x84, 0xbd, 0xa5, 0x54, 0xd2, 0x2a, 0x2f, 0x9f, + 0x6e, 0xfc, 0x00, 0x33, 0xd3, 0x9e, 0x3e, 0xc8, 0x46, 0x54, 0x7f, 0x56, 0xa2, 0x56, 0x86, 0xc9, 0xc2, 0x79, 0xc9, + 0xd4, 0x93, 0x01, 0x8f, 0x59, 0xc9, 0x23, 0xd9, 0xe9, 0x8d, 0x41, 0x10, 0xc0, 0x3a, 0x27, 0xad, 0x3a, 0xe3, 0x68, + 0xb4, 0xaa, 0x5c, 0x9c, 0xae, 0x72, 0x81, 0xe1, 0x76, 0x6b, 0xb6, 0x51, 0x75, 0x96, 0x9b, 0x5a, 0xa5, 0x7c, 0x07, + 0xf0, 0xb1, 0xac, 0x72, 0x41, 0xc7, 0x94, 0xa9, 0xf3, 0x06, 0x82, 0xb1, 0x55, 0x8d, 0x0b, 0xa7, 0xc6, 0x05, 0x8f, + 0xa8, 0xdd, 0x4d, 0x53, 0x8f, 0xb6, 0xc0, 0x52, 0x3a, 0xda, 0xf1, 0x12, 0x55, 0x0a, 0x3f, 0x0b, 0xbe, 0x0f, 0xe3, + 0xf8, 0x45, 0xb1, 0x55, 0x07, 0xe2, 0x6d, 0xb1, 0x45, 0xda, 0x17, 0xf9, 0x17, 0xe2, 0x80, 0xd7, 0xba, 0xa6, 0xbc, + 0xb6, 0xe6, 0x34, 0xb0, 0x35, 0x8c, 0x94, 0x14, 0xce, 0xcd, 0x9f, 0x87, 0x03, 0xad, 0xec, 0x5a, 0xdd, 0x15, 0x6a, + 0x3d, 0xe6, 0xb0, 0x61, 0xdf, 0x64, 0xe1, 0x4e, 0x94, 0xe0, 0xc8, 0x25, 0xff, 0x3a, 0x1c, 0xb4, 0xca, 0x52, 0x1d, + 0xe9, 0xb3, 0xfd, 0xd7, 0x60, 0xcc, 0xd0, 0xa5, 0x09, 0x58, 0x36, 0x46, 0xf2, 0xaf, 0xa6, 0x99, 0x37, 0x4c, 0xd6, + 0x4c, 0xe1, 0x38, 0x34, 0x8c, 0x90, 0x06, 0x74, 0x1b, 0xd4, 0x86, 0x27, 0xf3, 0x4d, 0x55, 0x7e, 0x75, 0x47, 0xaa, + 0xfd, 0x60, 0x78, 0x39, 0x11, 0xe7, 0x74, 0x49, 0x52, 0x4f, 0x25, 0x94, 0x84, 0x60, 0x97, 0x3e, 0x90, 0x13, 0x2b, + 0x20, 0x6b, 0x19, 0xcb, 0x6f, 0xf5, 0x80, 0xd0, 0x7f, 0xda, 0xad, 0x17, 0xfa, 0x4f, 0xd3, 0x6c, 0xa1, 0xae, 0x3f, + 0x4c, 0xee, 0x3b, 0x7a, 0xfd, 0xc1, 0xe1, 0x9d, 0xba, 0xaa, 0xb8, 0x8a, 0x47, 0xb5, 0x61, 0x92, 0x1b, 0x65, 0xe1, + 0xae, 0xd8, 0xd4, 0x6a, 0x79, 0x3a, 0x0e, 0x23, 0x30, 0x23, 0x28, 0x40, 0xd6, 0x75, 0x1b, 0x11, 0xc3, 0x4a, 0x2e, + 0x13, 0xf2, 0x09, 0x01, 0x59, 0x94, 0x1a, 0xe7, 0xe3, 0x16, 0xa8, 0x44, 0x30, 0x38, 0x0d, 0xad, 0x55, 0x37, 0xf9, + 0x49, 0x65, 0x63, 0x4b, 0x20, 0x87, 0x24, 0x93, 0xc5, 0x72, 0x74, 0x2b, 0x16, 0x45, 0x29, 0xde, 0x60, 0x3d, 0x5c, + 0xb3, 0x85, 0xfb, 0x0c, 0x08, 0xed, 0x27, 0x4a, 0x7b, 0x13, 0x69, 0x82, 0xee, 0x25, 0x5b, 0x01, 0xc8, 0x00, 0x8a, + 0xba, 0xda, 0xad, 0xcf, 0xf9, 0x39, 0x92, 0x66, 0x38, 0x8c, 0x6e, 0x9f, 0x2e, 0x83, 0xe5, 0xe0, 0x12, 0xb5, 0xd2, + 0x97, 0x2c, 0x6e, 0x61, 0x50, 0xed, 0xcd, 0x12, 0x0e, 0x6a, 0x66, 0xad, 0x8d, 0x40, 0x30, 0xd9, 0x43, 0x41, 0xc5, + 0x5c, 0xc1, 0x3e, 0x28, 0x58, 0x4b, 0x5e, 0x07, 0x87, 0x5b, 0xfb, 0xb2, 0x52, 0x5c, 0x3c, 0xbd, 0x48, 0x5a, 0x17, + 0x96, 0xf2, 0xe2, 0x69, 0x03, 0x06, 0x97, 0x23, 0x6c, 0x2a, 0x30, 0x49, 0x00, 0xe8, 0x56, 0x44, 0x11, 0x2f, 0x4a, + 0x61, 0xdb, 0xca, 0x67, 0x4e, 0xd8, 0x60, 0xc3, 0xee, 0xe1, 0x5e, 0x19, 0x94, 0x0c, 0x2e, 0xc4, 0xb8, 0xdd, 0xec, + 0x02, 0x5c, 0xc1, 0x50, 0x18, 0x5b, 0xf3, 0x77, 0x99, 0x17, 0x29, 0x01, 0x37, 0x43, 0x94, 0xaf, 0x0d, 0x9c, 0x4c, + 0x7a, 0x72, 0x2d, 0x58, 0x0c, 0x58, 0xd0, 0xe0, 0x3b, 0x6a, 0xfd, 0x9d, 0xc9, 0xbf, 0xf1, 0xf4, 0xd0, 0x0f, 0x5e, + 0x64, 0xde, 0xc2, 0x67, 0xef, 0x2a, 0x19, 0xad, 0x49, 0xa2, 0xbc, 0x7a, 0xb8, 0x00, 0xb9, 0x61, 0x31, 0xba, 0x67, + 0x0b, 0x10, 0x27, 0x16, 0xa3, 0x84, 0x32, 0xba, 0xc2, 0xbd, 0xca, 0x6c, 0x99, 0x08, 0xa4, 0x38, 0xb0, 0x90, 0x72, + 0x6f, 0xb1, 0x0e, 0x16, 0xb8, 0x3f, 0x91, 0x5c, 0x40, 0xc9, 0x03, 0x28, 0x57, 0x0a, 0x08, 0xf8, 0x74, 0x00, 0xe5, + 0x4b, 0x79, 0x11, 0xfe, 0xc4, 0x89, 0x1a, 0x2c, 0x46, 0xf7, 0x0d, 0xfb, 0xc9, 0x0b, 0x2d, 0xfb, 0xc3, 0x52, 0x6b, + 0x1a, 0x56, 0x7c, 0x09, 0xd3, 0x62, 0xe2, 0xf6, 0xe5, 0xca, 0xae, 0x8a, 0xcf, 0x56, 0xea, 0xec, 0xa6, 0x86, 0x24, + 0xec, 0x1b, 0xb2, 0x0a, 0x70, 0xb0, 0x2a, 0xe2, 0x9e, 0x75, 0xb9, 0x0f, 0xa3, 0xbf, 0x36, 0x69, 0x29, 0x2c, 0x54, + 0x49, 0x7f, 0xdf, 0x94, 0x02, 0xa9, 0x4c, 0x74, 0xa2, 0x85, 0xe0, 0x0a, 0x0c, 0x02, 0x77, 0x22, 0xaf, 0x01, 0x30, + 0x06, 0x5c, 0x0a, 0x94, 0x65, 0x5b, 0x42, 0x48, 0x75, 0x3f, 0x03, 0xb5, 0x9d, 0xb8, 0x4b, 0x23, 0xb2, 0x16, 0xa2, + 0xaf, 0x82, 0x31, 0x73, 0x5e, 0x4a, 0xb7, 0xd8, 0x74, 0xb5, 0x59, 0x7d, 0x42, 0xe7, 0xd2, 0x96, 0x9b, 0x9f, 0xb0, + 0xc5, 0x5a, 0x81, 0xb2, 0x09, 0x49, 0xdb, 0x39, 0xcf, 0x51, 0x36, 0xa1, 0xa5, 0xbd, 0xa7, 0x1e, 0x15, 0xaa, 0x93, + 0xad, 0x97, 0xaa, 0xa9, 0x45, 0x58, 0x2d, 0x2e, 0x2a, 0x3f, 0x00, 0xdd, 0x54, 0x5a, 0x3d, 0xaf, 0x6b, 0x34, 0x85, + 0x5a, 0x2d, 0x1c, 0x37, 0xda, 0xd9, 0x74, 0x91, 0x2e, 0x11, 0x67, 0x55, 0xda, 0xa1, 0x7f, 0xca, 0xb4, 0xeb, 0x65, + 0x47, 0xbf, 0x19, 0x57, 0x17, 0xb8, 0x10, 0x1b, 0xf0, 0x39, 0xf7, 0x97, 0xd7, 0x7b, 0x1a, 0xf7, 0xfc, 0xc3, 0x01, + 0xd9, 0x93, 0xda, 0x1f, 0xaa, 0x8f, 0x5d, 0xc1, 0x90, 0x85, 0x51, 0xea, 0x2f, 0x52, 0xde, 0x7b, 0x84, 0xe3, 0xfe, + 0xa5, 0xea, 0xb1, 0x5f, 0x32, 0xbe, 0xaf, 0x8b, 0x4d, 0x94, 0x50, 0x54, 0x43, 0x6f, 0x55, 0x6c, 0x2a, 0x11, 0x17, + 0xf7, 0x79, 0x8f, 0x61, 0x32, 0x8c, 0x85, 0x4c, 0x85, 0x3f, 0x65, 0x2a, 0x78, 0x84, 0x50, 0xe2, 0x66, 0xdd, 0x23, + 0xed, 0x26, 0xc4, 0x29, 0xd5, 0xa2, 0x94, 0xc9, 0xf8, 0xb7, 0x7e, 0x02, 0xe5, 0x39, 0x45, 0xcb, 0xf4, 0xa3, 0xc2, + 0x65, 0xfa, 0x66, 0x7d, 0x5c, 0x7a, 0x26, 0x42, 0x9d, 0xb9, 0xd8, 0xd4, 0x3a, 0x1d, 0x63, 0xa7, 0x74, 0x6a, 0xc3, + 0xbe, 0x56, 0x8a, 0xcb, 0x8a, 0xc2, 0xbf, 0x91, 0xc8, 0xaa, 0x67, 0xc4, 0xf1, 0x7f, 0x66, 0xed, 0x33, 0xac, 0x02, + 0xbf, 0x0c, 0xe4, 0xfd, 0x02, 0xe0, 0xe3, 0xba, 0x2e, 0xd3, 0xdb, 0x0d, 0xd0, 0x86, 0xd0, 0xf0, 0xf7, 0x7c, 0x64, + 0xc0, 0x74, 0x1f, 0xe1, 0x0c, 0xe9, 0xa1, 0xce, 0x39, 0x9d, 0x95, 0xe9, 0x9c, 0xab, 0xb0, 0x96, 0x60, 0x2f, 0x27, + 0x4d, 0x2e, 0xd7, 0x25, 0xa8, 0x99, 0xc0, 0xed, 0x43, 0x7b, 0x44, 0x08, 0xb5, 0x29, 0xab, 0xe9, 0x25, 0xd4, 0xbc, + 0x93, 0xd3, 0x8e, 0x26, 0x25, 0xb8, 0x6a, 0xe8, 0xac, 0x5c, 0xff, 0x75, 0x38, 0xf4, 0x6e, 0xb3, 0x22, 0xfa, 0xb3, + 0x87, 0xfe, 0x8e, 0xdb, 0x4f, 0xe9, 0x57, 0x88, 0x96, 0xb1, 0xfe, 0x86, 0x0c, 0xe8, 0x78, 0x32, 0xbc, 0x2d, 0xb6, + 0x3d, 0xf6, 0x15, 0x35, 0x58, 0xfa, 0xfa, 0xf1, 0x09, 0x24, 0x54, 0x5d, 0xfb, 0xc2, 0xe2, 0x09, 0xf3, 0x94, 0x68, + 0x5b, 0xf8, 0x10, 0x16, 0xfa, 0x15, 0x22, 0x23, 0x21, 0xdc, 0x54, 0x76, 0x8f, 0x92, 0x76, 0xa1, 0x2f, 0x7d, 0x2d, + 0xfb, 0xca, 0x77, 0x2e, 0x00, 0x56, 0xf6, 0xa9, 0x0d, 0xf7, 0xa4, 0x3f, 0xa5, 0xfa, 0xb0, 0xfd, 0x2d, 0x59, 0x40, + 0xa1, 0x85, 0xf5, 0x54, 0xce, 0xce, 0x65, 0xc9, 0xf3, 0x6c, 0xba, 0x5f, 0xc3, 0x1e, 0x75, 0x87, 0x5e, 0x53, 0xc1, + 0xf9, 0xa5, 0x19, 0xbd, 0xdf, 0x0d, 0x85, 0xea, 0xa8, 0x73, 0x07, 0x59, 0x96, 0xd6, 0x25, 0xe7, 0x2f, 0x2b, 0x77, + 0x14, 0xe6, 0x77, 0x21, 0x78, 0x86, 0x75, 0xef, 0x2e, 0xce, 0x7b, 0xbf, 0xb5, 0xe6, 0xc8, 0x2f, 0xd9, 0x2c, 0x45, + 0x2c, 0x92, 0x39, 0x58, 0xfd, 0xd0, 0xcf, 0x63, 0xbf, 0x0d, 0x72, 0x38, 0x6e, 0x1a, 0xd0, 0x61, 0x43, 0x66, 0xed, + 0x4b, 0x04, 0x4e, 0x35, 0x82, 0x34, 0x35, 0x41, 0xcd, 0xf2, 0x10, 0x89, 0xed, 0x52, 0xb6, 0x0d, 0x72, 0xdd, 0x05, + 0xd3, 0x1c, 0x69, 0xcf, 0xe0, 0x7d, 0x93, 0x26, 0xa9, 0xd0, 0x2c, 0xba, 0x58, 0xc9, 0xf8, 0x77, 0xa4, 0xcd, 0x94, + 0xec, 0xb1, 0x35, 0xf0, 0x5e, 0x82, 0x72, 0x32, 0x4c, 0x31, 0x7c, 0xc7, 0xd7, 0x3b, 0x8f, 0x2e, 0xe2, 0xe7, 0x63, + 0xb6, 0x49, 0xd9, 0x11, 0x4c, 0x92, 0x8d, 0x6f, 0x28, 0xde, 0xf0, 0xfd, 0x6d, 0x25, 0x4a, 0x00, 0xbd, 0x2c, 0xf8, + 0x33, 0x69, 0x73, 0x85, 0x6e, 0x77, 0xef, 0x28, 0x85, 0x5f, 0xf2, 0xf2, 0x70, 0xd8, 0xa6, 0x5e, 0x08, 0x9d, 0x2f, + 0xe2, 0x77, 0x60, 0x0e, 0x63, 0x88, 0xcd, 0x08, 0x10, 0xe6, 0xf8, 0x80, 0x3a, 0x58, 0x3f, 0x02, 0xd0, 0x38, 0x81, + 0x02, 0x8c, 0xbe, 0xda, 0x16, 0xf4, 0x2d, 0x2f, 0x2e, 0x22, 0x44, 0x8d, 0x02, 0x4c, 0x94, 0x34, 0x8b, 0x61, 0x38, + 0xd0, 0xf9, 0x7d, 0x73, 0x5b, 0x97, 0x02, 0x87, 0xde, 0xb1, 0x0c, 0xff, 0xed, 0x7f, 0xac, 0x2d, 0xad, 0x2a, 0xdb, + 0xad, 0x71, 0x9a, 0xf9, 0xdf, 0x6e, 0x8b, 0x74, 0x0b, 0x15, 0x8a, 0xe7, 0x1d, 0xaf, 0xdb, 0x5f, 0x20, 0x7a, 0x5f, + 0xb7, 0x72, 0x55, 0x6a, 0x37, 0xcc, 0x94, 0xdf, 0xa7, 0x79, 0x5c, 0xdc, 0x8f, 0xe2, 0xd6, 0x91, 0x37, 0x49, 0xcf, + 0x39, 0xff, 0x52, 0xf5, 0xfb, 0xde, 0x17, 0x20, 0xe3, 0xbd, 0x16, 0xc6, 0x11, 0x93, 0x38, 0xf8, 0xf6, 0x62, 0x14, + 0x6d, 0x4a, 0xd8, 0x90, 0xdb, 0xa7, 0x25, 0x68, 0x66, 0xfa, 0x7d, 0x94, 0x28, 0xad, 0xf9, 0xfe, 0x0f, 0x39, 0xdf, + 0xaf, 0x85, 0xbc, 0x59, 0xc9, 0x0f, 0x1f, 0xad, 0x30, 0xf0, 0x3d, 0x4e, 0xbf, 0x8a, 0x1e, 0x5b, 0x95, 0x3e, 0x7c, + 0x57, 0x5a, 0xfa, 0xac, 0xa2, 0xfe, 0x85, 0x8a, 0x9a, 0x6b, 0x31, 0x22, 0xe2, 0x41, 0xd0, 0xce, 0xb6, 0x4b, 0xed, + 0x5a, 0x82, 0x76, 0xc1, 0xa6, 0xb0, 0xbf, 0x3f, 0x38, 0xe4, 0xfd, 0xfe, 0xc7, 0xdc, 0x6b, 0xf1, 0xba, 0x1b, 0xb8, + 0xcb, 0xd2, 0x43, 0x08, 0x60, 0x2d, 0x03, 0x65, 0x1c, 0x61, 0xd2, 0x45, 0x5e, 0xa3, 0x6c, 0x3a, 0x11, 0xf8, 0x98, + 0x65, 0x57, 0x4e, 0x32, 0x0d, 0x30, 0xa3, 0x9a, 0xc2, 0x4c, 0x80, 0x91, 0xfa, 0x88, 0x75, 0xd3, 0xd3, 0x2a, 0xb4, + 0x7c, 0x0d, 0xc1, 0xba, 0xc8, 0x32, 0x8e, 0x62, 0x26, 0x00, 0xd8, 0x7c, 0x04, 0xf9, 0x8a, 0xae, 0x0e, 0x49, 0x2b, + 0x55, 0xde, 0xaf, 0x33, 0x22, 0xa3, 0x49, 0x88, 0xe6, 0xb7, 0xf0, 0xc0, 0xbe, 0x6d, 0x66, 0x54, 0xa9, 0x67, 0x54, + 0xe5, 0x33, 0x1c, 0x96, 0xc2, 0x31, 0xe2, 0xff, 0x9c, 0xaa, 0x1e, 0x11, 0xe8, 0x55, 0x99, 0x56, 0x51, 0x91, 0xe7, + 0x22, 0x42, 0x84, 0x6a, 0xe9, 0x1c, 0x0e, 0xfd, 0xd8, 0xef, 0xe3, 0x40, 0x98, 0x17, 0xeb, 0xe4, 0x81, 0xae, 0xac, + 0x69, 0xad, 0xa4, 0xc0, 0xa9, 0xa8, 0x11, 0x22, 0x84, 0xf7, 0x1b, 0xf0, 0xac, 0xa6, 0xbe, 0xdf, 0x58, 0x26, 0xba, + 0xdf, 0x33, 0xa0, 0xfc, 0x01, 0xf9, 0xba, 0x92, 0xe2, 0x8c, 0x48, 0x1e, 0x12, 0x67, 0x1c, 0x80, 0x98, 0x6f, 0x4b, + 0x34, 0x1a, 0xfb, 0x1f, 0x90, 0x60, 0xa8, 0x7e, 0xb0, 0xd3, 0x4d, 0xbd, 0x7f, 0x66, 0x12, 0x47, 0xd1, 0xa7, 0x6d, + 0xf2, 0x58, 0xb2, 0x34, 0x5a, 0x38, 0x7a, 0x8f, 0x18, 0xc6, 0xe1, 0x74, 0x3e, 0x26, 0xd9, 0xc6, 0x64, 0x15, 0x40, + 0x3a, 0x99, 0xa9, 0x63, 0x4a, 0x1d, 0x8d, 0x73, 0xbd, 0xa0, 0x0a, 0x3d, 0xd6, 0x25, 0xcf, 0xc1, 0x7a, 0xf2, 0xda, + 0x2b, 0xfd, 0xa9, 0x90, 0x73, 0xd8, 0x48, 0x04, 0x85, 0x1f, 0xe0, 0x6a, 0xb0, 0x52, 0xc0, 0x60, 0xea, 0x5b, 0xf8, + 0x9a, 0x78, 0x8e, 0x82, 0x47, 0x61, 0x17, 0x63, 0x6b, 0xe5, 0x3b, 0x9f, 0x14, 0x94, 0x7b, 0x56, 0xcc, 0x79, 0x05, + 0x9c, 0xcb, 0xa0, 0x10, 0xa6, 0xe3, 0x59, 0xfe, 0xcf, 0x24, 0xaf, 0x27, 0x36, 0x04, 0xc8, 0xe0, 0x4f, 0x89, 0xd3, + 0xd2, 0x1d, 0xba, 0xf3, 0xd0, 0xb3, 0x88, 0xc3, 0x46, 0x8f, 0xd6, 0x65, 0xb1, 0x4d, 0x51, 0x2f, 0x61, 0x7e, 0x20, + 0x3f, 0x6f, 0xc9, 0xf7, 0x21, 0x8a, 0xb7, 0xc1, 0xcf, 0x19, 0x8b, 0x05, 0xfe, 0xf5, 0xb7, 0x8c, 0xd1, 0x44, 0x0b, + 0xfe, 0x9e, 0x35, 0x48, 0x54, 0x0c, 0x58, 0x11, 0xc0, 0x65, 0xaa, 0x3e, 0x7c, 0x4a, 0x8c, 0xb7, 0x66, 0xc3, 0x03, + 0xdf, 0xac, 0x40, 0xa7, 0x3e, 0x77, 0x57, 0xb6, 0xa7, 0xab, 0x91, 0xaa, 0x6a, 0xfc, 0x9c, 0xaa, 0x6a, 0xfc, 0x9c, + 0x52, 0x35, 0xfe, 0xca, 0x28, 0x7e, 0xa7, 0xf2, 0x19, 0x32, 0x27, 0x9b, 0x98, 0xa4, 0xd3, 0xf7, 0x86, 0x13, 0xbb, + 0xec, 0xb7, 0x6e, 0x13, 0x69, 0x66, 0x22, 0x85, 0xdc, 0x1b, 0x80, 0x9a, 0x89, 0x1f, 0x73, 0xc3, 0x29, 0x71, 0x7e, + 0xee, 0xe1, 0x8a, 0x4d, 0xab, 0x6b, 0x5a, 0xb0, 0xc0, 0xe6, 0x65, 0x96, 0x67, 0x9a, 0xc0, 0xb6, 0x29, 0xb3, 0xbe, + 0xc9, 0x3d, 0x80, 0x60, 0x26, 0x35, 0x01, 0x20, 0x2d, 0x44, 0xa5, 0x10, 0xf9, 0x35, 0xce, 0xea, 0x73, 0xde, 0xdb, + 0xe4, 0x31, 0x91, 0x56, 0xf7, 0xfa, 0xfd, 0xf4, 0x2c, 0xcd, 0x29, 0xa8, 0xe1, 0x38, 0xeb, 0xf4, 0xa7, 0x2c, 0x10, + 0x89, 0x5c, 0xa5, 0xff, 0x70, 0x83, 0xbc, 0x8c, 0xef, 0xeb, 0xb6, 0xe7, 0x4f, 0xd4, 0xdf, 0x3b, 0xeb, 0x6f, 0x0b, + 0x04, 0x77, 0x72, 0xec, 0x27, 0xab, 0x52, 0x1e, 0x19, 0x97, 0xf6, 0x9e, 0xdf, 0xd4, 0x45, 0x91, 0xd5, 0xe9, 0xfa, + 0x83, 0xd4, 0xd3, 0xe8, 0xbe, 0xd8, 0x83, 0x31, 0x78, 0x07, 0x80, 0x67, 0x3a, 0x34, 0x40, 0xfa, 0x9e, 0x91, 0x87, + 0xfb, 0xdc, 0x92, 0x9f, 0x54, 0xd6, 0x26, 0x09, 0x2b, 0x8a, 0xcd, 0x30, 0x46, 0x28, 0x19, 0xa7, 0xb1, 0xf5, 0xfb, + 0x7d, 0xf5, 0xf7, 0x0e, 0xa3, 0xa8, 0xa8, 0xb8, 0x63, 0x34, 0x2a, 0xab, 0x7a, 0xb4, 0x1d, 0x1c, 0x0e, 0xe7, 0xb9, + 0x8d, 0xa3, 0xad, 0x57, 0xc0, 0xde, 0x0a, 0x95, 0xb2, 0x57, 0x22, 0x2c, 0x3f, 0x5c, 0xf9, 0xfd, 0x3e, 0xfc, 0x2b, + 0x23, 0x2d, 0x3c, 0x7f, 0x8a, 0xbf, 0x6e, 0xea, 0x02, 0xc3, 0x33, 0x68, 0x8d, 0x56, 0x10, 0x4c, 0xf0, 0x8f, 0x0e, + 0xd4, 0x4b, 0x2b, 0xed, 0x23, 0xe8, 0x56, 0xa0, 0x07, 0x8d, 0x7d, 0x20, 0x69, 0x5f, 0x48, 0xd4, 0xed, 0xad, 0x4e, + 0xa3, 0x3f, 0x2b, 0x96, 0xf3, 0x0a, 0x26, 0x87, 0x1b, 0xfa, 0xb4, 0x0a, 0xb7, 0x9f, 0xe1, 0xe9, 0x1b, 0xa0, 0xdc, + 0x3a, 0x1c, 0x72, 0x10, 0x5b, 0xc0, 0xcd, 0x63, 0x15, 0x7e, 0x29, 0x4a, 0x19, 0x51, 0x1f, 0x4f, 0x4b, 0xd0, 0xde, + 0x05, 0xe8, 0x80, 0xa5, 0x41, 0xbc, 0x42, 0xf2, 0x9c, 0x8d, 0x00, 0x96, 0x1d, 0x58, 0xce, 0x32, 0x4e, 0x61, 0x9e, + 0xe5, 0xb3, 0x4a, 0xe3, 0xb3, 0x27, 0x5e, 0xcd, 0x32, 0x70, 0x16, 0xb8, 0xa8, 0x7c, 0x96, 0x69, 0xd5, 0x53, 0x91, + 0xa0, 0xcf, 0x2b, 0x39, 0xc1, 0x95, 0xe0, 0x64, 0x03, 0xf2, 0x0b, 0x90, 0xa4, 0x29, 0x65, 0x4d, 0xf9, 0xec, 0x92, + 0x6e, 0xc8, 0xe8, 0x39, 0xef, 0x79, 0xd1, 0x30, 0xf4, 0x2f, 0xbc, 0x12, 0xc2, 0x37, 0x71, 0xdb, 0x46, 0x29, 0xec, + 0x6f, 0x02, 0x8b, 0x4f, 0xd8, 0x6b, 0x6f, 0xe1, 0x4f, 0xc7, 0x41, 0x38, 0x44, 0x6e, 0xa8, 0x98, 0x03, 0x7b, 0x1a, + 0xb0, 0xd8, 0xc4, 0x57, 0x9b, 0x49, 0x3c, 0x18, 0xf8, 0x3a, 0x63, 0x31, 0x8b, 0x81, 0x06, 0x39, 0x1e, 0x5c, 0xce, + 0xf5, 0x09, 0xa1, 0x1f, 0x46, 0x54, 0x8e, 0x0a, 0x74, 0x0e, 0xa2, 0xc1, 0x02, 0xf0, 0xd4, 0x5b, 0xd9, 0x20, 0xc9, + 0xd0, 0x40, 0x27, 0xae, 0x35, 0x49, 0x75, 0x38, 0xa1, 0x75, 0xa0, 0xe3, 0xea, 0x0d, 0x74, 0x3e, 0xae, 0x7b, 0x1f, + 0xaf, 0x86, 0x37, 0x54, 0xfa, 0x85, 0x18, 0x78, 0xf5, 0x74, 0x1c, 0x5c, 0xd2, 0xad, 0xf0, 0x66, 0x15, 0x6e, 0xdf, + 0xc8, 0x07, 0x8e, 0x3b, 0x2a, 0x69, 0x08, 0x0c, 0xde, 0x1e, 0xba, 0x9b, 0x19, 0xc7, 0x94, 0xa3, 0xc3, 0x38, 0x92, + 0x43, 0xac, 0x5a, 0x71, 0x21, 0xbd, 0x11, 0x7c, 0xbb, 0x50, 0x8c, 0x65, 0x63, 0x97, 0x86, 0xa2, 0xf0, 0x67, 0x00, + 0x3b, 0xd4, 0xfe, 0x4a, 0x25, 0x1f, 0x23, 0xa3, 0x9a, 0x06, 0x3a, 0x06, 0x60, 0xc9, 0xd2, 0x44, 0x52, 0x45, 0x1a, + 0x89, 0x3f, 0x32, 0x63, 0x1d, 0x35, 0x5d, 0x5f, 0xb0, 0x1c, 0x59, 0x92, 0x6e, 0x67, 0x12, 0xcb, 0x89, 0x24, 0xb5, + 0xdd, 0x47, 0xc4, 0x60, 0xe0, 0x83, 0x8d, 0x98, 0x66, 0x22, 0x1c, 0xf1, 0xa8, 0x44, 0x16, 0x5d, 0x7e, 0x1b, 0x61, + 0xd2, 0xf6, 0x65, 0x45, 0xb6, 0x20, 0x98, 0x9e, 0x44, 0x1f, 0x24, 0x29, 0xa7, 0x22, 0x91, 0x66, 0x84, 0x00, 0x3f, + 0x9e, 0x94, 0x57, 0xfa, 0x73, 0xd0, 0xb4, 0x12, 0xbc, 0x64, 0x90, 0x3c, 0x12, 0x3f, 0x93, 0x82, 0x59, 0x8c, 0x55, + 0x83, 0x01, 0x96, 0x53, 0x3d, 0x71, 0x4c, 0xd2, 0x7f, 0xeb, 0x74, 0xc2, 0x7e, 0xee, 0xe5, 0xb6, 0x96, 0x37, 0xcd, + 0xbd, 0xe7, 0x5e, 0xc5, 0x52, 0x0d, 0xcb, 0xa0, 0xff, 0x9a, 0x68, 0x17, 0x6c, 0x6d, 0x19, 0x13, 0x56, 0xfd, 0x00, + 0xd2, 0x1e, 0xe9, 0xf2, 0xaa, 0x61, 0xce, 0x04, 0x8f, 0x2e, 0xac, 0x79, 0x10, 0x5d, 0x08, 0x1f, 0xb9, 0xec, 0x26, + 0xc9, 0xd5, 0x78, 0xe2, 0x87, 0x83, 0x81, 0x02, 0xa0, 0xa5, 0x75, 0x52, 0x0c, 0xc2, 0x27, 0x42, 0x0e, 0xa4, 0xd1, + 0x51, 0x15, 0x60, 0xb1, 0xcc, 0xae, 0xca, 0x49, 0x36, 0x18, 0xf8, 0x20, 0x36, 0x26, 0x76, 0x43, 0xb3, 0xb9, 0xcf, + 0x4e, 0x14, 0x64, 0xb5, 0x39, 0x6a, 0xcd, 0x74, 0x0b, 0x0c, 0x00, 0x06, 0x11, 0xc1, 0x72, 0x9f, 0x1a, 0xf9, 0x88, + 0x3a, 0x3d, 0x85, 0x11, 0x10, 0xfc, 0x72, 0x22, 0x10, 0xb9, 0x48, 0xa0, 0x1e, 0x60, 0x26, 0xc0, 0x8c, 0x2a, 0x86, + 0x97, 0xc0, 0x2e, 0x9e, 0x9b, 0x57, 0x0c, 0xfa, 0x17, 0x89, 0xd9, 0x89, 0xa6, 0x12, 0x47, 0x63, 0xe4, 0x54, 0x1a, + 0x23, 0x03, 0x62, 0x17, 0xc7, 0xbf, 0xa7, 0xf4, 0x28, 0x48, 0xd9, 0x8b, 0xca, 0x10, 0x87, 0xa3, 0xf8, 0x0a, 0x56, + 0x8d, 0xc3, 0xa1, 0x36, 0xaf, 0xa7, 0xb3, 0x7a, 0x3e, 0x10, 0x01, 0xfc, 0x37, 0x14, 0xec, 0x57, 0x4d, 0x45, 0x6e, + 0x90, 0x3a, 0x0f, 0x87, 0x14, 0xe4, 0x53, 0xdd, 0xe4, 0x9f, 0x2a, 0x77, 0x3f, 0x9d, 0xcd, 0xad, 0x39, 0x7a, 0x51, + 0xe3, 0xba, 0xb5, 0xba, 0xa1, 0x90, 0x68, 0x4d, 0x93, 0xe2, 0xaa, 0x9a, 0x14, 0x03, 0x9e, 0xfb, 0x42, 0x75, 0xb1, + 0x35, 0x82, 0x85, 0x3f, 0xb7, 0x40, 0x98, 0xf4, 0xb7, 0x92, 0x0e, 0xa9, 0x1a, 0x77, 0x6d, 0xb5, 0xdb, 0x56, 0x36, + 0xa4, 0x68, 0x3e, 0xbc, 0x84, 0x5d, 0x3a, 0x45, 0xb4, 0xed, 0x92, 0xe0, 0x0b, 0xd0, 0xb2, 0x7a, 0x23, 0xf2, 0x98, + 0x7e, 0x85, 0xfc, 0x52, 0x0c, 0xff, 0x53, 0xba, 0x37, 0xa7, 0x36, 0xc8, 0x01, 0x6c, 0xf7, 0x1e, 0x6e, 0xc7, 0xe8, + 0x81, 0x0c, 0xde, 0x08, 0x39, 0xe7, 0xfc, 0x72, 0x6a, 0xcd, 0x98, 0x68, 0x58, 0xb0, 0x72, 0x18, 0xf9, 0x01, 0x32, + 0x5e, 0x4e, 0x81, 0x95, 0xfd, 0xa8, 0x88, 0x4b, 0x7f, 0x18, 0xf9, 0x17, 0x4f, 0x83, 0x8c, 0x7b, 0xd1, 0xb0, 0xe3, + 0x0b, 0xb0, 0x57, 0x5f, 0x3c, 0x65, 0xd1, 0x80, 0x57, 0x57, 0xf5, 0x34, 0x0b, 0x86, 0x19, 0x8b, 0xae, 0x8a, 0x21, + 0xf8, 0xd0, 0x3e, 0x2b, 0x07, 0xa1, 0xef, 0x9b, 0x9d, 0x43, 0x77, 0x43, 0x2c, 0x8f, 0xb0, 0x9f, 0xc0, 0x6d, 0x57, + 0x4b, 0xcc, 0x60, 0xb2, 0x59, 0x46, 0xcc, 0x60, 0xcb, 0x5f, 0x3c, 0x35, 0x5c, 0x42, 0xd5, 0x33, 0xa9, 0xd9, 0x28, + 0xd0, 0x9c, 0x5c, 0xa1, 0x39, 0x59, 0x09, 0xb5, 0xe4, 0x93, 0x0a, 0x27, 0xec, 0x7c, 0x92, 0x2b, 0xbb, 0xd1, 0x18, + 0x03, 0x17, 0xad, 0xb9, 0x1d, 0x0a, 0x23, 0x33, 0x9d, 0xa5, 0x68, 0xc0, 0xc2, 0x33, 0x71, 0x4a, 0x63, 0x40, 0xfb, + 0x72, 0x60, 0x69, 0x43, 0x7e, 0x91, 0x33, 0x03, 0x6d, 0x43, 0x4a, 0xa3, 0x66, 0xe0, 0xcf, 0xd4, 0x84, 0xf9, 0x0d, + 0xac, 0x44, 0x10, 0xd5, 0x05, 0x98, 0x24, 0x39, 0x19, 0x8d, 0x94, 0x95, 0x48, 0xce, 0x01, 0xef, 0x23, 0x78, 0xb2, + 0x88, 0x6d, 0xed, 0x4f, 0xe9, 0x7f, 0x75, 0xf8, 0x5c, 0xfa, 0x4f, 0x04, 0xb0, 0x90, 0x4b, 0x83, 0xc8, 0x40, 0xe1, + 0x90, 0x5a, 0x86, 0xf7, 0xc4, 0xf1, 0x0c, 0x7c, 0x05, 0x17, 0x68, 0x0a, 0xe8, 0x0f, 0x6a, 0x46, 0x11, 0x59, 0xf8, + 0xab, 0x67, 0x37, 0x75, 0xa1, 0xe7, 0x99, 0xf3, 0x1a, 0x34, 0x33, 0x10, 0xd2, 0xe3, 0x54, 0xbd, 0x0d, 0x89, 0xce, + 0xcb, 0x6b, 0xfd, 0x32, 0x21, 0x92, 0x95, 0x91, 0xa7, 0xef, 0x73, 0x30, 0x8f, 0x28, 0x42, 0x07, 0x57, 0xe6, 0xe1, + 0x70, 0x2e, 0x28, 0x7c, 0x47, 0x79, 0x3e, 0xe0, 0x34, 0xcb, 0x12, 0xd0, 0x06, 0xb2, 0xdc, 0x94, 0xb9, 0x4c, 0x5a, + 0xa6, 0xee, 0x3d, 0x58, 0x09, 0x2a, 0x74, 0x73, 0x0a, 0x0a, 0x65, 0x24, 0x28, 0xa5, 0xd5, 0x20, 0x94, 0xea, 0xb0, + 0x08, 0x22, 0x87, 0x2c, 0x04, 0xdc, 0x4c, 0x45, 0xa3, 0x25, 0x0d, 0x8f, 0x70, 0x6e, 0xa0, 0x10, 0x80, 0xc4, 0x9e, + 0x2a, 0xca, 0xb8, 0x1c, 0x02, 0x3e, 0x4a, 0x38, 0xc4, 0x59, 0x93, 0xb6, 0x3c, 0x07, 0x71, 0x2c, 0x17, 0x7c, 0x59, + 0x21, 0x18, 0x44, 0xe8, 0x33, 0xe4, 0x4f, 0x96, 0xf3, 0xef, 0xd6, 0x61, 0xda, 0x11, 0x3e, 0xec, 0x6a, 0x37, 0x5c, + 0xcc, 0x6e, 0xe7, 0x13, 0x88, 0x6f, 0xb9, 0x9d, 0x1f, 0x63, 0x88, 0xdc, 0xf8, 0x83, 0xe5, 0x50, 0x72, 0x45, 0xa1, + 0xcb, 0x7a, 0x44, 0x8a, 0xec, 0xe9, 0x9a, 0x23, 0x08, 0x0e, 0xb4, 0x6a, 0x90, 0xa1, 0x91, 0xf8, 0xe2, 0x29, 0x64, + 0x0d, 0xd6, 0xfc, 0x45, 0x45, 0xce, 0xea, 0xfe, 0x64, 0x03, 0xd5, 0x24, 0x93, 0xb5, 0xa2, 0x72, 0xfe, 0x76, 0x55, + 0x16, 0x27, 0xab, 0x32, 0x5c, 0x0d, 0xba, 0xaa, 0xb2, 0xe0, 0x48, 0x6d, 0x80, 0xd6, 0x74, 0x85, 0x18, 0x0a, 0x59, + 0x83, 0x85, 0x55, 0x95, 0x35, 0xf5, 0x09, 0x04, 0xfa, 0x00, 0xcb, 0xa8, 0xd9, 0x4f, 0x87, 0xbf, 0x04, 0xbf, 0xa8, + 0x90, 0xa5, 0x3a, 0xad, 0x33, 0xf1, 0x5b, 0xb0, 0x60, 0xf8, 0xc7, 0xef, 0xc1, 0x1a, 0xb0, 0x04, 0xc8, 0x72, 0xb7, + 0xb1, 0xd1, 0x7a, 0xe5, 0x15, 0xe2, 0x5d, 0xad, 0x2f, 0xfa, 0xad, 0xdb, 0x44, 0xad, 0x00, 0x23, 0x14, 0x5a, 0x04, + 0xd8, 0xea, 0x81, 0x7b, 0x0a, 0x7e, 0x20, 0x86, 0x73, 0x4d, 0x5a, 0x53, 0x27, 0xbc, 0xce, 0xc6, 0x91, 0x88, 0xea, + 0x2d, 0x5c, 0xdc, 0xeb, 0xad, 0xc5, 0xdf, 0xa8, 0x40, 0x00, 0x64, 0x31, 0xc5, 0xda, 0x79, 0x43, 0x7a, 0x65, 0xd8, + 0x49, 0xe8, 0xbd, 0x61, 0x27, 0x90, 0x17, 0x87, 0x9d, 0x42, 0x97, 0x68, 0x3b, 0x45, 0x6a, 0xa2, 0xed, 0xa4, 0x9b, + 0x55, 0x58, 0x42, 0xf0, 0xab, 0xf6, 0xd6, 0x51, 0xb6, 0x2f, 0xb2, 0x84, 0x69, 0x0b, 0x18, 0xe5, 0x56, 0x7d, 0xe6, + 0x14, 0xb1, 0x52, 0xf6, 0x4e, 0x27, 0x55, 0xee, 0x22, 0x9f, 0x5b, 0x4d, 0x91, 0xc9, 0x2f, 0x8e, 0x5b, 0x24, 0x9f, + 0xbc, 0x69, 0x37, 0x4c, 0xa6, 0x7f, 0x3c, 0xfa, 0x02, 0xba, 0x22, 0x3b, 0x7d, 0x02, 0x01, 0x99, 0x0a, 0xaa, 0xd5, + 0xad, 0x62, 0x9a, 0xb7, 0xab, 0xec, 0xf6, 0x42, 0x89, 0xe1, 0x74, 0x76, 0x12, 0x1e, 0x6d, 0x86, 0x0c, 0x1c, 0x82, + 0x40, 0x21, 0x54, 0x14, 0xc3, 0x23, 0x50, 0x6b, 0x24, 0x1f, 0xe0, 0x47, 0xbb, 0x53, 0x41, 0xa4, 0x76, 0x53, 0x71, + 0xe3, 0xe4, 0xa6, 0xeb, 0xa5, 0x40, 0xad, 0x53, 0xb2, 0x02, 0x28, 0x21, 0xea, 0xcf, 0x62, 0x5b, 0x5f, 0xc3, 0x15, + 0x9b, 0xef, 0x1b, 0x45, 0x4f, 0xae, 0x4f, 0x51, 0xb7, 0xe2, 0xea, 0x34, 0x6d, 0x35, 0xc7, 0x8e, 0x33, 0xe4, 0xe0, + 0x59, 0x41, 0xb0, 0x1d, 0x95, 0x28, 0xdf, 0xb6, 0x9b, 0x8e, 0x89, 0xad, 0xfe, 0xb9, 0xa9, 0x36, 0x4b, 0xa8, 0x88, + 0x88, 0x8f, 0xb2, 0x9b, 0x27, 0xed, 0x77, 0xb0, 0xc7, 0x5a, 0x0d, 0x22, 0xfb, 0x0c, 0xae, 0x72, 0x9d, 0x16, 0xb9, + 0x2d, 0x83, 0xf3, 0x0f, 0xaf, 0x76, 0x15, 0x36, 0x39, 0xd6, 0xd5, 0xd5, 0x4c, 0x75, 0x52, 0xb1, 0x81, 0xb1, 0xa6, + 0xb5, 0x54, 0xf3, 0x18, 0x92, 0xee, 0xca, 0xe2, 0xac, 0x4a, 0xba, 0xe9, 0xb9, 0x71, 0xa6, 0x10, 0x03, 0x67, 0xab, + 0xd1, 0x72, 0x86, 0x21, 0xba, 0x3e, 0xcc, 0x12, 0xbf, 0xd5, 0x53, 0xee, 0xf3, 0x70, 0xeb, 0x77, 0xf5, 0x82, 0x93, + 0xc9, 0x7e, 0x72, 0x9c, 0xbb, 0x5d, 0xa4, 0xfd, 0xc4, 0xb7, 0x61, 0xfe, 0xf5, 0x0d, 0x62, 0x29, 0xea, 0x5f, 0x2a, + 0x00, 0x1a, 0xdc, 0xe4, 0xb1, 0x44, 0xa9, 0xdf, 0xab, 0xea, 0x07, 0x35, 0x53, 0x35, 0x0d, 0x04, 0x73, 0x2a, 0x05, + 0xfc, 0xe1, 0x76, 0xe1, 0x8a, 0x47, 0xdc, 0xb0, 0x30, 0xfe, 0xe5, 0xd5, 0xec, 0x54, 0x50, 0x19, 0xb8, 0x19, 0xff, + 0xe5, 0x09, 0x76, 0x0a, 0x6b, 0x05, 0x64, 0x85, 0xbf, 0xbc, 0xfc, 0x81, 0xf7, 0x2b, 0xfe, 0x97, 0x57, 0x3d, 0xf0, + 0x3e, 0xe2, 0xbc, 0xfc, 0x85, 0xa4, 0x4e, 0x88, 0xea, 0xf2, 0x17, 0x61, 0x8a, 0xad, 0xd2, 0xfc, 0x25, 0x29, 0x7c, + 0x82, 0x2f, 0xc0, 0x77, 0xb8, 0x0a, 0xb7, 0xe6, 0x37, 0x78, 0xec, 0x58, 0x6c, 0xbb, 0xd4, 0x17, 0x50, 0x8e, 0xc0, + 0x22, 0x72, 0xfb, 0xed, 0xca, 0x7e, 0xb5, 0x30, 0xca, 0x18, 0xbb, 0x2f, 0x59, 0x89, 0xd2, 0x59, 0xbf, 0x5f, 0x48, + 0xc1, 0xc8, 0x2e, 0xac, 0xd1, 0x1e, 0xa5, 0xea, 0xd5, 0xb7, 0x61, 0x1d, 0x25, 0x69, 0xbe, 0x94, 0xd1, 0x47, 0x32, + 0xec, 0x48, 0x5f, 0x49, 0x89, 0xf6, 0x5a, 0x85, 0xe5, 0x68, 0xf6, 0xeb, 0x92, 0x03, 0xe5, 0x75, 0x2b, 0x28, 0x5f, + 0x35, 0x01, 0xf4, 0x4a, 0xb5, 0xcf, 0x40, 0x2b, 0x28, 0x2c, 0x95, 0x07, 0x2b, 0x71, 0x2e, 0xfa, 0xac, 0x38, 0x1c, + 0xd4, 0xc5, 0x90, 0x50, 0xa0, 0x4a, 0x9c, 0x84, 0x46, 0x3c, 0x87, 0x0b, 0xa1, 0x78, 0x96, 0x63, 0x6c, 0x45, 0x0e, + 0x1c, 0xc8, 0xf0, 0x03, 0x02, 0xef, 0x65, 0xff, 0x0a, 0x06, 0xc3, 0x04, 0x37, 0x32, 0xea, 0xe4, 0x9c, 0xfd, 0x85, + 0x81, 0x19, 0xd4, 0x93, 0xda, 0x7d, 0x76, 0xaf, 0x02, 0x7b, 0xe1, 0x0c, 0x68, 0xef, 0xc6, 0xe8, 0x67, 0x55, 0xac, + 0x9d, 0xf4, 0xcf, 0xc5, 0x1a, 0x92, 0xe9, 0xb0, 0x38, 0xda, 0xa6, 0xe1, 0x91, 0x3c, 0x39, 0x8e, 0x37, 0xfd, 0xc3, + 0x61, 0x8c, 0x1f, 0x47, 0xf9, 0xb5, 0x05, 0xbc, 0x8a, 0x5b, 0x48, 0x63, 0x91, 0xa2, 0x77, 0x20, 0xe6, 0x50, 0xf4, + 0x92, 0xfd, 0x96, 0xf1, 0x72, 0x22, 0x28, 0x25, 0x89, 0x0d, 0xef, 0x48, 0x4f, 0xd3, 0x7a, 0xb4, 0x95, 0x01, 0xfb, + 0xf5, 0x68, 0x47, 0x7f, 0x81, 0xe2, 0xd1, 0xc2, 0x5f, 0xd2, 0xdf, 0xc5, 0xdd, 0xdc, 0x73, 0xbe, 0x69, 0x7c, 0x47, + 0x5c, 0xa0, 0x58, 0xb3, 0xfb, 0x6b, 0x5a, 0x3a, 0xeb, 0x40, 0x70, 0xc0, 0x5b, 0xec, 0xa2, 0x7d, 0xbf, 0x71, 0x9d, + 0x9e, 0xf6, 0xdf, 0xbb, 0x35, 0xca, 0xf7, 0x7e, 0x91, 0x28, 0x07, 0xfb, 0x97, 0x2e, 0x9a, 0xbf, 0xfd, 0x94, 0x21, + 0xa9, 0xd0, 0xdc, 0x60, 0x3b, 0xd9, 0x22, 0xac, 0x8d, 0x71, 0x50, 0xb1, 0x65, 0x19, 0x46, 0xc0, 0xa0, 0x8e, 0xfd, + 0x8f, 0x3e, 0x9b, 0x36, 0x64, 0x1f, 0x00, 0x2a, 0x57, 0x21, 0x60, 0x0f, 0xc0, 0x89, 0x46, 0xb8, 0x01, 0x6e, 0x35, + 0x5a, 0xd2, 0x41, 0xdd, 0x16, 0x0c, 0x44, 0x4b, 0xd8, 0xc8, 0xdb, 0xae, 0x4e, 0xdf, 0x10, 0x3e, 0xd4, 0x4e, 0x4a, + 0x87, 0xf2, 0x37, 0xcf, 0xd9, 0x7f, 0xef, 0xb0, 0xa6, 0xa6, 0x7c, 0x02, 0xcc, 0x9c, 0x95, 0xc8, 0x2b, 0x84, 0x4e, + 0x91, 0xdf, 0xab, 0xba, 0x12, 0xc3, 0x45, 0x2d, 0xca, 0xce, 0xec, 0xd6, 0x89, 0xde, 0x39, 0x05, 0xb5, 0x54, 0x36, + 0xc8, 0x49, 0xaa, 0xcd, 0x47, 0xd6, 0x0a, 0x4a, 0xd4, 0x35, 0x0a, 0x1c, 0x9f, 0x72, 0xed, 0xfe, 0xdf, 0x39, 0x13, + 0xd4, 0x6c, 0xa3, 0xba, 0xbf, 0xd4, 0x4f, 0x55, 0x4d, 0x62, 0x01, 0x2e, 0x27, 0x69, 0xde, 0xf1, 0x08, 0xab, 0x7f, + 0x9c, 0x2c, 0x45, 0xa0, 0x97, 0x11, 0xed, 0x4a, 0x40, 0x82, 0x76, 0x72, 0x16, 0x2a, 0x02, 0x05, 0xfa, 0xfa, 0x8b, + 0x4d, 0x9a, 0xc5, 0x72, 0x35, 0xdb, 0xc3, 0x44, 0x59, 0xac, 0x87, 0x08, 0x72, 0x66, 0xea, 0x60, 0xbf, 0xa7, 0x19, + 0xcd, 0xc2, 0x2b, 0x53, 0x82, 0x4b, 0x71, 0x15, 0x15, 0x39, 0xf8, 0x1c, 0xe2, 0x0b, 0x9f, 0x0b, 0xb9, 0x41, 0x44, + 0xd3, 0x9f, 0x24, 0xaa, 0x1d, 0x29, 0x90, 0x43, 0xc9, 0x4f, 0x88, 0xbf, 0x64, 0x6d, 0x8c, 0xfb, 0xa5, 0x53, 0xed, + 0x6b, 0x85, 0xe0, 0xfe, 0xc6, 0x16, 0x1b, 0x55, 0x9e, 0xe8, 0xc1, 0xa7, 0x58, 0xff, 0x93, 0x05, 0x94, 0xea, 0xbe, + 0x0d, 0x4e, 0xc5, 0xa3, 0x70, 0x53, 0x17, 0x9f, 0x10, 0x5a, 0xa0, 0x1c, 0x55, 0xc5, 0xa6, 0x8c, 0x88, 0x13, 0x76, + 0x53, 0x17, 0x3d, 0xcd, 0x81, 0x2e, 0xe7, 0x75, 0x22, 0x4f, 0x84, 0x76, 0x0b, 0xba, 0xa7, 0x39, 0x56, 0xe2, 0xb9, + 0x2c, 0x1d, 0x64, 0x9d, 0x48, 0x13, 0x2a, 0x77, 0x75, 0xd5, 0x51, 0xa9, 0xd4, 0x0d, 0xaf, 0x52, 0xcd, 0xf8, 0xbb, + 0x30, 0x7f, 0x62, 0xd9, 0xaf, 0x5a, 0xbf, 0xd5, 0x6a, 0x6f, 0xac, 0x1e, 0x95, 0xac, 0x39, 0xce, 0x26, 0x24, 0xa5, + 0x4f, 0xd8, 0x6e, 0x26, 0x5d, 0xeb, 0xc0, 0x93, 0xe0, 0x72, 0xe8, 0x09, 0xa8, 0x18, 0x34, 0xf1, 0x76, 0x17, 0xa8, + 0x47, 0xe0, 0x19, 0x28, 0x9f, 0xa8, 0x75, 0xc0, 0xcf, 0x6b, 0x2d, 0x4f, 0x19, 0x61, 0x58, 0xed, 0x2c, 0x5a, 0x0e, + 0xce, 0x3b, 0x45, 0xe0, 0xda, 0x95, 0xc0, 0xf3, 0xa1, 0x7a, 0x2f, 0x04, 0x0c, 0xf7, 0xcf, 0x85, 0xca, 0x66, 0x37, + 0xc3, 0x79, 0xd4, 0x38, 0x3d, 0xd0, 0xde, 0x76, 0xad, 0x87, 0x7a, 0xd7, 0xed, 0xdc, 0x56, 0xba, 0xf7, 0x6b, 0x27, + 0x93, 0x2e, 0xa0, 0xb5, 0xf9, 0xec, 0x3b, 0xbb, 0xd2, 0xba, 0xe9, 0x39, 0x7b, 0xb0, 0x75, 0x4b, 0x74, 0x2e, 0x88, + 0x26, 0xbf, 0x1f, 0x78, 0xd6, 0xb6, 0xa3, 0xdf, 0xa6, 0x1d, 0xdb, 0xdc, 0x43, 0xdd, 0x2b, 0xa8, 0xf5, 0x86, 0xe6, + 0xfd, 0x33, 0xd7, 0xb6, 0xe3, 0xab, 0x5f, 0xd7, 0x1d, 0xae, 0xf3, 0x26, 0x38, 0x6e, 0xba, 0xb6, 0xd5, 0xce, 0x7e, + 0xee, 0xee, 0xad, 0x9b, 0x28, 0xcc, 0xb2, 0x9f, 0x8a, 0xe2, 0xcf, 0x4a, 0xdf, 0x11, 0xe8, 0xe8, 0xce, 0x8b, 0x3a, + 0x5d, 0xec, 0x3e, 0x10, 0xc6, 0x93, 0x57, 0x1f, 0x11, 0xdd, 0xfa, 0x3e, 0x73, 0xbf, 0x02, 0xdc, 0x08, 0xee, 0x20, + 0xda, 0xbb, 0xa5, 0x3e, 0xa9, 0xd5, 0xd7, 0x7a, 0xed, 0x3c, 0x3d, 0xbf, 0xe9, 0xdc, 0x7e, 0xf7, 0xcd, 0xd1, 0xd6, + 0x7b, 0x5c, 0x58, 0x2b, 0x4b, 0x4f, 0x55, 0xc1, 0xde, 0x2c, 0x4f, 0x55, 0xc1, 0xe4, 0x81, 0xd7, 0xec, 0x17, 0x34, + 0xb8, 0xd2, 0xd1, 0xc6, 0x7b, 0xa2, 0x06, 0x6e, 0x51, 0x58, 0x3a, 0xfc, 0x92, 0x9b, 0xc9, 0x35, 0xee, 0x2f, 0x15, + 0xb9, 0xd8, 0x77, 0xce, 0xe8, 0xce, 0xcc, 0xba, 0x57, 0x15, 0xae, 0x16, 0xe4, 0xea, 0xc0, 0xd6, 0xb2, 0x8b, 0xc3, + 0x0d, 0x8b, 0x28, 0x40, 0x20, 0xa6, 0x57, 0x6a, 0xed, 0x8f, 0x68, 0x10, 0xf2, 0xc1, 0xc0, 0x2f, 0x30, 0x58, 0x15, + 0x28, 0x7c, 0xa0, 0x48, 0xfe, 0xd2, 0x13, 0xb0, 0x8b, 0x67, 0x80, 0x6e, 0xc5, 0x66, 0xc5, 0x08, 0x11, 0x32, 0x59, + 0xce, 0x6a, 0x3a, 0x83, 0x7c, 0xea, 0x8b, 0xef, 0x6c, 0xd5, 0xe9, 0xbc, 0xad, 0xa9, 0x72, 0xea, 0x50, 0xe8, 0xee, + 0xa6, 0xee, 0xdc, 0xba, 0xc8, 0x53, 0x87, 0x90, 0x2b, 0x15, 0x2b, 0x31, 0x0d, 0x35, 0x4f, 0xd2, 0x8c, 0xfa, 0xab, + 0xbd, 0xdf, 0x6b, 0x14, 0x4e, 0xf9, 0xd3, 0x31, 0xa8, 0xc2, 0x55, 0x0d, 0x71, 0x2c, 0x55, 0xf1, 0xc8, 0x06, 0x81, + 0xe6, 0xd5, 0xad, 0x4a, 0x9a, 0x90, 0xc9, 0x8d, 0xf0, 0xa9, 0x49, 0x29, 0x4f, 0xd3, 0x26, 0xad, 0x14, 0xa9, 0x83, + 0x0f, 0xea, 0x54, 0xe3, 0xb9, 0x59, 0x3d, 0x03, 0x30, 0xe3, 0xfc, 0x8a, 0x5f, 0x2a, 0x2e, 0xa3, 0xb6, 0x32, 0x93, + 0xf6, 0x27, 0x47, 0x63, 0xa3, 0x2e, 0xa7, 0x8d, 0x32, 0xc2, 0x4a, 0x69, 0x4e, 0x8a, 0xe5, 0x78, 0xfe, 0x01, 0x83, + 0x35, 0x4f, 0x60, 0x07, 0x13, 0x95, 0xf2, 0x3e, 0x02, 0xe2, 0xeb, 0x24, 0x5d, 0x26, 0x90, 0x22, 0xfd, 0x4b, 0x17, + 0x3c, 0x75, 0x18, 0x1b, 0x88, 0x31, 0x2b, 0x66, 0x46, 0xff, 0x83, 0xbb, 0xa4, 0x3f, 0x09, 0x01, 0x70, 0x13, 0x4d, + 0xa1, 0x53, 0xe7, 0xc9, 0x45, 0x1e, 0x2c, 0x2e, 0x3c, 0xb4, 0x62, 0xc4, 0x83, 0xff, 0x7c, 0x16, 0x22, 0x88, 0x39, + 0xa6, 0x78, 0xfa, 0x85, 0xd1, 0x7f, 0x04, 0x97, 0x18, 0x41, 0xe8, 0xee, 0x9d, 0xc3, 0x10, 0x6e, 0xf6, 0x20, 0x83, + 0xfa, 0x43, 0x1d, 0x12, 0x35, 0xfc, 0xa5, 0xf2, 0xa0, 0xff, 0xeb, 0x4c, 0x58, 0x6a, 0x3f, 0x3d, 0x1d, 0x40, 0x05, + 0xef, 0x2b, 0xde, 0x46, 0xc4, 0xf7, 0x89, 0x9f, 0xc4, 0x83, 0xcd, 0x93, 0x0d, 0x58, 0xeb, 0x3e, 0xe4, 0xc6, 0xba, + 0x4a, 0xd8, 0x40, 0xc0, 0xd7, 0x98, 0xd6, 0x9e, 0xd7, 0x6e, 0xf7, 0xe0, 0x3f, 0xfd, 0x8b, 0x90, 0x01, 0x13, 0xa7, + 0xef, 0x33, 0x27, 0x6b, 0x74, 0x91, 0xc9, 0xf4, 0xa1, 0x93, 0xbe, 0xd1, 0xe9, 0xbe, 0x13, 0xfe, 0x51, 0x31, 0x8b, + 0x0f, 0xb7, 0xf4, 0x95, 0x26, 0xc5, 0x1d, 0xb0, 0xb2, 0x79, 0x50, 0x10, 0xea, 0x5c, 0x44, 0xdf, 0x98, 0xf2, 0x2d, + 0xa1, 0x66, 0xdf, 0x58, 0x52, 0x4a, 0xf7, 0x1a, 0x7a, 0x95, 0xd6, 0xfa, 0x6d, 0x94, 0x60, 0x4c, 0x74, 0x3c, 0x79, + 0x19, 0x8f, 0x95, 0xf7, 0xf1, 0xb8, 0x91, 0x0a, 0x79, 0x00, 0x22, 0x50, 0x31, 0xfe, 0x74, 0xe5, 0xc9, 0x49, 0x2f, + 0x8c, 0x57, 0xa1, 0x14, 0x14, 0x06, 0x74, 0x05, 0x52, 0xc0, 0xa3, 0xf6, 0x44, 0x67, 0x61, 0x97, 0x70, 0x8f, 0x6e, + 0x02, 0xc6, 0xfa, 0xfc, 0x0b, 0xa0, 0xb9, 0x0b, 0x77, 0x78, 0x31, 0x40, 0x6d, 0xea, 0xd5, 0xdd, 0xc7, 0xb5, 0x3a, + 0x87, 0x43, 0x70, 0xb0, 0x1a, 0x44, 0x70, 0x3a, 0x9f, 0x3a, 0x9a, 0x65, 0x01, 0x2a, 0x27, 0xcb, 0x8d, 0xbc, 0x79, + 0xb4, 0xe8, 0xd5, 0x7d, 0x6f, 0x91, 0x96, 0x55, 0x1d, 0x64, 0x2c, 0x0b, 0x2b, 0xc0, 0xd5, 0xa1, 0xf5, 0x83, 0x70, + 0x59, 0x38, 0x7f, 0x20, 0x04, 0xb1, 0x7b, 0xb5, 0x2d, 0x78, 0xae, 0xe6, 0xf0, 0x93, 0xa7, 0x6c, 0xcd, 0x25, 0xea, + 0xa4, 0x33, 0x11, 0x80, 0xd8, 0x53, 0xb3, 0x8a, 0xae, 0x81, 0xa4, 0x4e, 0xb3, 0x8a, 0xae, 0xa9, 0xd9, 0xc6, 0x38, + 0x90, 0x8f, 0x56, 0x29, 0x60, 0xdf, 0x4d, 0xc7, 0xc1, 0xea, 0x49, 0x2c, 0xaf, 0x43, 0xcb, 0x27, 0x1b, 0xe5, 0x33, + 0xa8, 0x5b, 0x6d, 0x8c, 0x89, 0xed, 0xe6, 0xcb, 0xb9, 0x7e, 0x3b, 0x58, 0xf8, 0x76, 0xd0, 0x9c, 0x53, 0xf6, 0x52, + 0x97, 0xbd, 0xb2, 0xcb, 0xa6, 0x9e, 0x3b, 0x2a, 0x5a, 0x8d, 0x01, 0xbd, 0x81, 0x05, 0xeb, 0x73, 0x91, 0x66, 0xab, + 0x52, 0x95, 0x80, 0x17, 0xc6, 0x8a, 0x2d, 0xfd, 0x46, 0x66, 0x48, 0xc2, 0x3c, 0xce, 0xc4, 0x5b, 0xba, 0xd7, 0xc2, + 0xe4, 0x38, 0x16, 0xc9, 0x94, 0xd0, 0x29, 0xdd, 0xd9, 0x86, 0xce, 0x55, 0x18, 0x45, 0xb4, 0x56, 0x52, 0x69, 0x24, + 0x30, 0x35, 0x03, 0x94, 0xcc, 0x15, 0x38, 0xa5, 0xcb, 0xfd, 0xef, 0x48, 0x8c, 0x33, 0x5f, 0x94, 0xcc, 0x80, 0x6e, + 0xf9, 0x75, 0xb1, 0x6e, 0xa5, 0xc8, 0x08, 0xf3, 0xe6, 0xb8, 0xbd, 0xae, 0x0f, 0x81, 0x5c, 0x2d, 0x7b, 0x14, 0x8d, + 0x83, 0x42, 0x87, 0x4b, 0x95, 0x00, 0xfb, 0x22, 0xf1, 0x33, 0xc2, 0x96, 0xf6, 0x40, 0x6e, 0x8f, 0xce, 0x84, 0x39, + 0xe7, 0xa4, 0x2c, 0x3b, 0x97, 0x66, 0x70, 0x39, 0x71, 0x25, 0xb8, 0x48, 0x6f, 0xdb, 0xd3, 0xa4, 0xa5, 0xed, 0x63, + 0xc3, 0x39, 0x1a, 0xda, 0x06, 0xdd, 0xb1, 0x3f, 0x34, 0x17, 0x8b, 0xd8, 0xba, 0x58, 0x0c, 0x3b, 0xb3, 0x1f, 0x2d, + 0x16, 0x20, 0x07, 0x80, 0xa3, 0x6e, 0xc3, 0xc7, 0x6c, 0x01, 0x9c, 0x56, 0xd3, 0x6c, 0xea, 0x6d, 0x78, 0xf5, 0x44, + 0xf5, 0xf4, 0x82, 0xe7, 0x4f, 0x84, 0x19, 0x8b, 0x0d, 0xcf, 0x9f, 0x58, 0x47, 0x4e, 0xf5, 0x44, 0x28, 0xd1, 0xba, + 0x80, 0x66, 0xe0, 0x35, 0x05, 0x8c, 0x58, 0x32, 0x99, 0x52, 0x45, 0x1e, 0xf7, 0xa6, 0x1b, 0x35, 0x78, 0x41, 0xe1, + 0x10, 0x48, 0xe9, 0xf4, 0x8b, 0xa7, 0x4c, 0xbf, 0x77, 0xf1, 0xb4, 0x43, 0xd6, 0x36, 0x4c, 0x97, 0x9b, 0x61, 0x32, + 0x28, 0xfd, 0x27, 0x66, 0x62, 0x5c, 0x58, 0x93, 0x04, 0x10, 0xff, 0xc6, 0x7e, 0x87, 0x14, 0x6e, 0xde, 0x5f, 0x0c, + 0xe3, 0x07, 0xde, 0x8f, 0x91, 0x3d, 0x49, 0x33, 0xc4, 0x9a, 0x49, 0x85, 0xdc, 0x7d, 0xb5, 0xfe, 0x31, 0xb1, 0x9b, + 0xec, 0x81, 0x05, 0x20, 0xb6, 0xa6, 0xad, 0x6e, 0x79, 0xbf, 0xef, 0x99, 0x22, 0xc0, 0x0f, 0xca, 0x3f, 0xba, 0x33, + 0x24, 0x83, 0xb2, 0xeb, 0x86, 0x10, 0x0f, 0xca, 0xa6, 0x69, 0xaf, 0xb7, 0xbd, 0x33, 0x8f, 0xd5, 0x75, 0xda, 0x59, + 0x5c, 0x2d, 0x32, 0x48, 0xab, 0x0f, 0xd9, 0x71, 0x66, 0x9f, 0x1d, 0x2d, 0x95, 0xee, 0xf7, 0x21, 0x22, 0xee, 0x28, + 0x6b, 0xfb, 0xed, 0x16, 0x5c, 0xc3, 0xd1, 0x20, 0x74, 0x65, 0x6f, 0x97, 0xd1, 0xc6, 0x85, 0x38, 0xee, 0x99, 0xce, + 0x17, 0x7c, 0x79, 0x94, 0x76, 0x1e, 0x9c, 0xea, 0x89, 0x3e, 0x37, 0xdd, 0x55, 0x26, 0xd7, 0x3a, 0xac, 0xc6, 0xa0, + 0x36, 0x0b, 0x5b, 0xb8, 0x0b, 0xdb, 0xe8, 0xa0, 0xb5, 0x2f, 0x0b, 0xfe, 0x29, 0x03, 0xf0, 0xa5, 0x67, 0xcb, 0xb6, + 0xd7, 0xa4, 0xd5, 0x2b, 0x19, 0x85, 0xd8, 0xd2, 0xf6, 0xea, 0xd3, 0x51, 0x3e, 0x6e, 0x4e, 0x28, 0x2e, 0xe4, 0x28, + 0x3f, 0x78, 0x0d, 0x51, 0xd7, 0xba, 0x8e, 0x8b, 0x45, 0x87, 0x1b, 0x57, 0xdd, 0x76, 0xe3, 0x7a, 0x8d, 0x78, 0x6b, + 0xb4, 0x49, 0xa1, 0x56, 0xc6, 0x8e, 0xe0, 0x65, 0xf9, 0x70, 0xc8, 0xc4, 0x70, 0x28, 0x21, 0x53, 0x1f, 0xba, 0x37, + 0x34, 0xed, 0xf3, 0xd3, 0xd6, 0x8f, 0x58, 0x6a, 0x1c, 0xc5, 0x86, 0x77, 0xfa, 0xce, 0x63, 0x6b, 0x5c, 0xc9, 0x97, + 0xc1, 0x6c, 0x57, 0x50, 0x6d, 0x8d, 0x37, 0xec, 0xe5, 0xfc, 0xa7, 0x4a, 0x2a, 0xf9, 0xdb, 0x9f, 0xe1, 0x1a, 0xde, + 0xda, 0xd2, 0x41, 0x53, 0xcd, 0x72, 0x96, 0xeb, 0x7b, 0xc1, 0xf1, 0xc7, 0xdd, 0x2b, 0x82, 0xc1, 0xef, 0xe9, 0x28, + 0xc8, 0xc5, 0x52, 0xad, 0x01, 0x05, 0xe9, 0xc8, 0x8e, 0xa9, 0x2c, 0x30, 0x0c, 0xe0, 0x0d, 0x19, 0x20, 0x8f, 0x29, + 0xdc, 0x0d, 0x15, 0x5e, 0xf8, 0x6b, 0x45, 0x76, 0x09, 0x6c, 0x6b, 0xc6, 0xc7, 0x0c, 0x77, 0x10, 0xf2, 0x8f, 0x60, + 0x4b, 0xb6, 0x62, 0xb7, 0xec, 0x86, 0x21, 0xd9, 0x38, 0x0e, 0x63, 0xcc, 0xc7, 0x93, 0xf8, 0x4a, 0x4c, 0xe2, 0x01, + 0x8f, 0xd0, 0x31, 0x62, 0xcd, 0xeb, 0x59, 0x2c, 0x07, 0x90, 0x2d, 0xb9, 0xd2, 0x01, 0x21, 0x34, 0x36, 0xb4, 0xe4, + 0x55, 0x61, 0x70, 0xb1, 0x63, 0x9f, 0x91, 0x48, 0xc6, 0x21, 0x58, 0xb4, 0xaa, 0x81, 0x85, 0x89, 0xdd, 0xf2, 0x62, + 0xb6, 0x9a, 0xe3, 0x3f, 0x87, 0x03, 0x02, 0x60, 0x07, 0xfb, 0x86, 0x2d, 0x23, 0x44, 0x7a, 0xbb, 0xe1, 0x4b, 0xcb, + 0xd3, 0x85, 0xdd, 0xf1, 0x6b, 0x3e, 0x66, 0xe7, 0xaf, 0x3d, 0x88, 0x9c, 0x3d, 0xff, 0x08, 0x68, 0x88, 0x77, 0xfc, + 0x36, 0xf5, 0x2a, 0x76, 0x4b, 0x14, 0x84, 0xb7, 0xe0, 0x0c, 0x74, 0x07, 0x11, 0xb0, 0xd7, 0xfc, 0x06, 0x63, 0xc5, + 0xce, 0xd2, 0x85, 0x87, 0x19, 0xa1, 0xf6, 0x74, 0xbe, 0xac, 0xd5, 0x24, 0xdc, 0x5c, 0x2d, 0x26, 0x83, 0xc1, 0xc6, + 0xdf, 0xf1, 0x35, 0xf0, 0xc1, 0x9c, 0xbf, 0xf6, 0x76, 0x54, 0x2e, 0xfc, 0xe7, 0x75, 0x96, 0xbc, 0xf3, 0xd9, 0xf5, + 0x80, 0xdf, 0x00, 0xde, 0x12, 0x3a, 0x70, 0xdd, 0xf9, 0x4c, 0xe2, 0xb5, 0x5d, 0xeb, 0x6b, 0x04, 0x12, 0xf9, 0x02, + 0x30, 0x62, 0x62, 0x7e, 0x5f, 0x43, 0x04, 0x46, 0x0c, 0xbe, 0xad, 0xda, 0x23, 0x7e, 0xcb, 0x0d, 0xe0, 0x57, 0xe6, + 0xb3, 0x7b, 0x1e, 0xea, 0x9f, 0x89, 0xcf, 0xde, 0xf2, 0xf7, 0xfc, 0x99, 0x27, 0x25, 0xe9, 0x72, 0xf6, 0x7e, 0x0e, + 0xd7, 0x43, 0x29, 0x4f, 0x87, 0xf4, 0xb3, 0x31, 0x18, 0x40, 0x28, 0x64, 0xbe, 0xf5, 0x80, 0x35, 0x29, 0xc4, 0xbf, + 0x80, 0x6f, 0x47, 0x09, 0x9b, 0x6f, 0xbd, 0xad, 0xaf, 0xe5, 0xcd, 0xb7, 0xde, 0xbd, 0x4f, 0x51, 0x80, 0x55, 0x50, + 0xca, 0x02, 0xab, 0x20, 0x6c, 0xb4, 0x11, 0xc6, 0xc0, 0xd5, 0xbb, 0xc6, 0x50, 0xd7, 0x73, 0xc4, 0xb6, 0x95, 0xbe, + 0x0b, 0xdf, 0x41, 0x06, 0x7c, 0xf0, 0xaa, 0x28, 0x89, 0x3e, 0xa7, 0xa6, 0x48, 0x5a, 0xf7, 0xdc, 0x6f, 0xad, 0x3b, + 0x5a, 0x53, 0xea, 0x23, 0x57, 0xe3, 0xc3, 0xa1, 0x7e, 0x26, 0xb4, 0x48, 0x30, 0x05, 0x8d, 0x6b, 0xd0, 0x16, 0x20, + 0xe8, 0xf3, 0x00, 0x59, 0x4b, 0x8a, 0x05, 0xdf, 0xfe, 0x0a, 0x31, 0x78, 0x65, 0x7a, 0xe7, 0x72, 0x95, 0x91, 0xb0, + 0xbd, 0xf0, 0xcb, 0x61, 0xed, 0x4f, 0x9c, 0x5a, 0x58, 0x5a, 0xcd, 0x41, 0xfd, 0xc4, 0x96, 0xe3, 0x54, 0xd5, 0xfe, + 0x2e, 0x49, 0xaa, 0x5d, 0xa5, 0xe5, 0xf4, 0xce, 0xbe, 0xe9, 0x32, 0xc1, 0xc6, 0x7e, 0x40, 0xd5, 0x91, 0xd5, 0xb0, + 0xfb, 0x42, 0x7d, 0xd1, 0x53, 0x32, 0xa1, 0xf9, 0xa8, 0xa2, 0x79, 0x76, 0xbf, 0xd9, 0x51, 0xff, 0xe9, 0xe5, 0x50, + 0x04, 0x48, 0x56, 0x69, 0xb1, 0x14, 0x39, 0x1b, 0xfb, 0xf1, 0x30, 0xc9, 0x54, 0x78, 0x41, 0x3a, 0xba, 0xfb, 0x8d, + 0xfb, 0x5b, 0x6e, 0x20, 0x2b, 0xb4, 0x6a, 0x83, 0xb1, 0x52, 0xb4, 0x0c, 0xd6, 0x57, 0xe3, 0x7e, 0x5f, 0x5c, 0x8d, + 0xa7, 0x22, 0xa8, 0x81, 0xb8, 0x48, 0x3c, 0x1b, 0x4f, 0x6b, 0x62, 0x49, 0xed, 0x0a, 0x8c, 0xd1, 0xe3, 0xaa, 0xa8, + 0x7d, 0xea, 0x67, 0x10, 0x8a, 0x54, 0x6b, 0xe6, 0x58, 0xe3, 0xc6, 0x88, 0xb8, 0xc3, 0xca, 0xb5, 0x53, 0x7b, 0x1d, + 0x80, 0xe5, 0xd5, 0xb8, 0x20, 0x2c, 0x92, 0x63, 0xe7, 0x02, 0x56, 0xa3, 0x21, 0xd5, 0x6e, 0xb8, 0xf5, 0xb2, 0xf3, + 0x9b, 0x6f, 0x12, 0x5b, 0x1b, 0xe1, 0x96, 0x02, 0xca, 0x28, 0xbf, 0xb1, 0x9c, 0xb0, 0x3b, 0xd5, 0x3b, 0x52, 0xb5, + 0x23, 0x4e, 0x5c, 0xc0, 0x72, 0xc3, 0x53, 0xab, 0x6f, 0x62, 0x70, 0x22, 0x54, 0xad, 0x74, 0xb8, 0x93, 0x09, 0xc4, + 0xfd, 0xea, 0xbe, 0xee, 0x95, 0xe0, 0x27, 0x21, 0xaf, 0xdf, 0xf2, 0x0e, 0x00, 0x2b, 0x3e, 0xe4, 0xc5, 0xb4, 0x70, + 0xb4, 0x2e, 0x83, 0x32, 0x40, 0x84, 0x66, 0x00, 0x74, 0x72, 0x75, 0x10, 0xa5, 0x81, 0x2b, 0xee, 0x10, 0xe1, 0xa7, + 0xd1, 0x93, 0xfc, 0x59, 0xf8, 0xa4, 0x9a, 0x86, 0x17, 0x79, 0x10, 0x5d, 0x54, 0x41, 0xf4, 0xa4, 0xba, 0x0a, 0x9f, + 0xe4, 0xd3, 0xe8, 0x22, 0x0f, 0xc2, 0x8b, 0xaa, 0xb1, 0xef, 0xda, 0xdd, 0x3d, 0x21, 0x6f, 0xbb, 0xfa, 0x23, 0xe7, + 0xca, 0x9e, 0x32, 0x3d, 0x3f, 0xaf, 0xf5, 0x4a, 0xed, 0x36, 0xd7, 0x6b, 0xd4, 0x4c, 0x7d, 0x94, 0xfd, 0xcd, 0x36, + 0x16, 0x1e, 0xcd, 0x21, 0xf4, 0x19, 0x69, 0x31, 0xf7, 0x38, 0xd7, 0x9b, 0x3d, 0x29, 0x0c, 0x8c, 0x98, 0x54, 0x32, + 0x72, 0x7a, 0x81, 0x8b, 0x50, 0x85, 0x18, 0xd6, 0xd2, 0xd5, 0x3e, 0xeb, 0xd2, 0x1b, 0xa8, 0x6b, 0x8a, 0x7d, 0x0d, + 0x19, 0x78, 0xd1, 0xf4, 0x32, 0x18, 0x03, 0x72, 0x04, 0xde, 0xf1, 0xd9, 0x02, 0x0e, 0xcc, 0x35, 0x40, 0xdf, 0x3c, + 0xe8, 0xeb, 0xb2, 0xe4, 0x6b, 0xd5, 0x37, 0xd3, 0xf5, 0x48, 0x29, 0x3f, 0x56, 0x7c, 0x79, 0xf1, 0x94, 0xdd, 0x72, + 0x8d, 0x8a, 0xf2, 0x42, 0x2f, 0xd6, 0x3b, 0xe0, 0xaa, 0x7b, 0x01, 0xb7, 0x59, 0x3c, 0x76, 0xe5, 0x01, 0xcb, 0xb6, + 0xec, 0x9e, 0xbd, 0x65, 0xef, 0xd9, 0x07, 0xf6, 0x99, 0x7d, 0x65, 0x35, 0x42, 0x94, 0x97, 0x4a, 0xca, 0xf3, 0x6f, + 0xf8, 0xad, 0xb4, 0x3d, 0x4a, 0x58, 0xb2, 0x7b, 0xdb, 0x4e, 0x33, 0xdc, 0xb0, 0xf7, 0xfc, 0x66, 0xb8, 0x62, 0x9f, + 0x21, 0x1b, 0x0a, 0xc5, 0x83, 0x15, 0xab, 0xe1, 0x0a, 0x4b, 0x19, 0xf4, 0x69, 0x58, 0x5a, 0xc2, 0xa2, 0x29, 0x14, + 0xa5, 0xe8, 0xcf, 0xbc, 0x26, 0xec, 0xb4, 0x1a, 0x0b, 0x91, 0x1f, 0x1a, 0xae, 0xd8, 0x3d, 0xbf, 0x19, 0xac, 0xd8, + 0x7b, 0x6d, 0x23, 0x1a, 0x6c, 0xdc, 0xe2, 0x08, 0xcc, 0x4a, 0x17, 0x26, 0x05, 0xea, 0xad, 0x7d, 0x13, 0xdc, 0xb0, + 0xb7, 0x58, 0xbf, 0x0f, 0x58, 0x34, 0xca, 0xfc, 0x83, 0x15, 0xfb, 0xca, 0x25, 0x86, 0x9a, 0x5b, 0x9e, 0x74, 0x0c, + 0xd5, 0x05, 0xd2, 0x15, 0xe1, 0x03, 0xa7, 0x17, 0xd9, 0x57, 0x2c, 0x83, 0xbe, 0x32, 0x5c, 0xb1, 0x2d, 0xd6, 0xee, + 0xad, 0x31, 0x6e, 0x59, 0xd5, 0x93, 0xa0, 0xc0, 0x28, 0xab, 0x94, 0x96, 0x8b, 0x23, 0x96, 0x4d, 0x1d, 0x35, 0xa8, + 0x0d, 0x03, 0xfa, 0x60, 0xf4, 0x1f, 0xbe, 0x7e, 0xf7, 0x91, 0x57, 0xea, 0x9b, 0xef, 0x0b, 0xc7, 0xbb, 0xb2, 0x44, + 0xef, 0xca, 0xdf, 0x78, 0x39, 0x7b, 0x31, 0x9f, 0xe8, 0x5a, 0xd2, 0x26, 0x43, 0xee, 0xa6, 0xb3, 0x17, 0x1d, 0xfe, + 0x96, 0xbf, 0xf9, 0x7e, 0x63, 0xf5, 0xb1, 0xfa, 0xae, 0xee, 0xde, 0xfb, 0xc1, 0xa6, 0x71, 0x2a, 0xbe, 0x3b, 0x5d, + 0x71, 0x6c, 0x67, 0xad, 0xbd, 0x33, 0xff, 0x87, 0x6b, 0xbd, 0xc5, 0xb1, 0x7b, 0xcb, 0xb7, 0xc3, 0x8d, 0x3d, 0x0c, + 0xf2, 0xfb, 0xca, 0x2f, 0xbf, 0xe6, 0xcf, 0xbd, 0x4e, 0x49, 0x16, 0x50, 0x8d, 0xde, 0x18, 0x69, 0xe8, 0x92, 0x99, + 0x98, 0x86, 0xf8, 0x22, 0x03, 0x74, 0x2e, 0x10, 0xcf, 0xee, 0xf8, 0x78, 0x72, 0x77, 0x15, 0x4f, 0xee, 0x06, 0xfc, + 0x8d, 0x69, 0x41, 0x7b, 0xc1, 0xdd, 0xf9, 0xec, 0x37, 0x5e, 0xd8, 0x4b, 0xf2, 0x85, 0xcf, 0xde, 0x09, 0x77, 0x95, + 0xbe, 0xf0, 0xd9, 0x57, 0xc1, 0x7f, 0x1b, 0x69, 0xb2, 0x0c, 0xf6, 0xb5, 0xe6, 0xbf, 0x8d, 0x90, 0xf5, 0x83, 0x7d, + 0x11, 0xfc, 0x1d, 0xf8, 0x7f, 0x57, 0x09, 0x5a, 0xc6, 0xbf, 0xd4, 0xea, 0xe7, 0x7b, 0x19, 0x9b, 0x03, 0x6f, 0x42, + 0x2b, 0xe8, 0xcd, 0xdb, 0x5a, 0xfe, 0x24, 0x2e, 0x8e, 0x54, 0x3d, 0x35, 0x1c, 0xb4, 0x58, 0xcc, 0x4d, 0x7d, 0x94, + 0x4e, 0xe5, 0x4d, 0xae, 0x79, 0x22, 0x2d, 0xcc, 0x77, 0x10, 0x0e, 0x7c, 0x6d, 0xc3, 0x14, 0xec, 0x38, 0x6e, 0x06, + 0xd7, 0x0c, 0x20, 0x24, 0xb3, 0xe9, 0x96, 0xbf, 0xe5, 0x1f, 0xf8, 0x57, 0xbe, 0x0b, 0xee, 0xf9, 0x7b, 0xfe, 0x99, + 0xd7, 0x35, 0xdf, 0xb1, 0x85, 0x84, 0x3c, 0xad, 0xb7, 0x97, 0xc1, 0x96, 0xd5, 0xbb, 0xcb, 0xe0, 0x9e, 0xd5, 0xdb, + 0xa7, 0xc1, 0x5b, 0x56, 0xef, 0x9e, 0x06, 0xef, 0xd9, 0xf6, 0x32, 0xf8, 0xc0, 0x76, 0x97, 0xc1, 0x67, 0xb6, 0x7d, + 0x1a, 0x7c, 0x65, 0xbb, 0xa7, 0x41, 0xad, 0x90, 0x1e, 0xbe, 0x0a, 0xc9, 0x74, 0xf2, 0xb5, 0x66, 0x86, 0x55, 0x37, + 0xf8, 0x22, 0xac, 0x5f, 0x54, 0xcb, 0xe0, 0x4b, 0xcd, 0x74, 0x9b, 0x03, 0x21, 0x98, 0x6e, 0x71, 0x70, 0x4b, 0x4f, + 0x4c, 0xbb, 0x82, 0x54, 0xb0, 0xae, 0x96, 0x06, 0x37, 0x75, 0xd3, 0x3a, 0x99, 0x1d, 0xef, 0xc4, 0xb8, 0xc3, 0x3b, + 0xf1, 0x86, 0x2d, 0x9a, 0x4e, 0x57, 0x9d, 0xd3, 0xe7, 0x81, 0x3e, 0x02, 0xf4, 0xde, 0x5f, 0x49, 0x0f, 0x9a, 0xa2, + 0xe1, 0xb9, 0xd2, 0x1d, 0xb7, 0xf6, 0xfb, 0xd0, 0xda, 0xef, 0x99, 0x54, 0xa4, 0x45, 0x2c, 0x2a, 0x8b, 0xaa, 0x42, + 0x3e, 0xf1, 0x20, 0xd3, 0x5a, 0xb5, 0x84, 0x91, 0x3a, 0x13, 0x30, 0xe9, 0x0b, 0x3a, 0x0c, 0x72, 0xb2, 0x2b, 0xb0, + 0x05, 0xdf, 0x0c, 0x12, 0xb6, 0xe6, 0xf1, 0x74, 0x98, 0x04, 0x0b, 0xb6, 0xe4, 0xc3, 0x6e, 0xb1, 0x60, 0xa5, 0xc2, + 0x98, 0xf4, 0xf5, 0xe9, 0x68, 0x77, 0xe7, 0xbd, 0x55, 0x1a, 0xc7, 0x99, 0x40, 0x9d, 0x5b, 0xa5, 0xb7, 0xf9, 0xad, + 0xb3, 0xab, 0xaf, 0xd5, 0x2e, 0x0f, 0x02, 0xc3, 0x6f, 0x40, 0xb4, 0x43, 0xbc, 0x77, 0x50, 0x63, 0xa4, 0x5b, 0x32, + 0xeb, 0xbe, 0xb2, 0xf7, 0xf5, 0xad, 0xd9, 0xaa, 0xff, 0xdd, 0x22, 0x68, 0x2f, 0x97, 0xbd, 0xff, 0xc6, 0xbc, 0xfa, + 0x7b, 0xc7, 0xab, 0x1b, 0x7f, 0x72, 0xcf, 0xdf, 0x60, 0x74, 0x02, 0x26, 0xb2, 0x1d, 0x7f, 0x33, 0xda, 0x36, 0x4e, + 0x79, 0x72, 0x2f, 0xff, 0xbf, 0x52, 0xa0, 0xbd, 0x9b, 0x57, 0xf6, 0xa6, 0xb8, 0xe5, 0x1d, 0x7b, 0xf9, 0xc2, 0xda, + 0x13, 0x0d, 0x42, 0xc9, 0x1b, 0xee, 0x06, 0x45, 0xc3, 0x9e, 0xf8, 0x82, 0x57, 0xb3, 0x37, 0xf3, 0xc9, 0x96, 0x1f, + 0xef, 0x88, 0x6f, 0x3a, 0x76, 0xc4, 0x17, 0xfe, 0x60, 0xd1, 0x7c, 0xab, 0x57, 0x3b, 0x77, 0x72, 0xa7, 0xd2, 0x3b, + 0x7e, 0xbc, 0x8f, 0x0f, 0xff, 0xed, 0x4a, 0xef, 0xbe, 0xbb, 0xd2, 0x76, 0x95, 0xbb, 0x3b, 0xdf, 0x74, 0x7c, 0x23, + 0x6b, 0x8d, 0xe1, 0x66, 0x46, 0xc1, 0x08, 0xd3, 0x16, 0xa6, 0x69, 0x10, 0x59, 0x8a, 0x45, 0x48, 0xd4, 0x28, 0x9d, + 0x13, 0x7d, 0x16, 0x74, 0x0a, 0xba, 0xb8, 0xd1, 0xdf, 0xf2, 0x31, 0xbb, 0x31, 0x2e, 0x9b, 0xb7, 0x57, 0x37, 0x93, + 0xc1, 0xe0, 0xd6, 0xdf, 0xdf, 0xf1, 0x70, 0x76, 0x3b, 0x67, 0xd7, 0xfc, 0x8e, 0xd6, 0xd3, 0x44, 0x35, 0xbe, 0x78, + 0x48, 0x02, 0xbb, 0xf5, 0xfd, 0x89, 0x45, 0x04, 0x6b, 0xdf, 0x38, 0x6f, 0xfd, 0x81, 0x34, 0x4b, 0xcb, 0xad, 0xfd, + 0xfd, 0xc3, 0x1a, 0x8a, 0x5b, 0x10, 0x32, 0xde, 0xdb, 0x2a, 0x87, 0xcf, 0xfc, 0xa3, 0x77, 0xed, 0x4f, 0xaf, 0x75, + 0xf0, 0xcd, 0x44, 0x9d, 0x4b, 0x9f, 0x2f, 0x9e, 0xb2, 0xdf, 0xf8, 0x1b, 0x79, 0xa6, 0xbc, 0x13, 0x72, 0xda, 0x7e, + 0x42, 0x12, 0x27, 0x3a, 0x2a, 0xbe, 0xba, 0x89, 0x04, 0x0a, 0x01, 0xbb, 0xc2, 0xd7, 0x9a, 0xdf, 0x4f, 0xca, 0xa9, + 0xb7, 0x03, 0x92, 0x57, 0x6e, 0x2b, 0xa2, 0x6f, 0x39, 0xe7, 0x37, 0xc3, 0xcb, 0xe9, 0xd7, 0x6e, 0xdf, 0x1e, 0x15, + 0xd6, 0xa6, 0x22, 0xde, 0x6e, 0x31, 0x08, 0xeb, 0x64, 0x66, 0x99, 0x4b, 0xbe, 0xf4, 0xb5, 0x36, 0x73, 0x8f, 0xe9, + 0x1d, 0x67, 0x9a, 0x21, 0xa3, 0x2f, 0x30, 0x33, 0x1d, 0x0e, 0xcb, 0x73, 0x2c, 0x8f, 0x0f, 0x3f, 0x3f, 0xf9, 0x30, + 0xf8, 0x80, 0x21, 0x5c, 0x56, 0x58, 0xc8, 0x57, 0x3e, 0xcc, 0xea, 0xd6, 0xb5, 0xe3, 0xe2, 0xe9, 0xf0, 0x05, 0xe4, + 0x0d, 0xba, 0x1e, 0x9a, 0x22, 0x5a, 0xe5, 0x77, 0x14, 0x7d, 0xa2, 0xe4, 0xa0, 0xe3, 0x09, 0xd4, 0x0e, 0xb9, 0x70, + 0xbf, 0x3e, 0xe1, 0xa0, 0xe8, 0xc0, 0x52, 0xfb, 0xfd, 0xf3, 0x37, 0x44, 0x28, 0x0d, 0xe3, 0xfd, 0x22, 0x8c, 0xfe, + 0x8c, 0xcb, 0x62, 0x0d, 0x47, 0xec, 0x00, 0x3e, 0xf7, 0x44, 0x5f, 0xc3, 0x96, 0xbe, 0xef, 0x07, 0xde, 0x96, 0xbf, + 0x65, 0x5f, 0xb9, 0x77, 0x39, 0xfc, 0xec, 0x3f, 0xf9, 0x00, 0xf2, 0x13, 0xe2, 0xa4, 0x60, 0x48, 0x6c, 0x47, 0x31, + 0x6a, 0x1d, 0x7e, 0xa9, 0x21, 0x56, 0xeb, 0x0d, 0x52, 0x77, 0x41, 0xfa, 0x07, 0x85, 0xec, 0x27, 0x04, 0x56, 0x93, + 0xf4, 0x29, 0x30, 0x89, 0x6f, 0x6b, 0x48, 0x20, 0x4d, 0x0b, 0xc4, 0xe0, 0x40, 0xf1, 0xa9, 0xe0, 0x5f, 0x87, 0x5f, + 0x48, 0xfe, 0xbb, 0xa9, 0xf9, 0x18, 0xfe, 0x86, 0xa1, 0x99, 0x54, 0xf7, 0x69, 0x1d, 0x25, 0x5e, 0x0d, 0xa7, 0x5e, + 0x58, 0x09, 0x75, 0x32, 0x04, 0xa9, 0x18, 0x72, 0x21, 0x2e, 0x9e, 0x4e, 0x6e, 0x4b, 0x11, 0xfe, 0x39, 0xc1, 0x67, + 0x72, 0xa5, 0xc9, 0x67, 0xf4, 0xa4, 0x91, 0x05, 0xdc, 0xcb, 0xf7, 0x65, 0xaf, 0x06, 0x37, 0xf5, 0x90, 0xdf, 0xd6, + 0xee, 0xfb, 0x72, 0x4e, 0xd0, 0x23, 0xfb, 0x01, 0xcd, 0xc1, 0x40, 0xcd, 0x40, 0xca, 0x10, 0xdc, 0xc2, 0xa5, 0xdf, + 0x53, 0x05, 0xf9, 0xf2, 0x7b, 0x5f, 0x84, 0x0c, 0x5c, 0xb9, 0x21, 0x4c, 0xb9, 0x54, 0x48, 0x81, 0xe3, 0xb6, 0x1e, + 0x7c, 0xd1, 0xe8, 0x24, 0x12, 0x7c, 0x4a, 0x40, 0x92, 0xb4, 0x3c, 0x90, 0x34, 0x62, 0x3a, 0x10, 0x17, 0x4a, 0xd3, + 0xac, 0xa4, 0x88, 0x43, 0xec, 0xaa, 0xd7, 0x48, 0x78, 0x16, 0xbc, 0x67, 0xb0, 0x76, 0xa4, 0x68, 0xf1, 0xd5, 0x98, + 0x8e, 0x75, 0xd8, 0xd0, 0x52, 0x16, 0xf7, 0x9b, 0xa4, 0x4e, 0x23, 0x71, 0xe5, 0x9d, 0x90, 0x3f, 0xff, 0xa9, 0x44, + 0x20, 0xbd, 0xab, 0x81, 0x18, 0x04, 0x3f, 0x40, 0xff, 0x01, 0x8b, 0x1c, 0x04, 0xa5, 0xba, 0x0c, 0xf3, 0x2a, 0xa3, + 0x02, 0x67, 0x3b, 0xb6, 0x9d, 0x33, 0x55, 0xb7, 0xe0, 0x8b, 0x30, 0x0c, 0x69, 0x67, 0xab, 0xe6, 0xe4, 0x56, 0x6f, + 0xa0, 0x9e, 0x49, 0x1c, 0xa9, 0xa5, 0x38, 0xd2, 0xd6, 0xdc, 0xa7, 0x0b, 0xaf, 0x5b, 0x5e, 0xd0, 0x70, 0x01, 0x7a, + 0x51, 0xba, 0xeb, 0x7c, 0x42, 0xa1, 0xcb, 0x6a, 0x5c, 0x0d, 0x45, 0x1d, 0xca, 0x31, 0xd6, 0xfe, 0x5c, 0xc9, 0xf3, + 0x3b, 0xb0, 0x1e, 0xa1, 0xe1, 0xab, 0x52, 0x07, 0xb1, 0xfd, 0x44, 0xef, 0x3a, 0x95, 0xfa, 0x1b, 0x00, 0x06, 0x4e, + 0x1d, 0x0f, 0xf5, 0x51, 0x3b, 0x85, 0x6c, 0xe7, 0xde, 0x12, 0xa3, 0x72, 0x25, 0x3c, 0x55, 0x5a, 0x9e, 0x52, 0x56, + 0x7d, 0x2d, 0xb8, 0x95, 0xdd, 0x67, 0x03, 0xc8, 0x68, 0x83, 0x02, 0x79, 0x46, 0x6d, 0x8d, 0x07, 0xa9, 0xa6, 0x59, + 0xe2, 0x18, 0x3e, 0x28, 0xd2, 0xac, 0x02, 0x8b, 0x97, 0xb9, 0x64, 0x0e, 0x0a, 0x96, 0xeb, 0xcd, 0x66, 0x9a, 0xa9, + 0xbe, 0xc8, 0xed, 0x8d, 0xc6, 0xcb, 0xf4, 0xdf, 0x2c, 0x19, 0xf0, 0xe8, 0xe2, 0xa9, 0x1f, 0x40, 0x9a, 0xa4, 0x78, + 0x80, 0x24, 0xd8, 0x1e, 0xec, 0x62, 0x87, 0x61, 0xab, 0x58, 0xd9, 0x93, 0xa7, 0xcb, 0x1d, 0x9a, 0x72, 0x09, 0x2e, + 0x39, 0x31, 0x97, 0x53, 0xdf, 0x97, 0xac, 0x37, 0x14, 0xa7, 0x6c, 0x9a, 0x80, 0x92, 0x40, 0xbb, 0x05, 0xff, 0x85, + 0x4f, 0x0d, 0x9d, 0x16, 0x60, 0xa9, 0xed, 0x06, 0xfc, 0x17, 0xfa, 0xc5, 0x76, 0x17, 0xf5, 0x03, 0xf3, 0x60, 0x6f, + 0x16, 0x57, 0xc6, 0x80, 0x93, 0xc4, 0x95, 0xe6, 0x91, 0xeb, 0x07, 0x45, 0x9f, 0x2e, 0x6b, 0x07, 0xce, 0x14, 0x17, + 0x56, 0xa9, 0x4d, 0xd2, 0x6b, 0xbf, 0xa5, 0x26, 0xde, 0x44, 0x49, 0x55, 0xd8, 0x0e, 0x69, 0xff, 0x92, 0x72, 0xa6, + 0x8a, 0x3b, 0x44, 0x4f, 0x76, 0x13, 0x57, 0x81, 0x17, 0x56, 0x15, 0x1b, 0xa1, 0x36, 0x23, 0xcb, 0x09, 0x9c, 0xee, + 0xb1, 0xba, 0xe0, 0x63, 0xbb, 0x9a, 0x5d, 0xb0, 0x92, 0xad, 0x99, 0x74, 0x9f, 0xb7, 0x63, 0x2e, 0xe4, 0x95, 0x5e, + 0x16, 0xad, 0x80, 0xf6, 0x20, 0x70, 0xf8, 0x85, 0xa6, 0x7b, 0xf4, 0x6c, 0xb3, 0x4d, 0x6d, 0x36, 0xb6, 0x16, 0x21, + 0x64, 0x20, 0x1a, 0xfa, 0x42, 0xce, 0x28, 0xf2, 0x55, 0x5a, 0xae, 0xd5, 0xc6, 0x2a, 0xe3, 0x05, 0x26, 0x82, 0x0c, + 0x67, 0xe1, 0x1d, 0x7a, 0x5a, 0x8f, 0x34, 0xc5, 0x24, 0x38, 0xe9, 0xe2, 0x2f, 0xc0, 0x86, 0xf2, 0x24, 0x37, 0x07, + 0xe4, 0x00, 0x2a, 0x97, 0xa2, 0x54, 0xca, 0xe0, 0x37, 0xea, 0x8e, 0x6c, 0xab, 0xfe, 0x3b, 0x0d, 0x64, 0x70, 0x07, + 0xfa, 0xb6, 0x17, 0x5a, 0x3b, 0xda, 0xb9, 0xb2, 0x35, 0x6d, 0x8b, 0x34, 0x8f, 0x91, 0xc5, 0x06, 0x90, 0x4f, 0xa4, + 0x73, 0x20, 0xf2, 0x9a, 0x68, 0xbc, 0xb3, 0x67, 0x7c, 0x3c, 0x15, 0x0f, 0xc9, 0x7b, 0x95, 0xef, 0x9b, 0x7b, 0x7d, + 0x30, 0xc6, 0xbe, 0x05, 0x65, 0xe2, 0x83, 0xd5, 0xd6, 0xba, 0xc4, 0x7a, 0xab, 0x34, 0x89, 0x6e, 0xb8, 0x82, 0x8e, + 0x23, 0x71, 0x83, 0x18, 0x1c, 0x33, 0x5e, 0x5b, 0x65, 0xe9, 0x2b, 0x2c, 0x73, 0x1d, 0xb3, 0x64, 0xc8, 0xa4, 0xce, + 0x13, 0x05, 0x4f, 0x7e, 0x9e, 0x90, 0x8c, 0x88, 0x9a, 0x6d, 0x39, 0x4a, 0xb9, 0x69, 0x01, 0x97, 0x19, 0x19, 0xc0, + 0x37, 0x69, 0x02, 0x50, 0x2e, 0x5f, 0x82, 0x54, 0x1a, 0x22, 0xb8, 0x66, 0x7b, 0xc9, 0xe8, 0xd6, 0xd1, 0x3a, 0xa8, + 0x92, 0xcc, 0x1d, 0x9c, 0xdb, 0x59, 0xa4, 0xd4, 0x9b, 0x8f, 0x30, 0xec, 0xe4, 0x43, 0x58, 0x27, 0xf8, 0x6d, 0x40, + 0x4d, 0xfa, 0x5c, 0x78, 0xd1, 0x08, 0xd0, 0xd4, 0x77, 0xaa, 0x8c, 0xcf, 0x85, 0x97, 0x8d, 0xb6, 0x2c, 0xa3, 0x14, + 0xaa, 0x0b, 0x66, 0xb7, 0xa6, 0x0b, 0x31, 0xaf, 0xaa, 0x81, 0x36, 0xc8, 0xed, 0x3a, 0x66, 0x40, 0xa3, 0xb6, 0x2b, + 0x8f, 0x2c, 0xc0, 0xad, 0x99, 0x08, 0x8c, 0x9c, 0x7f, 0x9f, 0x5f, 0xab, 0x70, 0x9e, 0x7e, 0x3f, 0xf4, 0xf6, 0xdb, + 0x20, 0x1a, 0x6d, 0x2f, 0xd9, 0x2e, 0x88, 0x46, 0xbb, 0xcb, 0x86, 0xd1, 0xef, 0xa7, 0xf4, 0xfb, 0x69, 0x03, 0xaa, + 0x12, 0x61, 0x22, 0xee, 0xf5, 0x1b, 0xb5, 0x7c, 0xa5, 0xd6, 0xef, 0xd4, 0xf2, 0xa5, 0x1a, 0xde, 0xda, 0x93, 0x48, + 0x10, 0x59, 0x1a, 0x9b, 0x7b, 0xc9, 0x96, 0x6a, 0xa9, 0x74, 0x8c, 0x2a, 0x23, 0x6a, 0xe9, 0x6c, 0x8e, 0x15, 0x23, + 0xed, 0x1c, 0x94, 0x0c, 0xc8, 0xb4, 0xb8, 0xaa, 0x31, 0xdd, 0xac, 0x68, 0x89, 0xc9, 0x08, 0x2b, 0xdb, 0xf2, 0x76, + 0x93, 0xaa, 0xe9, 0x9c, 0xdc, 0xdc, 0x2a, 0xe5, 0xe6, 0x56, 0xf0, 0xfc, 0x1b, 0xba, 0xe5, 0x92, 0x6b, 0x2f, 0xb3, + 0x69, 0xa1, 0x74, 0xcb, 0xb8, 0x06, 0x5b, 0xfb, 0x26, 0x90, 0x65, 0x3e, 0x50, 0xd4, 0xd8, 0x5e, 0x34, 0xca, 0x37, + 0xc8, 0x56, 0xc4, 0xa8, 0x53, 0x16, 0x8c, 0xbf, 0xdd, 0xd1, 0x03, 0x19, 0xa8, 0xaa, 0x6a, 0xe3, 0xe0, 0xce, 0x4a, + 0x7f, 0x58, 0x5e, 0x3c, 0x65, 0x89, 0x95, 0x4e, 0x2e, 0x54, 0xa1, 0x3f, 0x08, 0xd1, 0x4d, 0x65, 0xc3, 0xc1, 0xa1, + 0x2e, 0xb6, 0x32, 0x20, 0xf4, 0x30, 0xbd, 0xb7, 0xb1, 0x92, 0xe5, 0xae, 0x29, 0x5f, 0xcc, 0x78, 0xc2, 0x71, 0xf4, + 0xe5, 0x6a, 0x11, 0xd6, 0x6a, 0x91, 0x9d, 0x00, 0x0f, 0xad, 0xd5, 0x52, 0xc8, 0xd5, 0x22, 0x9c, 0x99, 0x2e, 0xd4, + 0x4c, 0xcf, 0x40, 0x01, 0x29, 0xd4, 0x2c, 0x4f, 0x00, 0x16, 0x5e, 0x98, 0x19, 0x2e, 0xcc, 0x0c, 0xc7, 0x21, 0x35, + 0xfe, 0x0f, 0x7a, 0xaf, 0x73, 0xcf, 0x2d, 0x77, 0xa3, 0xd3, 0x88, 0x6f, 0x47, 0x1b, 0xcc, 0xf1, 0x41, 0x38, 0xa9, + 0xfa, 0xfd, 0xb4, 0x44, 0xac, 0x1e, 0x03, 0x23, 0x28, 0x87, 0xca, 0xd1, 0x7e, 0x59, 0x58, 0x92, 0x25, 0x61, 0x49, + 0xee, 0xd5, 0x38, 0x97, 0x96, 0x8b, 0x57, 0x49, 0x20, 0x12, 0x19, 0x2f, 0xa5, 0x09, 0x3e, 0xe1, 0xe5, 0xc8, 0x48, + 0xcd, 0x93, 0x9b, 0xd4, 0xcb, 0x59, 0xc6, 0xc6, 0x88, 0x61, 0x14, 0xfa, 0x4d, 0xd5, 0xef, 0xe7, 0xa5, 0x97, 0x53, + 0x3b, 0x3f, 0x83, 0xeb, 0xe5, 0xa9, 0xb3, 0xc8, 0x11, 0xf2, 0x6a, 0x24, 0x15, 0x96, 0xd7, 0x4a, 0x3d, 0x7d, 0x09, + 0x3e, 0xa8, 0xbb, 0x37, 0x0a, 0x80, 0xb8, 0xc8, 0xa5, 0x7f, 0x6d, 0x09, 0x97, 0xa6, 0xdc, 0xc0, 0xa0, 0x87, 0x3c, + 0x27, 0x21, 0x54, 0x82, 0x90, 0x14, 0xd6, 0x8d, 0xfb, 0xe2, 0xe9, 0xc4, 0x75, 0x67, 0xb1, 0x81, 0x09, 0x0e, 0x07, + 0x40, 0x3c, 0x98, 0x7a, 0xd1, 0x80, 0x97, 0x6a, 0xce, 0x7c, 0xf4, 0x72, 0x82, 0xc9, 0x00, 0x55, 0xc5, 0xc0, 0x29, + 0xeb, 0x89, 0x7c, 0x64, 0xdc, 0xcc, 0x7c, 0x3f, 0xc0, 0x77, 0xeb, 0x42, 0xa2, 0x3f, 0x28, 0x80, 0x82, 0x4c, 0x01, + 0x14, 0x24, 0x06, 0xa0, 0x20, 0x36, 0x00, 0x05, 0x9b, 0x86, 0x2f, 0xa5, 0x0e, 0x37, 0x02, 0xba, 0x08, 0x1f, 0x7a, + 0x16, 0x36, 0x56, 0x28, 0x9e, 0x8d, 0xd9, 0x98, 0x15, 0x6a, 0xe7, 0xc9, 0xe5, 0x54, 0xec, 0x2c, 0xc6, 0xba, 0x8a, + 0xac, 0x13, 0x2f, 0x24, 0x14, 0x39, 0xe7, 0x46, 0xa2, 0xee, 0x7e, 0xee, 0xbd, 0x24, 0x63, 0xc9, 0xbc, 0xa1, 0x51, + 0x83, 0x79, 0xd9, 0x75, 0x00, 0xd3, 0x92, 0x6f, 0x0b, 0x1a, 0x4c, 0xa7, 0xca, 0x23, 0xd2, 0x24, 0xa8, 0x9d, 0xcb, + 0xa4, 0xc8, 0x09, 0x61, 0x12, 0xf4, 0x4a, 0xf0, 0x1b, 0x89, 0xf2, 0xff, 0x4d, 0x27, 0x78, 0x80, 0x63, 0xa2, 0x55, + 0xf2, 0x15, 0x0c, 0x98, 0x39, 0x7f, 0x2e, 0x9d, 0xb2, 0x11, 0x8a, 0xb1, 0x4c, 0xe3, 0xd1, 0x57, 0x36, 0x44, 0x68, + 0xab, 0xe7, 0x68, 0x62, 0x82, 0x3a, 0xc0, 0x23, 0xfa, 0x6b, 0xf4, 0xd5, 0x50, 0xa8, 0x74, 0x35, 0x52, 0xd7, 0xec, + 0x9c, 0xf3, 0x77, 0xb5, 0xe1, 0x44, 0xc6, 0xb4, 0x29, 0xf0, 0x0d, 0x08, 0xe4, 0x1b, 0x08, 0x00, 0x57, 0x4d, 0x67, + 0xf6, 0x0a, 0xe0, 0x1c, 0x08, 0xe0, 0x71, 0xde, 0xf1, 0xf8, 0x81, 0xfe, 0x2a, 0x8e, 0x7b, 0xa7, 0x69, 0xd8, 0xfe, + 0x2b, 0x30, 0x16, 0x43, 0x39, 0x9e, 0xef, 0x14, 0x24, 0x7b, 0x94, 0xb2, 0x74, 0xd5, 0x44, 0x76, 0x28, 0xd6, 0xa7, + 0x39, 0x65, 0x2c, 0x6d, 0xcb, 0x31, 0xda, 0x78, 0xfd, 0x10, 0x8f, 0x6f, 0x6e, 0xf4, 0xe4, 0x83, 0x1e, 0xdc, 0xde, + 0x5e, 0xbf, 0xec, 0x31, 0x9b, 0x6f, 0xc5, 0xe2, 0x59, 0x11, 0x27, 0x4e, 0xeb, 0x90, 0x03, 0x1c, 0xe4, 0x24, 0x04, + 0xd2, 0x31, 0x2e, 0xb5, 0xe8, 0xa0, 0x66, 0x39, 0xaf, 0x81, 0x65, 0x16, 0x41, 0x36, 0x40, 0x54, 0xd3, 0x54, 0xac, + 0x86, 0x07, 0xa5, 0x6a, 0x4e, 0xa9, 0xd4, 0xbe, 0xe1, 0x6c, 0x75, 0xfa, 0xc4, 0xaa, 0x4d, 0xb8, 0xf5, 0x6f, 0xb5, + 0x27, 0x68, 0x2b, 0x69, 0x20, 0xd4, 0xf3, 0x65, 0xba, 0xa4, 0x28, 0x1e, 0x67, 0x26, 0x9e, 0xaa, 0xc0, 0xd8, 0xb7, + 0x76, 0x04, 0x05, 0x49, 0xd3, 0x75, 0xc0, 0x61, 0x1a, 0x9d, 0xb0, 0xf8, 0xa7, 0xf4, 0xa1, 0xbc, 0xa8, 0x15, 0x38, + 0xc9, 0x3f, 0x84, 0x8b, 0x48, 0x62, 0xa1, 0x5f, 0x12, 0x00, 0x89, 0x0c, 0x5e, 0x8d, 0x8a, 0xb5, 0x50, 0x01, 0x72, + 0x8a, 0xd2, 0x5b, 0xc5, 0xc7, 0xa5, 0x28, 0x55, 0x4a, 0x65, 0x6e, 0x54, 0x0a, 0x08, 0x6b, 0x03, 0x47, 0x17, 0xf0, + 0x05, 0x04, 0xad, 0xe5, 0x6e, 0x6d, 0x7b, 0xde, 0xc8, 0x7c, 0x66, 0x9a, 0xa7, 0xd5, 0x07, 0xf5, 0xf7, 0xfb, 0x05, + 0x86, 0xd9, 0x78, 0xfa, 0xfb, 0x36, 0x43, 0xb8, 0xf9, 0x1b, 0x86, 0x68, 0x09, 0xe0, 0x98, 0xa5, 0x3d, 0x14, 0xb2, + 0x60, 0x82, 0x35, 0x54, 0xe5, 0x29, 0x9f, 0xbd, 0x7c, 0x72, 0x03, 0x68, 0x6a, 0xe8, 0xe2, 0x46, 0xa7, 0xba, 0x2a, + 0x41, 0xf8, 0xbe, 0x2b, 0xd4, 0x63, 0x73, 0xc0, 0xa9, 0x01, 0xa0, 0x58, 0xe4, 0xb5, 0x1e, 0xdb, 0x3f, 0xe8, 0x8d, + 0x7a, 0x03, 0xc4, 0xd3, 0x39, 0x2f, 0xfc, 0x23, 0xfa, 0x75, 0xea, 0xcf, 0xb8, 0x10, 0x44, 0xbd, 0x9e, 0x84, 0x77, + 0xe2, 0x2c, 0x8d, 0x83, 0xb3, 0xde, 0xc0, 0x5c, 0x04, 0x8a, 0xb3, 0x34, 0x3f, 0x03, 0xb1, 0x1c, 0xe1, 0x11, 0x6b, + 0xb6, 0x02, 0xc4, 0xc0, 0x52, 0x87, 0x24, 0xab, 0x8e, 0xed, 0xf7, 0x5f, 0x8d, 0x0c, 0x6f, 0x3a, 0x22, 0xc2, 0xe8, + 0xdf, 0x15, 0x08, 0x50, 0xb0, 0xcc, 0x6c, 0x67, 0x26, 0x5d, 0xed, 0x59, 0x3d, 0x6f, 0x36, 0x79, 0x57, 0xef, 0x58, + 0x4d, 0xcb, 0xa9, 0x69, 0x95, 0xd5, 0xb4, 0x49, 0x0e, 0x35, 0x13, 0xfd, 0xbe, 0xc6, 0x47, 0xcd, 0xe7, 0x80, 0xcb, + 0x86, 0xc9, 0xaf, 0x66, 0xd5, 0xbc, 0xdf, 0xf7, 0xe4, 0x23, 0xf8, 0x85, 0xc4, 0x65, 0x6e, 0x8d, 0xe5, 0xd3, 0xd7, + 0xc4, 0x67, 0x66, 0x10, 0x8f, 0x56, 0x47, 0x50, 0x5f, 0x9f, 0x84, 0xd7, 0x31, 0x57, 0xd8, 0x4c, 0x4c, 0x5f, 0xc1, + 0xe0, 0x79, 0xc2, 0x07, 0x6f, 0x39, 0xfa, 0x1b, 0xe9, 0xcc, 0x14, 0x2c, 0xe4, 0xdc, 0x9f, 0xbc, 0x42, 0xe8, 0x64, + 0x44, 0x7a, 0xd0, 0xe9, 0x04, 0x0d, 0xd9, 0xef, 0xdf, 0x42, 0x67, 0xb6, 0x52, 0x29, 0x5b, 0x15, 0x95, 0xe9, 0xba, + 0x2e, 0xca, 0x0a, 0x3a, 0x96, 0x7e, 0xde, 0x0a, 0x99, 0x59, 0x3f, 0xb3, 0x90, 0x9f, 0x6e, 0x25, 0xd6, 0x94, 0x6d, + 0x9f, 0xa8, 0x0d, 0xd2, 0xac, 0x0b, 0xd5, 0x05, 0xce, 0x9d, 0xb5, 0xd7, 0x1b, 0xa1, 0xfe, 0x39, 0x1f, 0xad, 0x8b, + 0xb5, 0x07, 0x2e, 0x31, 0xb3, 0x74, 0xae, 0x38, 0x34, 0x72, 0x7f, 0xf4, 0xa5, 0x48, 0x73, 0xca, 0x03, 0x34, 0x88, + 0x62, 0x6e, 0xbf, 0x05, 0xd2, 0x0f, 0xbd, 0x05, 0xb2, 0x8f, 0xce, 0x39, 0x79, 0x05, 0xe0, 0x74, 0x88, 0x88, 0x5b, + 0x91, 0xa0, 0x63, 0xd5, 0xf0, 0xc6, 0xc2, 0x3d, 0xed, 0xa5, 0x71, 0x2f, 0xcd, 0xcf, 0xd2, 0x7e, 0xdf, 0x00, 0x68, + 0xa6, 0x88, 0x0c, 0x8f, 0x33, 0x72, 0x97, 0xb4, 0x10, 0x4c, 0x69, 0xff, 0xd5, 0x18, 0x12, 0x04, 0x02, 0xfe, 0x0f, + 0xe1, 0x7d, 0x00, 0xb4, 0x4d, 0xda, 0x80, 0xab, 0x1e, 0xd3, 0x81, 0xd9, 0x92, 0xb3, 0x55, 0x67, 0x03, 0x50, 0x4e, + 0x95, 0xd6, 0x53, 0x1e, 0xd7, 0x14, 0x11, 0xa9, 0xb2, 0x50, 0xbf, 0xb1, 0x9e, 0x4c, 0x56, 0xb9, 0xc8, 0x90, 0xa3, + 0x32, 0xbd, 0xab, 0x19, 0x21, 0x76, 0xe9, 0xe7, 0x37, 0xb0, 0x64, 0xe3, 0x8f, 0x38, 0x79, 0x4b, 0x80, 0xb4, 0x9d, + 0xb5, 0xab, 0x6a, 0x97, 0xe3, 0xd6, 0x6e, 0x0e, 0x48, 0xbe, 0xde, 0x68, 0x34, 0xd2, 0x7e, 0x72, 0x02, 0x86, 0xaa, + 0xa7, 0x96, 0x42, 0x8f, 0xd5, 0x0a, 0x5b, 0xb7, 0x23, 0x97, 0x59, 0x32, 0x98, 0x2f, 0x8c, 0xe3, 0x6b, 0xf3, 0xd1, + 0x87, 0x4b, 0x65, 0xed, 0x3a, 0xe2, 0xeb, 0x3f, 0xca, 0x6a, 0x7d, 0xcf, 0xbb, 0xaa, 0x09, 0xf8, 0xa2, 0x8a, 0x2d, + 0xfd, 0x8e, 0xf7, 0x64, 0xef, 0xe2, 0x6b, 0x9f, 0xb0, 0x4b, 0xbe, 0xe7, 0x2d, 0xea, 0x3c, 0x5f, 0xf9, 0xba, 0x51, + 0xa5, 0xdb, 0x7b, 0xc9, 0x0d, 0xae, 0xbd, 0xa3, 0xa6, 0xb1, 0x9e, 0xf9, 0xd1, 0xc3, 0x22, 0x64, 0x3b, 0x1f, 0x7a, + 0x5f, 0x35, 0x4f, 0xcf, 0x1a, 0x7a, 0x93, 0x1a, 0xfa, 0xd0, 0x8b, 0xb2, 0x7d, 0x6a, 0x1a, 0xd1, 0x6b, 0xd8, 0xd0, + 0x87, 0xde, 0x92, 0x93, 0x43, 0x82, 0xc1, 0xa9, 0x31, 0x7f, 0x78, 0x38, 0x9d, 0xe1, 0xef, 0x18, 0x50, 0x89, 0xc9, + 0x7c, 0x7a, 0x4c, 0x3b, 0x0a, 0x30, 0xa3, 0x4a, 0x6f, 0x9f, 0x1e, 0xd8, 0x8e, 0x97, 0xf5, 0xd0, 0xd2, 0xbb, 0x27, + 0x47, 0xb7, 0xe3, 0x55, 0x35, 0xbe, 0x94, 0x43, 0x9e, 0xe7, 0xb3, 0xd1, 0x68, 0x24, 0x0c, 0x3a, 0x77, 0xa5, 0x37, + 0xb0, 0x02, 0x19, 0x5c, 0x54, 0x1f, 0xca, 0xa5, 0xb7, 0x53, 0x87, 0x76, 0xe5, 0x4f, 0xf2, 0xc3, 0xa1, 0x18, 0x99, + 0x63, 0x1c, 0x70, 0x4e, 0x0a, 0x25, 0x47, 0xc9, 0x5a, 0x82, 0xe8, 0x94, 0xc6, 0x53, 0x59, 0xaf, 0xad, 0x88, 0xbc, + 0x1a, 0x21, 0x1f, 0x82, 0x9f, 0x3c, 0x50, 0x8b, 0x3f, 0xd3, 0x82, 0xd8, 0x43, 0x9f, 0x2a, 0xa5, 0x43, 0xbc, 0x2a, + 0x20, 0x44, 0x18, 0xf0, 0x06, 0xda, 0x41, 0x09, 0x0e, 0x3b, 0xdc, 0x23, 0x44, 0x88, 0x7e, 0xe9, 0xe5, 0x33, 0x19, + 0xae, 0xdc, 0x1b, 0x54, 0x73, 0x06, 0x88, 0x95, 0x3e, 0x03, 0x17, 0x4c, 0x40, 0x3d, 0xc5, 0xa7, 0xe8, 0x5f, 0x6f, + 0x1e, 0x36, 0x5d, 0x9f, 0x96, 0x80, 0x8a, 0xe8, 0xd9, 0xcf, 0xc7, 0x00, 0xde, 0xd9, 0xb5, 0x19, 0x69, 0x2f, 0x7f, + 0x03, 0x0c, 0x2b, 0x25, 0x89, 0x76, 0x4e, 0x89, 0xc0, 0x9d, 0x8f, 0x6c, 0xe9, 0x47, 0x29, 0x10, 0x73, 0xc7, 0x93, + 0x44, 0xf6, 0x60, 0x23, 0x27, 0x70, 0x8b, 0x01, 0x8f, 0x0e, 0x40, 0xe5, 0x4a, 0x41, 0xee, 0x35, 0x47, 0x72, 0xc7, + 0x0f, 0xbd, 0x1f, 0x06, 0xf5, 0xe0, 0x87, 0xde, 0x59, 0x4a, 0x72, 0x47, 0x78, 0xa6, 0xa6, 0x84, 0x88, 0xcf, 0x7e, + 0x18, 0xe4, 0x03, 0x3c, 0x4b, 0xb4, 0x48, 0x8b, 0xdc, 0x6a, 0xa2, 0xc6, 0x4d, 0x78, 0x97, 0x48, 0x1a, 0xa2, 0x6d, + 0xe7, 0x11, 0x71, 0x03, 0x20, 0x59, 0x7c, 0x36, 0x6f, 0x28, 0xea, 0xdd, 0x84, 0x6f, 0xd1, 0x5d, 0x16, 0xfb, 0xfd, + 0x75, 0x9e, 0xd6, 0x3d, 0x1d, 0x2a, 0x83, 0x2f, 0x48, 0x35, 0x01, 0x1e, 0xed, 0x2f, 0xcd, 0xf1, 0xea, 0xd5, 0xe6, + 0x48, 0xb9, 0x51, 0x25, 0xea, 0xb7, 0x58, 0xcd, 0x7a, 0x88, 0xc8, 0x9d, 0x65, 0xc6, 0xde, 0x5e, 0xf0, 0x4a, 0xce, + 0xaa, 0xd8, 0x2e, 0xc7, 0x57, 0x84, 0xb5, 0x95, 0x04, 0xe8, 0x68, 0x3d, 0xd6, 0xa6, 0x18, 0xf9, 0x95, 0x42, 0x02, + 0x2e, 0x3a, 0xb6, 0x16, 0x8a, 0x8d, 0x17, 0xa0, 0x2f, 0xd9, 0x99, 0x06, 0x58, 0x6f, 0xf4, 0x2a, 0xe2, 0xb6, 0x7c, + 0xa0, 0xc2, 0x9b, 0xdc, 0x54, 0x99, 0x95, 0xcd, 0x4d, 0xbb, 0x9f, 0x2a, 0x5e, 0x21, 0x6e, 0xbd, 0x51, 0x7b, 0x14, + 0xa0, 0xf6, 0xd0, 0x42, 0x19, 0xa0, 0x4b, 0xd3, 0x0c, 0x00, 0x19, 0x00, 0x64, 0xaa, 0x88, 0xcf, 0x04, 0xa8, 0xb4, + 0xd5, 0x8d, 0x02, 0x27, 0xd2, 0x4b, 0x60, 0x5c, 0x60, 0xa5, 0x8f, 0x6c, 0x64, 0xb0, 0xd8, 0x22, 0xc0, 0x2d, 0x47, + 0xfa, 0x30, 0x0d, 0x27, 0xdb, 0x68, 0x0e, 0x93, 0x34, 0xbf, 0x0b, 0xb3, 0x54, 0x42, 0x4b, 0xbc, 0x96, 0x35, 0x46, + 0x2c, 0x20, 0x7d, 0x9f, 0xbe, 0x29, 0xb2, 0x98, 0x20, 0xe1, 0xac, 0xa7, 0x0e, 0xa0, 0x9a, 0x9c, 0x6b, 0x4d, 0xab, + 0x67, 0xb5, 0xc9, 0x43, 0x16, 0xe8, 0xec, 0xc1, 0x98, 0xd4, 0x72, 0x43, 0x8f, 0xec, 0xaf, 0x1c, 0xcf, 0x08, 0xdf, + 0xf5, 0x0c, 0xa7, 0xfe, 0xfb, 0x54, 0x03, 0x29, 0x53, 0x02, 0x08, 0x32, 0x38, 0x9a, 0x10, 0xca, 0xd3, 0x31, 0x99, + 0xda, 0xfc, 0x08, 0x84, 0x23, 0x82, 0x57, 0xf0, 0xdc, 0xd0, 0xba, 0xe5, 0xc6, 0xce, 0x22, 0x4f, 0x13, 0x40, 0x16, + 0x2f, 0xf8, 0x1d, 0x20, 0x73, 0xea, 0x55, 0x21, 0x7b, 0xf6, 0x5c, 0x4c, 0x67, 0xf3, 0xe0, 0xcf, 0x84, 0xf6, 0x2f, + 0x26, 0xfc, 0xa6, 0xbb, 0x4a, 0xae, 0x4c, 0xad, 0x7b, 0x13, 0x3d, 0xe6, 0x72, 0xa7, 0x4f, 0x2b, 0x8e, 0x11, 0xcf, + 0x60, 0x15, 0x90, 0x73, 0x36, 0xe4, 0xcf, 0xce, 0x01, 0xbb, 0x65, 0x25, 0xbc, 0x88, 0x3f, 0x0b, 0x65, 0xb5, 0x00, + 0xf9, 0x91, 0xf3, 0xc8, 0xfc, 0xf2, 0xd5, 0x76, 0x28, 0xe7, 0x14, 0x45, 0xb4, 0x9c, 0x9a, 0x96, 0x14, 0xb2, 0x43, + 0x4f, 0xc1, 0x64, 0x6a, 0xcb, 0xdf, 0x77, 0x89, 0x4b, 0xf2, 0xcd, 0x24, 0xb2, 0xaf, 0x03, 0xac, 0x59, 0xab, 0xee, + 0xa1, 0x1b, 0x82, 0x01, 0x22, 0x23, 0x94, 0xd9, 0x5c, 0xdf, 0xad, 0x07, 0x03, 0x05, 0xf3, 0x2b, 0xe8, 0xa6, 0x45, + 0xa7, 0x38, 0x40, 0xce, 0x5a, 0xd7, 0xa8, 0x54, 0x15, 0x87, 0x0e, 0xf3, 0x6e, 0x59, 0x95, 0x5d, 0x96, 0x5e, 0x08, + 0x52, 0xa3, 0xae, 0x82, 0x45, 0x4a, 0x45, 0x14, 0xef, 0xc9, 0xaf, 0x81, 0x89, 0x67, 0x56, 0x8e, 0xd2, 0x78, 0x0e, + 0x88, 0x41, 0x0a, 0x88, 0x53, 0x7e, 0x05, 0x68, 0xa2, 0x8b, 0x28, 0xcc, 0x5e, 0xc7, 0x55, 0x50, 0x5b, 0x4d, 0xbf, + 0x77, 0x20, 0x63, 0xcf, 0xeb, 0x7e, 0x3f, 0x25, 0x46, 0x3f, 0x8c, 0xc2, 0xc0, 0xbf, 0xc7, 0xd3, 0x7d, 0x13, 0xa4, + 0xe6, 0x95, 0x3f, 0xe1, 0x15, 0x5d, 0x6e, 0x6d, 0xca, 0x15, 0x8d, 0x0b, 0x7f, 0x8d, 0xe0, 0xf0, 0xa9, 0xa3, 0xd8, + 0x6e, 0x53, 0xe5, 0xd4, 0xc6, 0x60, 0x10, 0xc2, 0x7d, 0x2b, 0xe3, 0xf7, 0x89, 0x97, 0xcf, 0xa2, 0x39, 0x28, 0x4a, + 0x33, 0xcd, 0x17, 0x52, 0x48, 0x37, 0x01, 0xfa, 0x68, 0x10, 0x6a, 0x75, 0xe5, 0xa7, 0xc4, 0x4b, 0xd5, 0xb4, 0x36, + 0x4f, 0xb1, 0x46, 0x81, 0x98, 0x45, 0xf3, 0x86, 0x65, 0x74, 0x48, 0xaa, 0xcb, 0xa5, 0x69, 0xc6, 0x27, 0xab, 0x19, + 0xaa, 0x15, 0x47, 0x4d, 0x50, 0xa3, 0xf4, 0x09, 0x2e, 0x80, 0x3f, 0xd3, 0x1d, 0x47, 0x35, 0x8a, 0x14, 0x0d, 0xf8, + 0x04, 0x31, 0x62, 0xcd, 0xe6, 0x09, 0x6b, 0x4d, 0x5d, 0x33, 0xfa, 0x7d, 0x19, 0x32, 0x64, 0x92, 0x90, 0xa7, 0x0f, + 0x97, 0xeb, 0x47, 0x52, 0x5d, 0x00, 0xbf, 0x72, 0xc5, 0x66, 0xbd, 0xde, 0x1c, 0xe0, 0x7a, 0x61, 0xfd, 0xc2, 0xc6, + 0x15, 0x9c, 0x5f, 0x12, 0xfc, 0xae, 0xfa, 0x11, 0x66, 0x19, 0x54, 0x01, 0x19, 0x7f, 0x2c, 0x14, 0xf5, 0xbc, 0xc5, + 0xec, 0x3e, 0x52, 0x17, 0x94, 0x59, 0x3a, 0xb7, 0x38, 0x41, 0xc0, 0x79, 0x58, 0x3d, 0x81, 0x64, 0x5f, 0x3e, 0xf6, + 0x69, 0x46, 0x81, 0xea, 0x08, 0xf0, 0xd9, 0xac, 0x1f, 0xc2, 0xfe, 0x01, 0x91, 0x85, 0xfa, 0x9b, 0x6f, 0xe5, 0xac, + 0x21, 0x79, 0x20, 0xd5, 0xdc, 0xc7, 0x70, 0x6a, 0xdc, 0xe0, 0x4b, 0x37, 0xbd, 0xa9, 0xe0, 0x35, 0x21, 0x73, 0xdf, + 0xa0, 0xb5, 0xef, 0x06, 0x8e, 0x10, 0xc1, 0x65, 0x94, 0xe2, 0xb4, 0xb7, 0xeb, 0x05, 0xc8, 0x6d, 0x6e, 0x41, 0x5e, + 0x5f, 0xbb, 0xf8, 0xc5, 0x29, 0xd2, 0xb3, 0xe8, 0x02, 0x03, 0x5d, 0x90, 0x79, 0xe3, 0x9f, 0x15, 0xac, 0x5c, 0x40, + 0xef, 0xa5, 0x62, 0x25, 0x27, 0xdb, 0x4e, 0xfd, 0x51, 0x2a, 0xfb, 0xed, 0x99, 0x35, 0x81, 0xdf, 0x27, 0xf6, 0x4b, + 0x64, 0xf2, 0x4d, 0x8f, 0x4d, 0xbe, 0x32, 0x2c, 0x3a, 0xb5, 0x0c, 0xce, 0xe9, 0x91, 0xc1, 0xb9, 0xb7, 0xb3, 0x6a, + 0x13, 0xc2, 0x50, 0x90, 0x04, 0x9a, 0x2e, 0x3c, 0xac, 0x9b, 0xfe, 0xfc, 0xa4, 0x45, 0xb5, 0x55, 0xfb, 0xd6, 0xfd, + 0x38, 0xc4, 0x2e, 0x7e, 0x9f, 0x78, 0x86, 0x88, 0xd4, 0x07, 0x3a, 0x30, 0x19, 0x3c, 0x71, 0xd9, 0xef, 0x43, 0x61, + 0xb3, 0xf1, 0x7c, 0x54, 0x17, 0x6f, 0x8a, 0x7b, 0x40, 0x75, 0xa8, 0xc0, 0x2e, 0x87, 0x32, 0x94, 0x11, 0x9b, 0xda, + 0x72, 0xcf, 0x1f, 0xd7, 0x61, 0x0e, 0xf2, 0x8e, 0x86, 0xc7, 0x39, 0x03, 0x31, 0x0c, 0xbe, 0xfe, 0xc3, 0xa3, 0x7d, + 0xda, 0xfc, 0x70, 0x06, 0xdf, 0x1d, 0x9d, 0x7d, 0x40, 0xba, 0x9b, 0xb3, 0x75, 0x59, 0xdc, 0xa5, 0xb1, 0x38, 0xfb, + 0x01, 0x52, 0x7f, 0x38, 0x2b, 0xca, 0xb3, 0x1f, 0x54, 0x65, 0x7e, 0x38, 0xa3, 0x05, 0x37, 0xfa, 0xc3, 0x9a, 0x78, + 0xbf, 0x57, 0x9a, 0x01, 0x6d, 0x01, 0x91, 0x59, 0x5a, 0xfd, 0x08, 0x4a, 0x44, 0xc5, 0x8f, 0x2a, 0xa3, 0x5a, 0xad, + 0x1d, 0xe7, 0x51, 0xa2, 0x91, 0xb2, 0x69, 0x42, 0xe2, 0x6a, 0x09, 0xeb, 0x50, 0xcf, 0x4e, 0x9b, 0x6f, 0xc7, 0x79, + 0xa0, 0x0e, 0x88, 0x9c, 0x3f, 0xcb, 0x47, 0x5b, 0xfa, 0x1a, 0x7c, 0xeb, 0x70, 0xc8, 0x47, 0x3b, 0xf3, 0xd3, 0x27, + 0x6b, 0xa5, 0x8c, 0x3b, 0x52, 0xe4, 0x42, 0xc8, 0x19, 0xb7, 0xed, 0x31, 0xe0, 0x00, 0xf0, 0x0f, 0x07, 0xfa, 0xbd, + 0x93, 0xbf, 0xd5, 0x6e, 0x69, 0xd5, 0xf3, 0x43, 0x8b, 0x3b, 0xe3, 0x75, 0x6d, 0x88, 0xda, 0xf6, 0x12, 0x5b, 0x7a, + 0xdf, 0x34, 0xa8, 0x29, 0xa2, 0x9f, 0xb0, 0x9a, 0x58, 0xc5, 0x61, 0x41, 0x4a, 0x48, 0x62, 0x38, 0x46, 0x3b, 0xf4, + 0x38, 0x5d, 0x2c, 0x3d, 0xb9, 0xef, 0xf0, 0x72, 0xeb, 0xfb, 0x80, 0xa4, 0x55, 0x38, 0x7f, 0xe4, 0x85, 0x06, 0x1e, + 0xbd, 0xc8, 0xab, 0x22, 0x13, 0x23, 0x41, 0xa3, 0xfc, 0x9a, 0xc4, 0x99, 0x33, 0xac, 0xc5, 0x99, 0x02, 0x0b, 0x0b, + 0x09, 0xdd, 0xbb, 0x28, 0x29, 0x3d, 0x38, 0x7b, 0xb4, 0x2f, 0x9b, 0x3f, 0x08, 0x1e, 0x62, 0x74, 0x03, 0x8c, 0x38, + 0xbb, 0x76, 0x79, 0xf7, 0x61, 0x99, 0x7b, 0x7f, 0xbc, 0x5e, 0xe6, 0x05, 0x84, 0x68, 0x9e, 0x49, 0xc5, 0x6a, 0x79, + 0x06, 0x8c, 0x79, 0x22, 0x3e, 0x0b, 0x2b, 0x39, 0x0d, 0xaa, 0x8e, 0x62, 0xf5, 0x36, 0x9e, 0x7b, 0x40, 0xf1, 0xfd, + 0x28, 0x01, 0x2e, 0x77, 0x9f, 0xbd, 0x52, 0xae, 0xa9, 0xa4, 0x47, 0x9e, 0x43, 0xb4, 0xe4, 0x75, 0x02, 0x14, 0xcf, + 0x10, 0x27, 0x29, 0xac, 0x9e, 0x9b, 0x20, 0x15, 0xf9, 0xfa, 0x84, 0xe2, 0x8b, 0xe6, 0x51, 0xd4, 0xb0, 0x90, 0x25, + 0x70, 0x3c, 0x24, 0xb3, 0x6c, 0x8e, 0x2c, 0xe5, 0x69, 0x7b, 0x8a, 0x74, 0x74, 0x62, 0x89, 0xdf, 0xd6, 0xfc, 0x7a, + 0x91, 0x8a, 0xc0, 0xa4, 0x9d, 0xad, 0xcc, 0xbd, 0x10, 0x86, 0x2a, 0xe1, 0xde, 0xeb, 0x7a, 0x16, 0xca, 0x4d, 0xd1, + 0xaa, 0x98, 0x3d, 0x4c, 0x89, 0x19, 0xa6, 0x58, 0x7f, 0x61, 0xc3, 0xdf, 0x26, 0x5e, 0x0c, 0x86, 0xeb, 0x05, 0x2f, + 0x67, 0x1b, 0xb3, 0x10, 0x0e, 0x87, 0xcd, 0xa4, 0x98, 0x2d, 0x20, 0xcc, 0x75, 0x31, 0x3f, 0x1c, 0xba, 0x5a, 0xb6, + 0x16, 0x1e, 0x3c, 0x54, 0x2d, 0xdc, 0x34, 0x2c, 0x87, 0x9f, 0xc9, 0x2c, 0xc6, 0xf6, 0x35, 0x3e, 0xb3, 0x3f, 0x5f, + 0x74, 0xcf, 0x12, 0x24, 0xdf, 0x58, 0x03, 0xed, 0xd8, 0xac, 0xdd, 0xe1, 0x6a, 0x04, 0x24, 0xa5, 0xbb, 0xd1, 0xdf, + 0x95, 0x9d, 0x3c, 0x25, 0xc8, 0x1d, 0xad, 0xc0, 0x7e, 0xf7, 0x8d, 0x3f, 0xd1, 0x62, 0x0f, 0xda, 0x6d, 0x6c, 0x09, + 0x51, 0x4d, 0x7b, 0x2e, 0x57, 0x8a, 0xa5, 0x79, 0x2b, 0x6d, 0xf4, 0x7c, 0x58, 0x9f, 0xfb, 0x46, 0x0e, 0x14, 0x8c, + 0x11, 0x4f, 0xad, 0x83, 0x68, 0x36, 0x07, 0x1a, 0x0c, 0x34, 0x8f, 0xf0, 0xd4, 0x42, 0x07, 0x65, 0xd6, 0x86, 0xfd, + 0x3c, 0x39, 0x59, 0x1e, 0x87, 0x6f, 0xe1, 0x5f, 0x3e, 0xc3, 0x26, 0x31, 0xc5, 0xf6, 0xf8, 0x57, 0xa5, 0xa8, 0xf0, + 0xd8, 0x8e, 0xb8, 0xd6, 0x3e, 0x89, 0xda, 0x50, 0x39, 0xfc, 0x4b, 0xd8, 0x47, 0xd8, 0x5f, 0x68, 0x82, 0x30, 0xd8, + 0xf5, 0x67, 0x02, 0x21, 0x62, 0x21, 0x5e, 0xf0, 0xaf, 0x4a, 0x52, 0xd1, 0x09, 0x9f, 0xed, 0x4a, 0xe0, 0xad, 0xc3, + 0x80, 0x3e, 0x21, 0x3f, 0x13, 0x09, 0x43, 0x33, 0xa1, 0x77, 0xf4, 0xdf, 0x89, 0x9d, 0x6c, 0x92, 0x5b, 0x21, 0x1f, + 0x48, 0x2a, 0x09, 0x26, 0x58, 0x79, 0xa1, 0xfc, 0xd1, 0xbd, 0x50, 0x6a, 0xad, 0x05, 0xad, 0x5f, 0xfe, 0x3c, 0xf1, + 0x0c, 0xfe, 0x1e, 0xc8, 0x18, 0x74, 0x1b, 0x51, 0x4d, 0x72, 0x4c, 0x1f, 0xa5, 0xf3, 0x0c, 0x54, 0x40, 0x67, 0xeb, + 0x2c, 0xac, 0x17, 0x45, 0xb9, 0x6a, 0x45, 0x8a, 0xca, 0xd2, 0x47, 0xea, 0x31, 0xe6, 0x85, 0x79, 0x72, 0x22, 0x1f, + 0x3c, 0x02, 0x60, 0x3c, 0xca, 0xd3, 0xaa, 0xa3, 0xb4, 0x7e, 0x60, 0x19, 0x30, 0x02, 0x27, 0xca, 0x80, 0x47, 0x58, + 0x06, 0xe6, 0x69, 0x97, 0xa1, 0x06, 0xb1, 0x46, 0xd5, 0x95, 0xda, 0x60, 0x4e, 0x14, 0x25, 0x9f, 0x62, 0x69, 0x85, + 0x31, 0x34, 0x75, 0xe5, 0x91, 0xf5, 0x92, 0x13, 0xf6, 0x64, 0x37, 0x90, 0x6e, 0x61, 0xa3, 0x70, 0x06, 0x5d, 0xcb, + 0x12, 0xe5, 0xa2, 0x5b, 0x46, 0x94, 0x89, 0x90, 0xfa, 0xd9, 0xc3, 0x99, 0x56, 0xfb, 0x8d, 0x9d, 0xb4, 0x6f, 0x8f, + 0x14, 0xbd, 0x60, 0xd0, 0x3e, 0xed, 0x91, 0x52, 0xcf, 0x1a, 0xb9, 0x0c, 0x6c, 0xe9, 0x52, 0xd5, 0xf3, 0x5f, 0xa0, + 0x7c, 0x07, 0x33, 0xe3, 0x6c, 0xf6, 0x87, 0xde, 0xdc, 0x1e, 0xed, 0xeb, 0xe6, 0x0f, 0xd6, 0xeb, 0xc1, 0xd6, 0x20, + 0x13, 0x9f, 0x2b, 0x16, 0x2a, 0xab, 0x10, 0x2b, 0x48, 0xfb, 0x5f, 0xc2, 0xfb, 0x03, 0xde, 0x1a, 0xa1, 0x59, 0x19, + 0x0f, 0xf3, 0xd1, 0xa3, 0xbd, 0x68, 0xfe, 0xe8, 0x2c, 0xdb, 0xca, 0x55, 0xc9, 0x6c, 0x7f, 0x1c, 0x25, 0xcd, 0xd9, + 0xc3, 0x35, 0x92, 0x3a, 0xc0, 0x87, 0xeb, 0x33, 0x7c, 0xa0, 0x12, 0x4a, 0x2d, 0xa8, 0x6a, 0xd0, 0xfa, 0xd8, 0x1f, + 0xad, 0xe7, 0xf4, 0xf1, 0x63, 0x39, 0xdd, 0x92, 0x22, 0x8c, 0x1f, 0x18, 0x4c, 0xd9, 0x89, 0x53, 0x97, 0xbc, 0x19, + 0xd2, 0xbb, 0x6e, 0x95, 0xd4, 0x65, 0x8f, 0x12, 0x41, 0xa8, 0x83, 0xf5, 0x8b, 0xfd, 0x10, 0x66, 0xb6, 0xe8, 0x0f, + 0x9b, 0xd5, 0x9c, 0x00, 0x11, 0x01, 0xad, 0x55, 0xde, 0x07, 0x8e, 0xf9, 0xc2, 0xac, 0xb9, 0x21, 0xdd, 0x7a, 0x73, + 0xa5, 0xbd, 0x92, 0x02, 0xfa, 0x39, 0xc8, 0xdc, 0x3e, 0xba, 0xe5, 0xaa, 0x65, 0x9e, 0x4b, 0x5b, 0x0e, 0x58, 0xb4, + 0x10, 0xa8, 0xd9, 0xb9, 0x74, 0x38, 0x50, 0x10, 0xea, 0x4a, 0x54, 0x11, 0x57, 0x47, 0xd1, 0x42, 0xd4, 0x6a, 0xd5, + 0x2e, 0x27, 0x9b, 0x0a, 0xd9, 0x92, 0x08, 0x32, 0x4a, 0x31, 0x74, 0xe9, 0xa3, 0x5c, 0xed, 0x99, 0x86, 0x03, 0x34, + 0x01, 0x9b, 0x36, 0xf8, 0x5b, 0xe0, 0x5e, 0x06, 0x67, 0xa6, 0x7d, 0x1a, 0x46, 0xc0, 0x69, 0x0e, 0x31, 0x7f, 0x7e, + 0xd7, 0x83, 0x0a, 0x1e, 0x74, 0xa4, 0xbf, 0xae, 0x67, 0x05, 0x9e, 0xb9, 0x27, 0x9e, 0xbf, 0x3a, 0x91, 0x5e, 0xe4, + 0xf0, 0x40, 0xd3, 0x20, 0x66, 0xfc, 0x79, 0x59, 0x86, 0xbb, 0xd1, 0xa2, 0x2c, 0x56, 0x5e, 0xa4, 0xf7, 0xf1, 0x4c, + 0x8a, 0x81, 0xc4, 0x8c, 0x99, 0xd1, 0x55, 0xac, 0xe3, 0x1c, 0xc6, 0xbd, 0x3d, 0x09, 0x2b, 0xb4, 0x7f, 0x96, 0xd8, + 0xeb, 0x02, 0xb0, 0x1c, 0xb2, 0x06, 0xad, 0xf0, 0x4e, 0xb7, 0xb7, 0x7b, 0x5c, 0xb2, 0xa3, 0xb8, 0x01, 0xf4, 0xb3, + 0x1a, 0x5a, 0x26, 0xa8, 0x65, 0xd6, 0x9d, 0x4c, 0xa6, 0x48, 0x2e, 0xdf, 0x86, 0xbd, 0x62, 0x45, 0x3e, 0x6f, 0xe4, + 0xf6, 0xf0, 0x2e, 0x5c, 0x89, 0x58, 0x5b, 0xd0, 0x49, 0x47, 0xc6, 0xe1, 0x5e, 0x68, 0x6e, 0xa4, 0xfb, 0x47, 0x55, + 0x12, 0x96, 0x22, 0x86, 0x5b, 0x20, 0xdb, 0xab, 0x6d, 0x25, 0x28, 0x81, 0x0f, 0xf6, 0x43, 0x29, 0x16, 0xe9, 0x56, + 0x00, 0xae, 0x03, 0xff, 0x4d, 0x22, 0x12, 0xba, 0x3b, 0x0f, 0x51, 0xac, 0x91, 0xf7, 0x0d, 0xa2, 0xb1, 0xbf, 0x04, + 0x39, 0x0d, 0xc8, 0x44, 0x8a, 0x91, 0x2c, 0x18, 0xf8, 0x00, 0x72, 0xbe, 0x06, 0x93, 0xdc, 0x34, 0xf7, 0xfc, 0x20, + 0xd7, 0x1d, 0x4c, 0xfb, 0xa0, 0x7b, 0x71, 0xad, 0x59, 0x0e, 0x5e, 0x31, 0x11, 0xff, 0xb9, 0xf6, 0x4a, 0x96, 0xb3, + 0xcc, 0x6f, 0xcc, 0x45, 0x27, 0x83, 0xab, 0x86, 0xf0, 0x8b, 0x59, 0x36, 0xe7, 0xd1, 0x2c, 0xd3, 0x51, 0xff, 0x45, + 0x73, 0x54, 0x0a, 0xc0, 0xa9, 0xe3, 0x05, 0x58, 0x43, 0x5f, 0xe9, 0xa6, 0x15, 0x0f, 0x34, 0xc6, 0x28, 0xa8, 0xd0, + 0x41, 0xe8, 0xe7, 0x1a, 0x90, 0x36, 0x98, 0xa4, 0x49, 0xa8, 0x7c, 0x70, 0x41, 0x37, 0xcc, 0xcb, 0x95, 0xcb, 0x55, + 0x93, 0xaa, 0xe5, 0x97, 0x23, 0xea, 0xbb, 0x5a, 0x72, 0xa9, 0x36, 0x9f, 0x1a, 0x65, 0x8d, 0x20, 0x93, 0xa3, 0xf4, + 0xfb, 0x94, 0x0b, 0xb7, 0x32, 0x26, 0xeb, 0xc3, 0xc1, 0x2b, 0xb8, 0xa9, 0xf1, 0xeb, 0x9c, 0x08, 0x45, 0xed, 0x21, + 0x11, 0xb6, 0x76, 0x2b, 0x74, 0xef, 0x71, 0xa3, 0x34, 0x8f, 0xb2, 0x4d, 0x2c, 0x2a, 0xaf, 0x97, 0x80, 0xb5, 0xb8, + 0x07, 0xbc, 0xa8, 0xb4, 0xf4, 0x2b, 0x56, 0x00, 0x7a, 0x80, 0x14, 0x36, 0x5e, 0x23, 0x03, 0xd6, 0x23, 0x2f, 0xf5, + 0xfb, 0x7d, 0x63, 0xca, 0x7f, 0x7f, 0x9f, 0x03, 0x49, 0xa1, 0x28, 0xeb, 0x1d, 0x4c, 0x20, 0xb8, 0x76, 0x92, 0xf6, + 0xac, 0xe6, 0xcf, 0xd6, 0xb5, 0x07, 0xfc, 0x56, 0xbe, 0x45, 0x62, 0xf5, 0xd2, 0xbe, 0xd8, 0xec, 0xd3, 0xea, 0x93, + 0xd1, 0x38, 0x08, 0x96, 0x56, 0xaf, 0xb5, 0xca, 0x21, 0x6f, 0x78, 0x01, 0x22, 0x95, 0x75, 0x75, 0xad, 0x9c, 0xab, + 0x6b, 0xc1, 0x91, 0x4b, 0xb6, 0xe4, 0x39, 0xfc, 0x17, 0x72, 0xaf, 0x3c, 0x1c, 0x0a, 0xbf, 0xdf, 0x4f, 0x67, 0xa4, + 0x95, 0x05, 0xf6, 0xb4, 0x75, 0xed, 0x85, 0xfe, 0xe1, 0xf0, 0x1a, 0xbc, 0x46, 0xfc, 0xc3, 0xa1, 0xec, 0xf7, 0x3f, + 0x9a, 0x9b, 0xcc, 0xf9, 0x58, 0x29, 0x65, 0x2f, 0x51, 0xe9, 0xfe, 0x39, 0xe1, 0xbd, 0xff, 0x3d, 0xfa, 0xdf, 0xa3, + 0xcb, 0x9e, 0x0a, 0x01, 0x4b, 0xf8, 0x0c, 0x6f, 0xe8, 0x4c, 0x5d, 0xce, 0x99, 0x74, 0x77, 0x57, 0x7e, 0xe8, 0x3d, + 0x0d, 0x15, 0xdf, 0x9b, 0x9b, 0x36, 0xfe, 0x5c, 0x1d, 0x69, 0x12, 0x3a, 0x2e, 0xfa, 0x87, 0xc3, 0x9b, 0x44, 0xeb, + 0xd3, 0x52, 0xa5, 0x4f, 0x53, 0x38, 0x4a, 0x86, 0xdc, 0xcd, 0x2d, 0x4c, 0x07, 0xf6, 0xe3, 0xe6, 0xab, 0xe4, 0xc5, + 0x59, 0x0a, 0xd7, 0xde, 0x7c, 0x96, 0xce, 0xa7, 0x60, 0x5d, 0x19, 0xe6, 0xb3, 0x7a, 0x1e, 0x40, 0xea, 0x10, 0xd2, + 0xac, 0x69, 0xf8, 0x8f, 0xca, 0x15, 0xbc, 0xb5, 0xc7, 0xbb, 0x81, 0x8b, 0x52, 0x47, 0xfa, 0xa4, 0x8d, 0xa6, 0x4b, + 0x2a, 0xf9, 0x8f, 0x22, 0x8f, 0x31, 0x66, 0xe3, 0x25, 0xf1, 0x7e, 0x16, 0xf9, 0x75, 0x01, 0xd8, 0x45, 0x00, 0x86, + 0x9c, 0xce, 0x1d, 0x49, 0xfc, 0x63, 0xf2, 0xfd, 0x1f, 0xd3, 0xa5, 0x7d, 0x28, 0x8b, 0x65, 0x29, 0xaa, 0xea, 0xa8, + 0xb4, 0xad, 0x2d, 0xd7, 0x03, 0x93, 0x68, 0xbf, 0x2f, 0x99, 0x44, 0x53, 0x0c, 0x45, 0x81, 0x5b, 0x63, 0x6f, 0x9a, + 0x72, 0xc5, 0x58, 0x3d, 0x32, 0xd6, 0xcf, 0x17, 0xbb, 0xd7, 0xb1, 0x97, 0xfa, 0x41, 0x0a, 0x82, 0xb0, 0x86, 0x52, + 0x4a, 0x91, 0x0f, 0xce, 0x67, 0x98, 0x4a, 0xd4, 0xba, 0x94, 0x2a, 0x7f, 0x18, 0x69, 0x3e, 0x4c, 0x41, 0x2f, 0xfb, + 0xaf, 0x0a, 0xe6, 0xbf, 0x6e, 0x0f, 0xd6, 0xa7, 0x75, 0x99, 0x46, 0x15, 0x51, 0xe5, 0x85, 0xa9, 0x36, 0x81, 0x08, + 0xfe, 0x4c, 0x58, 0x7c, 0xbf, 0x3e, 0x39, 0x12, 0x34, 0x66, 0xb2, 0xbc, 0x3a, 0x72, 0xbf, 0xb0, 0xaf, 0x5c, 0xc7, + 0xf3, 0x3f, 0x37, 0xf3, 0x7f, 0x80, 0xce, 0x90, 0xc5, 0x33, 0x6e, 0x19, 0x2c, 0x70, 0xf6, 0x4b, 0x57, 0x0f, 0xf8, + 0x9b, 0x79, 0xe2, 0x19, 0xd0, 0x31, 0x3f, 0x43, 0x57, 0xc5, 0x74, 0x56, 0x0c, 0x80, 0xcb, 0xd6, 0x6f, 0xac, 0x39, + 0xf1, 0xce, 0xa2, 0xbc, 0x92, 0x0b, 0x42, 0x5f, 0x57, 0x61, 0x36, 0xae, 0x8a, 0x4d, 0x25, 0x8a, 0x4d, 0xdd, 0x23, + 0xb5, 0x6c, 0x3e, 0xad, 0x6d, 0x85, 0xec, 0xdf, 0x45, 0x8b, 0xc1, 0xcb, 0xb0, 0x4e, 0x46, 0x59, 0xba, 0x9e, 0x02, + 0xbf, 0x5e, 0x00, 0x67, 0x91, 0x79, 0xe5, 0xab, 0xb3, 0x07, 0x6c, 0xd1, 0x78, 0x0a, 0xe4, 0xa8, 0xf4, 0x47, 0xde, + 0x18, 0x9d, 0x9e, 0xe8, 0xf7, 0xf3, 0x29, 0xc5, 0x7c, 0xfd, 0x1d, 0xe0, 0xb9, 0x6a, 0xb9, 0x00, 0x7d, 0x19, 0xea, + 0xa0, 0x12, 0xa5, 0x56, 0x0c, 0x23, 0x16, 0xfe, 0x2e, 0x90, 0xc8, 0x99, 0x02, 0x9b, 0x55, 0x94, 0x84, 0x4a, 0x54, + 0x4a, 0xb6, 0x26, 0xa8, 0xa5, 0xf7, 0x45, 0x59, 0xef, 0x2b, 0x70, 0x94, 0x8c, 0xb4, 0x59, 0x4e, 0x9a, 0x71, 0x05, + 0xca, 0x5c, 0xf4, 0x83, 0xfd, 0xbd, 0xf2, 0xfc, 0x46, 0xe6, 0xb3, 0xdc, 0x77, 0x74, 0x4e, 0xdb, 0x71, 0x81, 0x32, + 0xb7, 0x9c, 0xb6, 0x5a, 0xf2, 0x98, 0xbc, 0x67, 0xa1, 0xb6, 0x2c, 0x41, 0x8a, 0x45, 0x98, 0x4f, 0xa8, 0xb2, 0xf9, + 0x17, 0x84, 0xda, 0xe2, 0xc0, 0x1e, 0xbb, 0x30, 0x11, 0xff, 0x2d, 0x58, 0x12, 0xc3, 0xac, 0x14, 0x61, 0xbc, 0x03, + 0xef, 0x9f, 0x4d, 0x25, 0x46, 0x67, 0xe8, 0xe4, 0x7e, 0x76, 0x9f, 0xd6, 0xc9, 0xd9, 0xeb, 0x97, 0x67, 0x3f, 0xf4, + 0x06, 0xc5, 0x28, 0x8d, 0x07, 0xbd, 0x1f, 0xce, 0x56, 0x1b, 0x40, 0xcb, 0x14, 0x67, 0x31, 0x99, 0xd2, 0x44, 0x7c, + 0x46, 0x86, 0xc1, 0xb3, 0x3a, 0x11, 0x67, 0x34, 0x31, 0xdd, 0xd7, 0x28, 0x4d, 0xbe, 0x1d, 0x85, 0x39, 0xbc, 0x5c, + 0x8a, 0x4d, 0x25, 0x62, 0xb0, 0x53, 0xaa, 0x79, 0x96, 0xb7, 0xcf, 0xe2, 0x7c, 0xd4, 0x21, 0xab, 0x74, 0xe0, 0x6f, + 0x4f, 0xa4, 0x5d, 0x95, 0xae, 0x80, 0xd0, 0x03, 0xe0, 0xa4, 0x2b, 0x7f, 0x1e, 0x0e, 0x69, 0x02, 0xa1, 0x16, 0xcc, + 0xc9, 0x34, 0xa2, 0x1b, 0xd2, 0x35, 0xf6, 0x19, 0x98, 0x85, 0x94, 0xe6, 0xc1, 0xcd, 0xd5, 0x62, 0xe8, 0xae, 0x58, + 0x39, 0x0a, 0xab, 0xb5, 0x88, 0x6a, 0x64, 0x3d, 0x06, 0xe7, 0x1d, 0x88, 0x00, 0x50, 0xe4, 0xe0, 0x19, 0x8f, 0xfa, + 0xfd, 0x48, 0x05, 0xe5, 0x24, 0xf4, 0x8b, 0x42, 0xbf, 0x34, 0x1c, 0x65, 0xcc, 0xbf, 0x84, 0x9a, 0x23, 0xa0, 0xde, + 0xf2, 0x50, 0xd1, 0x05, 0xe0, 0x72, 0x8e, 0x98, 0x71, 0xde, 0xe3, 0x2e, 0x30, 0xe7, 0xff, 0xe1, 0xed, 0x4b, 0xbc, + 0xdb, 0xb6, 0xb1, 0x7e, 0xff, 0x15, 0x8b, 0x2f, 0x55, 0x89, 0x08, 0x92, 0x25, 0x27, 0xe9, 0x4c, 0x29, 0xc3, 0xfa, + 0xdc, 0x2c, 0x6d, 0x3a, 0xcd, 0xd2, 0x24, 0x5d, 0x66, 0xf4, 0xf4, 0xb9, 0x34, 0x09, 0x5b, 0x6c, 0x68, 0x40, 0x25, + 0x29, 0x2f, 0x91, 0xf8, 0xbf, 0xbf, 0x73, 0x2f, 0x56, 0x52, 0x94, 0x93, 0x99, 0xf7, 0xbd, 0x77, 0x72, 0x4e, 0x2c, + 0x82, 0x20, 0x76, 0x5c, 0x5c, 0xdc, 0xe5, 0x77, 0x4d, 0x08, 0x0a, 0x73, 0x1d, 0x2c, 0x0c, 0x00, 0xbd, 0x6b, 0x8f, + 0xb6, 0x9c, 0x74, 0x09, 0x16, 0xcf, 0x0d, 0x2c, 0x5e, 0x5d, 0x2c, 0xaa, 0x2b, 0xae, 0xe5, 0x16, 0x36, 0xa5, 0xac, + 0x62, 0x08, 0x20, 0xd0, 0x8c, 0x19, 0x76, 0xcb, 0x5d, 0x8e, 0x64, 0x5d, 0x14, 0x5c, 0xec, 0x04, 0x86, 0x6e, 0xc6, + 0x25, 0x33, 0x07, 0x57, 0x33, 0xac, 0x93, 0x8a, 0x02, 0xec, 0xea, 0x02, 0x64, 0x2f, 0x0c, 0x75, 0xdd, 0xcc, 0x96, + 0xeb, 0xc0, 0xd7, 0xa5, 0x0b, 0x5f, 0x52, 0xf0, 0x72, 0x25, 0x45, 0x99, 0x5d, 0xf3, 0x9f, 0xec, 0xcb, 0x66, 0x2c, + 0x29, 0xb4, 0x23, 0x7d, 0xd5, 0xee, 0x8e, 0x16, 0xe3, 0xd8, 0x72, 0x7c, 0x4b, 0xa5, 0x5b, 0x3d, 0xaa, 0x5e, 0x08, + 0x6d, 0x9d, 0x6b, 0x99, 0xa5, 0x29, 0x17, 0x2f, 0x45, 0x9a, 0x25, 0x5e, 0x72, 0xac, 0x63, 0x55, 0xbb, 0x20, 0x58, + 0x2e, 0x4c, 0xf2, 0xb3, 0xac, 0xc4, 0xd8, 0xc1, 0x8d, 0x46, 0xb5, 0xa2, 0x4e, 0x99, 0x18, 0x18, 0xf2, 0x1d, 0x06, + 0xdf, 0x66, 0x32, 0x01, 0x86, 0x1f, 0x13, 0xf5, 0x25, 0x3d, 0x85, 0x80, 0x0f, 0x2a, 0x34, 0xf7, 0x33, 0x8e, 0xe0, + 0xd7, 0x56, 0x65, 0x0e, 0x4c, 0xb6, 0x56, 0x41, 0x22, 0xee, 0x5d, 0x36, 0xd7, 0x8b, 0x68, 0xa1, 0xee, 0x42, 0xbd, + 0x78, 0xbb, 0xed, 0x25, 0x8a, 0x0e, 0x38, 0xf9, 0x69, 0xf0, 0x22, 0xce, 0x72, 0x9e, 0x1e, 0x54, 0xf2, 0x40, 0x6d, + 0xa8, 0x03, 0xe5, 0xcc, 0x01, 0x3b, 0xef, 0xeb, 0xea, 0x40, 0xaf, 0xe9, 0x03, 0xdd, 0xce, 0x03, 0xb8, 0x60, 0xe0, + 0xce, 0xbd, 0xcc, 0xae, 0xb9, 0x38, 0x00, 0x65, 0xa0, 0x35, 0x1e, 0xa8, 0xcb, 0x6a, 0xa4, 0x26, 0x46, 0xc7, 0xb0, + 0x4e, 0xf4, 0xc1, 0x1c, 0xd0, 0x9f, 0x21, 0xac, 0x7d, 0xeb, 0xed, 0x4a, 0x1f, 0xb4, 0x01, 0x7d, 0xb7, 0x34, 0x7d, + 0xd0, 0x81, 0xe3, 0x55, 0x74, 0xe0, 0xc6, 0x90, 0x6a, 0xd0, 0x56, 0x23, 0xab, 0x40, 0xf1, 0x86, 0xb7, 0x78, 0x77, + 0xae, 0x25, 0x1b, 0xef, 0x25, 0x62, 0x7c, 0x65, 0xa2, 0x8a, 0x33, 0x71, 0xea, 0xa5, 0xf2, 0x5a, 0x3b, 0xc9, 0x08, + 0xe3, 0x5b, 0x56, 0x52, 0x7f, 0x87, 0x98, 0x5b, 0xa4, 0x39, 0x0c, 0x5e, 0x86, 0x15, 0x99, 0xf1, 0x7e, 0x5f, 0xce, + 0x64, 0x54, 0xce, 0xc4, 0x61, 0x19, 0x29, 0xb0, 0xb6, 0x7d, 0x22, 0xa0, 0x7b, 0x25, 0x40, 0xbe, 0x00, 0xa8, 0xba, + 0x4f, 0xf8, 0x73, 0x9f, 0xd4, 0xa7, 0x53, 0xe8, 0x53, 0x68, 0xeb, 0x15, 0x57, 0x10, 0xaf, 0xea, 0xc6, 0xc8, 0x36, + 0x2a, 0x68, 0xf1, 0x58, 0x9e, 0xd5, 0x86, 0xb1, 0x39, 0xb5, 0xfe, 0xf5, 0x66, 0x83, 0x29, 0x9b, 0x0b, 0xb5, 0x0a, + 0x43, 0x12, 0x7d, 0x2c, 0xbd, 0x48, 0x22, 0x16, 0x36, 0xab, 0xb5, 0xf9, 0x4d, 0x18, 0x90, 0x4c, 0xa4, 0xb8, 0x9f, + 0x2d, 0x71, 0xee, 0xe2, 0xf1, 0xbc, 0xea, 0x6b, 0x2d, 0x2d, 0x32, 0x6d, 0xbe, 0xd5, 0x97, 0x21, 0x4d, 0x45, 0x0d, + 0x69, 0xd4, 0x99, 0x41, 0xf7, 0xed, 0xf2, 0x96, 0xd5, 0x08, 0x13, 0xe0, 0x95, 0xce, 0xa0, 0x1b, 0x8d, 0x07, 0x62, + 0x59, 0x8d, 0x8a, 0xb5, 0x10, 0x08, 0x3c, 0x0c, 0x39, 0x66, 0x96, 0x90, 0x64, 0x9f, 0xf8, 0x77, 0x2a, 0xce, 0x42, + 0x11, 0xdf, 0x18, 0x64, 0xef, 0xca, 0xba, 0x76, 0xd7, 0x91, 0x9f, 0x13, 0x0b, 0xab, 0xfd, 0x87, 0xe6, 0x51, 0x6b, + 0x9c, 0x05, 0xb4, 0x35, 0xad, 0x6e, 0x38, 0xdc, 0xa3, 0x3a, 0x16, 0xa5, 0xc1, 0x26, 0xf6, 0xc8, 0x72, 0xd1, 0x3a, + 0x66, 0xd0, 0x80, 0xfe, 0x36, 0xbb, 0x5a, 0x5f, 0x21, 0x80, 0x5b, 0x89, 0xac, 0x93, 0x54, 0xfe, 0x25, 0xed, 0x51, + 0xd7, 0xf6, 0x54, 0xfe, 0xb7, 0x6d, 0xaa, 0x1c, 0x5a, 0x4c, 0x79, 0xec, 0xe6, 0x2c, 0x50, 0x1d, 0x09, 0xa2, 0x40, + 0x6d, 0xbd, 0x60, 0xea, 0x9d, 0x32, 0x45, 0x07, 0x08, 0x74, 0x61, 0xce, 0xb0, 0x2f, 0x38, 0x62, 0xcc, 0x52, 0x89, + 0xc1, 0xd4, 0xc7, 0x18, 0xd5, 0xb4, 0x56, 0x80, 0xae, 0x9f, 0x6e, 0xe0, 0x4f, 0x54, 0xd4, 0x68, 0xa8, 0x35, 0x92, + 0x42, 0xd1, 0x44, 0x85, 0x22, 0x4b, 0x0b, 0x1d, 0x57, 0xa1, 0x93, 0x48, 0x58, 0x02, 0x1a, 0x26, 0x44, 0x27, 0x15, + 0x78, 0x6b, 0x00, 0x67, 0x3e, 0x2e, 0xca, 0x75, 0xa1, 0x0d, 0xe6, 0x7e, 0x88, 0xaf, 0xf9, 0xcb, 0x67, 0xce, 0xa8, + 0xbe, 0x65, 0xad, 0xef, 0x69, 0x41, 0x7e, 0x08, 0x39, 0x45, 0x07, 0x26, 0x76, 0xb2, 0x41, 0x63, 0x8c, 0xb2, 0xd6, + 0x51, 0x2f, 0xde, 0xe8, 0x50, 0x2c, 0xda, 0x04, 0xef, 0x1e, 0x4f, 0x11, 0x6d, 0x78, 0x28, 0x8c, 0x55, 0x35, 0x3e, + 0x95, 0xac, 0xa5, 0x07, 0x2b, 0x78, 0xba, 0x4e, 0x78, 0x08, 0x7a, 0x24, 0xc2, 0x4e, 0xc2, 0x62, 0x1e, 0x2f, 0xe0, + 0x38, 0x29, 0x08, 0xa8, 0x1d, 0xf4, 0x15, 0x7c, 0xbe, 0x40, 0xf7, 0x57, 0x89, 0x1e, 0x60, 0x68, 0x41, 0xdc, 0x8c, + 0x82, 0x3a, 0xba, 0x8a, 0x57, 0x0d, 0x15, 0x09, 0x9f, 0x17, 0x60, 0x3b, 0xa4, 0xd4, 0x53, 0xa0, 0x85, 0x4a, 0x94, + 0x7e, 0x18, 0xf8, 0x0e, 0x8d, 0x81, 0xad, 0x75, 0x80, 0x86, 0x7e, 0xc6, 0x34, 0xb5, 0xce, 0x50, 0xf9, 0xcc, 0xbb, + 0x67, 0x46, 0xcb, 0x99, 0x45, 0x63, 0xd0, 0xb7, 0xd1, 0x14, 0xc5, 0x39, 0xf9, 0x2c, 0x28, 0xe2, 0x34, 0x8b, 0x73, + 0xf0, 0xdb, 0x8c, 0x0b, 0xcc, 0x98, 0xc4, 0x15, 0xbf, 0x94, 0x05, 0x68, 0xbb, 0x73, 0x95, 0x5a, 0xd7, 0x20, 0x20, + 0xfb, 0x01, 0xac, 0x5e, 0x1a, 0x3a, 0x2a, 0xe7, 0xdd, 0xa5, 0x4d, 0x21, 0x62, 0x11, 0x82, 0x4d, 0x33, 0x5d, 0xb2, + 0xd3, 0x50, 0x69, 0x73, 0x20, 0xd4, 0x11, 0x1a, 0xf7, 0x4f, 0xc3, 0xd8, 0x6a, 0x8a, 0xad, 0xdd, 0xdb, 0x76, 0xfb, + 0x8f, 0xd2, 0x4b, 0xa7, 0x39, 0xe9, 0x31, 0xf6, 0x8f, 0x32, 0x2c, 0x46, 0xb6, 0x23, 0x04, 0x96, 0x9c, 0xf7, 0xa9, + 0xff, 0x8a, 0x96, 0xf3, 0x04, 0x4c, 0x47, 0x74, 0xb0, 0x5c, 0xa0, 0xec, 0x18, 0xd0, 0x1d, 0x18, 0x5c, 0xd1, 0xef, + 0x83, 0x55, 0x86, 0xb9, 0x90, 0x2c, 0x49, 0xca, 0xe0, 0x79, 0xea, 0xc1, 0xc1, 0xaf, 0x99, 0x32, 0x77, 0x51, 0xd6, + 0xa7, 0x4b, 0x32, 0x4d, 0x91, 0x81, 0x58, 0x87, 0x9b, 0x2c, 0x8d, 0x12, 0x25, 0x22, 0x5b, 0xa2, 0x7f, 0xa4, 0xa1, + 0x58, 0x3a, 0x72, 0x2f, 0x52, 0x25, 0x42, 0xc5, 0x3c, 0xc5, 0x93, 0x3a, 0xad, 0xd3, 0x11, 0x86, 0x9e, 0x04, 0xa5, + 0x5c, 0x0d, 0x03, 0x55, 0x52, 0xbd, 0x14, 0x36, 0xc5, 0x76, 0xab, 0x2f, 0x56, 0x62, 0x1e, 0x2f, 0xf0, 0xa5, 0xc0, + 0x51, 0xfc, 0x07, 0xf7, 0xc2, 0x4e, 0xa9, 0xed, 0x41, 0xed, 0x88, 0x12, 0xfa, 0x0f, 0x0e, 0x17, 0x89, 0xef, 0xa4, + 0x0e, 0x01, 0x88, 0x16, 0x21, 0x67, 0xea, 0x20, 0x35, 0xdc, 0xd0, 0x8e, 0xf0, 0xdf, 0x70, 0x7d, 0xc6, 0x19, 0xbd, + 0xa9, 0x66, 0xd4, 0x50, 0xbe, 0x1e, 0xb4, 0x31, 0xea, 0xb3, 0x81, 0xc3, 0x0a, 0x51, 0x68, 0xc3, 0x4e, 0x4a, 0x25, + 0x5a, 0x18, 0x4a, 0xf5, 0x97, 0x50, 0x71, 0xc2, 0x9d, 0x19, 0x65, 0xc9, 0xf8, 0xb4, 0x3c, 0x16, 0xd3, 0xc1, 0xa0, + 0x24, 0x95, 0xb1, 0xd0, 0x83, 0xeb, 0x81, 0xe7, 0xdf, 0x03, 0xb7, 0x10, 0x0f, 0x19, 0x59, 0x0c, 0xb9, 0xc1, 0xc9, + 0x6f, 0x71, 0x72, 0xd5, 0xa8, 0x54, 0x71, 0xac, 0x89, 0x6a, 0xc1, 0xf7, 0x65, 0x18, 0xa0, 0x4f, 0x52, 0x00, 0x26, + 0x83, 0x29, 0xbf, 0x05, 0x89, 0xd2, 0x99, 0xba, 0x21, 0xfd, 0x22, 0x0a, 0x7e, 0xc1, 0x0b, 0x2e, 0x12, 0x57, 0x80, + 0xe5, 0x1d, 0x6c, 0xaf, 0xa3, 0x8a, 0x2a, 0x4c, 0x5e, 0xd3, 0xe3, 0x88, 0x1b, 0xef, 0x3f, 0xd3, 0x63, 0x8b, 0xd9, + 0x6a, 0x1d, 0x1b, 0x7c, 0xe6, 0x18, 0x5c, 0xd0, 0xb5, 0xc4, 0xd6, 0x50, 0x0d, 0x2b, 0x02, 0x03, 0x17, 0x70, 0x10, + 0x96, 0x28, 0x8e, 0xad, 0xe4, 0x15, 0x69, 0x48, 0x69, 0xef, 0x19, 0x8e, 0x36, 0xc9, 0xf1, 0x6d, 0x96, 0xdd, 0x04, + 0xce, 0x17, 0x9d, 0x93, 0x66, 0xc2, 0xda, 0xe0, 0x7d, 0xde, 0x9c, 0x5f, 0xfb, 0x87, 0x84, 0xaa, 0xb8, 0x37, 0xbc, + 0x1d, 0xf7, 0xc6, 0x09, 0xbf, 0xe6, 0x62, 0xa1, 0x43, 0xb5, 0x98, 0x4b, 0x96, 0xdf, 0x5a, 0xef, 0x96, 0x24, 0xb5, + 0x02, 0xda, 0x67, 0x59, 0x50, 0x13, 0x01, 0x20, 0x7f, 0xf8, 0x0b, 0x84, 0xce, 0xf0, 0xb7, 0xc7, 0xe0, 0x8a, 0x14, + 0xee, 0x1d, 0x02, 0x61, 0x4d, 0x37, 0x77, 0x6a, 0x03, 0xbe, 0x18, 0xf7, 0x67, 0x4c, 0x3d, 0xfd, 0x36, 0x93, 0xbb, + 0xba, 0x6e, 0x8f, 0x2c, 0xc3, 0x47, 0xb8, 0x52, 0x00, 0x37, 0x13, 0xfe, 0x62, 0x98, 0x49, 0xf5, 0x09, 0x60, 0xaa, + 0xe9, 0xe0, 0x3e, 0x41, 0x60, 0x00, 0x95, 0x68, 0x31, 0xba, 0x56, 0x8e, 0x68, 0x06, 0x6e, 0x4d, 0xb7, 0xc2, 0x78, + 0xeb, 0x41, 0x0b, 0x3d, 0xd3, 0x70, 0xe2, 0x3f, 0x68, 0xe6, 0x55, 0x01, 0x01, 0xb4, 0x32, 0x82, 0xb7, 0xd6, 0x47, + 0x73, 0x84, 0xf8, 0x84, 0x25, 0xd1, 0x84, 0xc5, 0x33, 0xc5, 0x8f, 0x09, 0xdd, 0x34, 0xb5, 0x4d, 0x1f, 0x90, 0xfe, + 0xe2, 0x9a, 0xf5, 0x53, 0x96, 0xb5, 0x6f, 0x0f, 0x15, 0x2f, 0xa6, 0xcd, 0x38, 0x88, 0x89, 0x2a, 0xc6, 0xff, 0x82, + 0xfb, 0x52, 0x2b, 0x40, 0x64, 0xee, 0xaa, 0xa7, 0xdf, 0x6f, 0x66, 0xcb, 0x81, 0x50, 0xf9, 0x9d, 0x41, 0xd2, 0xa7, + 0x43, 0xfb, 0x81, 0x4d, 0xa2, 0xb6, 0xd0, 0xf3, 0xc7, 0xa5, 0x6e, 0xe2, 0xe5, 0xb5, 0xa9, 0x11, 0xad, 0x90, 0xa1, + 0xb2, 0x75, 0xc0, 0xfa, 0xfe, 0x21, 0xdc, 0x5d, 0xd4, 0x34, 0xd4, 0xba, 0xe7, 0xae, 0x45, 0xc1, 0x89, 0x3f, 0xc0, + 0x58, 0x5c, 0x48, 0x6a, 0x1d, 0x8f, 0x49, 0x3f, 0x5a, 0xc8, 0xe4, 0x46, 0x5d, 0x9d, 0x9c, 0x29, 0xe6, 0x09, 0x5c, + 0x80, 0xcb, 0xb6, 0xbf, 0xa2, 0x52, 0x97, 0x72, 0x7b, 0x45, 0x69, 0x7a, 0x48, 0xdb, 0xab, 0x38, 0x6f, 0x0b, 0x2e, + 0xf8, 0x17, 0x0a, 0x2e, 0xac, 0x83, 0x75, 0xc7, 0x9d, 0xb2, 0x27, 0x3c, 0x51, 0xa6, 0xb5, 0xc1, 0x5d, 0x37, 0x18, + 0x13, 0x63, 0xbf, 0xbb, 0xe4, 0xc9, 0x47, 0x64, 0xc1, 0xbf, 0xcb, 0x04, 0x78, 0x26, 0xbb, 0x57, 0x2a, 0xff, 0x0f, + 0xfe, 0xd5, 0xd6, 0xbe, 0xb3, 0xe6, 0x9f, 0x9e, 0xf5, 0x70, 0xe7, 0x30, 0xf9, 0xb1, 0x3a, 0x03, 0xba, 0xb9, 0x92, + 0x29, 0x07, 0x64, 0x00, 0x6b, 0x91, 0x8c, 0x06, 0x7c, 0x68, 0x65, 0xd9, 0xf6, 0x9d, 0x56, 0x17, 0x84, 0xbd, 0x04, + 0x6e, 0xba, 0xbf, 0x36, 0x33, 0x73, 0xba, 0x56, 0xa2, 0xe9, 0xd2, 0xd8, 0x5a, 0x96, 0x2a, 0x8c, 0xf7, 0xbd, 0x27, + 0xd9, 0x34, 0x3f, 0x5e, 0x4e, 0x73, 0x4b, 0xdd, 0x36, 0x6e, 0xd9, 0x00, 0x1a, 0x62, 0xd7, 0xda, 0xca, 0x01, 0x2f, + 0xb7, 0x07, 0xd1, 0x7c, 0xad, 0x08, 0x3d, 0x55, 0x22, 0xf4, 0x69, 0xda, 0xec, 0x83, 0x5d, 0x55, 0xeb, 0x46, 0xc8, + 0xa3, 0x41, 0xaa, 0x19, 0xf9, 0x37, 0xd7, 0xbc, 0xb8, 0xc8, 0xe5, 0x0d, 0xc0, 0x21, 0x93, 0xda, 0x28, 0x2c, 0xaf, + 0xc0, 0x9d, 0x1f, 0x1d, 0xc7, 0x99, 0x18, 0xe5, 0x18, 0xb7, 0x15, 0x91, 0x92, 0x75, 0xe2, 0x0c, 0xf0, 0x90, 0xfd, + 0x49, 0xd3, 0xa1, 0x5d, 0x0b, 0x0c, 0xef, 0x0b, 0xdc, 0x55, 0xce, 0x4e, 0x36, 0xb9, 0x5d, 0xf4, 0xcd, 0x19, 0xd6, + 0x1d, 0x29, 0xad, 0x8d, 0x45, 0xd7, 0x1d, 0xac, 0x35, 0x83, 0xb6, 0x08, 0x25, 0x1f, 0x72, 0x27, 0xed, 0xa7, 0x80, + 0x06, 0x67, 0x59, 0x7a, 0x6b, 0xad, 0xf2, 0x37, 0x5a, 0x88, 0x13, 0xc5, 0xd4, 0x89, 0x6f, 0xa2, 0x44, 0x9f, 0x9f, + 0x89, 0x71, 0x03, 0x81, 0xd4, 0x1f, 0x30, 0xbe, 0x46, 0x11, 0x26, 0x70, 0x1d, 0x88, 0x62, 0x7b, 0xa2, 0x36, 0x96, + 0x23, 0xe8, 0x84, 0x10, 0xef, 0xa0, 0x0c, 0x63, 0x75, 0x71, 0xa0, 0x0d, 0x96, 0xbe, 0x6e, 0xad, 0x73, 0x43, 0x28, + 0x8c, 0x13, 0x98, 0x62, 0x90, 0xd4, 0x59, 0x67, 0x99, 0xa0, 0xca, 0x8e, 0x49, 0xe7, 0x7d, 0x80, 0xee, 0xae, 0x45, + 0x53, 0x7c, 0xdd, 0xb9, 0x83, 0xf6, 0x71, 0xfd, 0x5a, 0x8b, 0xdc, 0xe0, 0xcf, 0x5b, 0x22, 0x2c, 0x02, 0x67, 0xad, + 0xc9, 0x57, 0x8d, 0x70, 0x60, 0x4a, 0x32, 0x0d, 0x7b, 0xb9, 0xb2, 0xe9, 0xde, 0x6e, 0x7b, 0xbd, 0xbd, 0x22, 0xae, + 0x1e, 0x63, 0x95, 0x77, 0x33, 0xb7, 0x77, 0xaa, 0xb5, 0xd8, 0xbd, 0x69, 0xfb, 0x29, 0x76, 0xd4, 0x5a, 0xbb, 0xdd, + 0x70, 0x42, 0x0d, 0xf9, 0x56, 0x54, 0x69, 0x75, 0xba, 0x31, 0x68, 0x87, 0xd0, 0xd6, 0x22, 0x83, 0x1b, 0xe5, 0x33, + 0x27, 0x74, 0x52, 0x21, 0x57, 0x9d, 0xba, 0x60, 0x73, 0xc5, 0xab, 0xa5, 0x4c, 0x23, 0x41, 0xd1, 0xe6, 0x3c, 0x2a, + 0x69, 0x22, 0xd7, 0xa2, 0x8a, 0x64, 0x8d, 0x7a, 0x51, 0xab, 0x31, 0x40, 0x40, 0xa6, 0xb3, 0xa6, 0x07, 0x55, 0x30, + 0x1b, 0xca, 0x48, 0x4e, 0x5f, 0x80, 0xa5, 0x3d, 0x72, 0xac, 0xf5, 0xbe, 0x3a, 0x5b, 0x7c, 0xab, 0x27, 0x04, 0x53, + 0x98, 0x3d, 0x10, 0x11, 0xae, 0x69, 0x0c, 0x39, 0xed, 0x12, 0x97, 0x35, 0xdd, 0x12, 0xf6, 0x70, 0xbb, 0x92, 0x9d, + 0xb8, 0x79, 0xd2, 0xdc, 0x5c, 0xc1, 0x4e, 0x8a, 0xf9, 0x18, 0xb4, 0x5f, 0x52, 0x5d, 0xbb, 0x34, 0xb7, 0x1e, 0x0f, + 0x02, 0x1a, 0x0c, 0x0a, 0xc3, 0xbf, 0x4e, 0x8c, 0x87, 0x27, 0x0d, 0x08, 0x92, 0x72, 0x11, 0x8e, 0x7d, 0x23, 0xfa, + 0xc9, 0x54, 0x1e, 0x73, 0xb4, 0x78, 0x87, 0x56, 0xe7, 0x10, 0xd0, 0x4b, 0x84, 0x92, 0x18, 0x55, 0xa1, 0x11, 0x41, + 0x79, 0x5a, 0xfe, 0x52, 0x55, 0x87, 0x80, 0x42, 0xda, 0x57, 0x14, 0xca, 0x36, 0x89, 0xa1, 0x19, 0x7e, 0x39, 0x9f, + 0x2c, 0xf4, 0x0c, 0x0c, 0xe4, 0xfc, 0x68, 0xa1, 0x67, 0x61, 0x20, 0xe7, 0x8f, 0x16, 0xb5, 0x5b, 0x07, 0x9a, 0x80, + 0x78, 0x2e, 0x1c, 0x9d, 0x94, 0x56, 0x65, 0x0b, 0xe8, 0xe6, 0x3e, 0x82, 0xfe, 0x0f, 0x7b, 0x08, 0x3a, 0xb9, 0xd0, + 0x8e, 0xdc, 0x80, 0xb6, 0x43, 0x12, 0xd8, 0x2b, 0x26, 0x15, 0x26, 0x16, 0xd1, 0x31, 0x1b, 0x83, 0x21, 0xb6, 0xfa, + 0xe0, 0x98, 0x8d, 0xa7, 0x3e, 0x09, 0x02, 0x46, 0xf7, 0x07, 0x03, 0x0e, 0x7e, 0x8b, 0x57, 0xe9, 0x93, 0x8d, 0x40, + 0x37, 0x7d, 0x77, 0x37, 0xf4, 0x2e, 0xae, 0xe0, 0x54, 0xed, 0xee, 0x49, 0xe8, 0x26, 0xd3, 0x8e, 0xd5, 0x6b, 0x88, + 0x1b, 0xf2, 0x2b, 0xa3, 0xd1, 0xc8, 0xa6, 0x84, 0x84, 0x18, 0xce, 0xa1, 0x99, 0xd3, 0x72, 0xf9, 0xea, 0xd6, 0xb3, + 0x01, 0x19, 0x66, 0x7a, 0xcb, 0x64, 0x7d, 0x0f, 0x65, 0xd5, 0x63, 0x68, 0x87, 0xde, 0x23, 0xc7, 0xf7, 0x0f, 0xbe, + 0xc9, 0xf8, 0x99, 0xc3, 0xb5, 0x87, 0x73, 0xe1, 0xbb, 0xac, 0x19, 0x99, 0x43, 0xe7, 0xd9, 0xc7, 0xf1, 0x1e, 0xc6, + 0xc9, 0xe7, 0x59, 0x28, 0x6f, 0xbc, 0xa6, 0xff, 0x51, 0xe9, 0xcd, 0x0e, 0x87, 0x9c, 0xae, 0x60, 0xc5, 0xcd, 0xaa, + 0xd0, 0xf0, 0xb3, 0xc8, 0x1b, 0x47, 0xbc, 0x26, 0x51, 0xd5, 0x7d, 0xde, 0xdb, 0x88, 0xa5, 0x1d, 0xe3, 0x00, 0xe0, + 0x44, 0xad, 0x1a, 0x76, 0xa5, 0x71, 0xad, 0x0e, 0x62, 0x44, 0x4a, 0xd8, 0x2a, 0x71, 0x24, 0x94, 0xbf, 0x01, 0x08, + 0x8b, 0xa1, 0x38, 0xde, 0x1a, 0xd6, 0x7b, 0xd8, 0x0f, 0x5d, 0xa0, 0x69, 0x4e, 0xa9, 0x66, 0x00, 0x90, 0x04, 0xfc, + 0xd1, 0xd3, 0x4d, 0x43, 0x65, 0x9b, 0xe7, 0xa1, 0x65, 0x75, 0x05, 0xf7, 0xf4, 0xd4, 0x95, 0x0c, 0x8c, 0xab, 0x3a, + 0xf6, 0x36, 0xfb, 0xdb, 0xa3, 0x55, 0xe4, 0x3b, 0x9b, 0xd4, 0x34, 0x0b, 0x20, 0x45, 0xe3, 0xd2, 0x17, 0x7a, 0x3a, + 0x01, 0x5a, 0xaf, 0x2d, 0x15, 0xed, 0xf7, 0x51, 0x8c, 0x1a, 0x17, 0x0a, 0xac, 0xc2, 0x04, 0x85, 0x43, 0x84, 0x11, + 0x42, 0x7f, 0x2e, 0xc3, 0x8d, 0x2f, 0xc8, 0x20, 0x1a, 0xae, 0x45, 0x87, 0x22, 0x72, 0xbc, 0x68, 0x5b, 0xaa, 0x6a, + 0x4e, 0x9a, 0xb6, 0x04, 0xde, 0x44, 0x06, 0x6c, 0xe7, 0x9f, 0x36, 0x44, 0xae, 0xc2, 0x05, 0x0c, 0xdf, 0x11, 0xd7, + 0x82, 0xe8, 0xa6, 0x36, 0xf5, 0x36, 0xec, 0x10, 0x1d, 0x4d, 0xf1, 0xe8, 0x90, 0x7b, 0xee, 0x9e, 0xdb, 0x22, 0xbe, + 0xf9, 0x0c, 0xb9, 0x6b, 0x3a, 0x7b, 0x29, 0xc2, 0xa0, 0x6e, 0xd9, 0x40, 0xb1, 0x8e, 0x9d, 0xa0, 0x00, 0x03, 0xb8, + 0x7c, 0x02, 0x3a, 0x36, 0x18, 0x54, 0x04, 0x9f, 0x14, 0xb6, 0x4d, 0x83, 0xfc, 0x11, 0xef, 0x86, 0x0e, 0xaf, 0x2d, + 0x79, 0x20, 0x5e, 0x61, 0x9f, 0x29, 0x61, 0xff, 0x82, 0x82, 0xee, 0x28, 0x2f, 0x57, 0x85, 0xab, 0xd2, 0x00, 0x54, + 0xd9, 0xf1, 0x5c, 0x6b, 0x4a, 0x5a, 0xc0, 0x4a, 0x49, 0xdd, 0xf9, 0x4d, 0x70, 0xdc, 0x92, 0xa9, 0xf0, 0xad, 0xba, + 0x51, 0xe5, 0xb1, 0x44, 0x91, 0x8e, 0x3d, 0xdb, 0x39, 0x58, 0x03, 0xe0, 0x29, 0x6c, 0x2f, 0xce, 0x04, 0x7c, 0xee, + 0xb4, 0xcb, 0x96, 0xb9, 0x04, 0x8a, 0xfa, 0x7e, 0x9c, 0x97, 0x1d, 0x5f, 0xee, 0x8e, 0xb6, 0xf7, 0xd0, 0x1b, 0xb1, + 0x31, 0x5e, 0x5f, 0x46, 0x4d, 0xbf, 0x78, 0x86, 0x2b, 0x4b, 0x41, 0xee, 0x69, 0xaa, 0x47, 0x18, 0x1d, 0x02, 0xd3, + 0x94, 0x9f, 0xb0, 0xf1, 0x74, 0x38, 0x34, 0x64, 0xd0, 0x6b, 0x26, 0x86, 0x02, 0xfb, 0x02, 0x5a, 0x67, 0x26, 0xae, + 0xf1, 0x69, 0xfb, 0x0a, 0x5a, 0xdd, 0xa2, 0x4c, 0xee, 0x0c, 0x0c, 0x1f, 0x68, 0xc9, 0x14, 0x4c, 0x15, 0xde, 0x10, + 0xa9, 0x64, 0x9f, 0x96, 0xd6, 0x61, 0xdf, 0x2e, 0x14, 0x5a, 0x68, 0xe2, 0x57, 0x19, 0xe2, 0xa7, 0xae, 0x33, 0xff, + 0x36, 0xed, 0x53, 0x83, 0x58, 0x38, 0x12, 0x83, 0x88, 0x5f, 0x9c, 0x2a, 0xdb, 0x09, 0xa1, 0x62, 0xe3, 0xa1, 0x6b, + 0xdd, 0x38, 0x92, 0x2a, 0x0c, 0xa5, 0xd0, 0x78, 0x6a, 0xb8, 0xef, 0x85, 0x0e, 0x5f, 0x87, 0x59, 0xdc, 0x66, 0x8d, + 0xa4, 0xc6, 0x38, 0x15, 0x26, 0x4e, 0xa5, 0x5c, 0x45, 0x02, 0x03, 0xe5, 0xd9, 0xc2, 0x20, 0xc0, 0x24, 0x26, 0x19, + 0x5b, 0x0b, 0x61, 0xc2, 0xd8, 0xb9, 0xc2, 0x34, 0x75, 0x91, 0xfa, 0xcd, 0xc0, 0x64, 0x41, 0x43, 0x7e, 0x8f, 0x46, + 0x6b, 0xaa, 0xa6, 0x00, 0xc3, 0x38, 0x4a, 0x35, 0xfe, 0x2d, 0x42, 0x6d, 0x86, 0x01, 0x80, 0x6d, 0xde, 0xca, 0x4c, + 0x54, 0x2f, 0x05, 0x42, 0xa0, 0x39, 0xfb, 0xa9, 0xb8, 0xda, 0x99, 0x05, 0xa3, 0x68, 0xb7, 0x57, 0x3e, 0x1f, 0x38, + 0xa1, 0x3c, 0x55, 0x17, 0xa8, 0x17, 0xb2, 0x78, 0x25, 0x53, 0xde, 0x0a, 0x91, 0x79, 0x20, 0xd9, 0x4f, 0xf9, 0x08, + 0xce, 0x2b, 0x74, 0x2a, 0x37, 0xdb, 0x44, 0x99, 0x25, 0x49, 0xc6, 0x02, 0x63, 0xf3, 0x12, 0xcc, 0xa4, 0x66, 0xc6, + 0xf0, 0x6b, 0x88, 0x33, 0xb6, 0x73, 0x12, 0x6e, 0xf6, 0xf3, 0xc0, 0x10, 0xa5, 0x5c, 0xb4, 0x44, 0xc3, 0xd6, 0x8e, + 0xd7, 0x93, 0x6b, 0xc2, 0x7d, 0xd8, 0x88, 0x35, 0x19, 0x63, 0x5c, 0x9b, 0x1b, 0x59, 0x3f, 0x5a, 0xe0, 0xc1, 0x98, + 0xb2, 0xfe, 0x04, 0x32, 0xad, 0xa4, 0xac, 0xf3, 0x85, 0x11, 0x33, 0xa9, 0x44, 0xef, 0xf6, 0x8d, 0xcf, 0xea, 0x2e, + 0xa2, 0x7e, 0x6b, 0xbf, 0x27, 0xf5, 0x70, 0xe7, 0x3f, 0x28, 0xac, 0x41, 0x65, 0xc4, 0x65, 0x44, 0x79, 0xe6, 0x40, + 0x37, 0x4d, 0x8a, 0x38, 0x3d, 0x5b, 0xc5, 0x45, 0xc9, 0x53, 0xa8, 0x54, 0x53, 0xb7, 0xa8, 0x37, 0x01, 0x7b, 0x43, + 0x24, 0x49, 0xd6, 0xd2, 0xd8, 0x8a, 0x5d, 0x1a, 0xa4, 0x67, 0x6f, 0xc4, 0xa5, 0x17, 0x15, 0x1a, 0xd2, 0x52, 0xef, + 0x2c, 0x54, 0x32, 0x7f, 0xc5, 0x7f, 0x06, 0xb5, 0x02, 0x1d, 0x6d, 0x52, 0x8c, 0xa7, 0xc0, 0x88, 0xef, 0x06, 0xb3, + 0xba, 0x87, 0xb8, 0x68, 0x82, 0x52, 0xef, 0x88, 0x1d, 0x3f, 0x37, 0x79, 0x78, 0x17, 0x72, 0xce, 0xe0, 0xd3, 0xfb, + 0x59, 0xa2, 0xd6, 0x3a, 0x12, 0x23, 0x35, 0x03, 0x68, 0x3a, 0x28, 0x73, 0x1e, 0x8b, 0x60, 0xd6, 0x33, 0x89, 0x51, + 0x8f, 0xeb, 0x5f, 0xa0, 0xa1, 0xf6, 0x9b, 0x95, 0xe5, 0x59, 0x75, 0xf7, 0x25, 0x1c, 0xd8, 0xd4, 0x56, 0xd0, 0xe3, + 0x75, 0x25, 0x2f, 0x2f, 0x55, 0xb7, 0xfd, 0x42, 0x8c, 0x9c, 0xae, 0x71, 0x2d, 0x9d, 0x57, 0x0b, 0xd6, 0xeb, 0x4e, + 0x37, 0x8b, 0xbb, 0x59, 0x46, 0x03, 0x61, 0x6d, 0xe7, 0x13, 0xcd, 0x9f, 0x35, 0xdb, 0xee, 0xe3, 0x2d, 0x88, 0x59, + 0x00, 0x10, 0xe9, 0x41, 0x14, 0x2c, 0xb3, 0x94, 0x07, 0x54, 0xee, 0xe3, 0x28, 0x0b, 0xa5, 0x97, 0xb3, 0x8c, 0x9f, + 0x36, 0x8d, 0xb5, 0xce, 0x0a, 0x65, 0x68, 0x6d, 0x74, 0xa7, 0xab, 0x0c, 0xb1, 0xfd, 0x24, 0xce, 0x16, 0xe0, 0xfe, + 0x98, 0xa1, 0xd0, 0xd0, 0x59, 0x46, 0x9a, 0x68, 0xf8, 0xae, 0x3d, 0x83, 0x8c, 0xe2, 0x64, 0x9d, 0x57, 0xd2, 0x8d, + 0x3e, 0x6b, 0x23, 0x61, 0xee, 0x21, 0xfa, 0x55, 0x0c, 0x1e, 0xe5, 0x3e, 0xaf, 0x8d, 0x4e, 0xa6, 0x65, 0xa4, 0xdd, + 0xf9, 0x49, 0xbd, 0xcc, 0x52, 0xad, 0xc3, 0xf6, 0x19, 0xf6, 0xd6, 0x98, 0xf4, 0x26, 0xa4, 0x86, 0x91, 0xf8, 0x7c, + 0x46, 0x8d, 0x10, 0xd0, 0x96, 0xe3, 0xef, 0xf0, 0x19, 0x86, 0xa6, 0xc0, 0x52, 0xc5, 0x2d, 0xec, 0x86, 0xaf, 0xf9, + 0x64, 0xd5, 0x02, 0x10, 0xcc, 0xca, 0xd7, 0xbb, 0x78, 0x25, 0xd4, 0x67, 0xda, 0x0c, 0x00, 0x59, 0x50, 0xca, 0x1d, + 0x3f, 0xa5, 0xd2, 0xc1, 0x12, 0x45, 0xdb, 0xcb, 0xe9, 0x1b, 0x1d, 0x1b, 0xdf, 0xa7, 0xe7, 0x02, 0xb6, 0x0b, 0xf9, + 0xad, 0xbd, 0x7a, 0x89, 0x8a, 0xd4, 0xb6, 0x59, 0xf7, 0xf0, 0xe5, 0x06, 0x4d, 0xc2, 0x08, 0xca, 0x94, 0x29, 0x80, + 0xc1, 0x4d, 0x35, 0x0a, 0x26, 0xad, 0x46, 0xc2, 0x96, 0x7a, 0x92, 0xe5, 0xa6, 0x0f, 0x4e, 0xb5, 0x47, 0xd0, 0x73, + 0xab, 0x9c, 0x2f, 0x5a, 0xf6, 0x6b, 0x05, 0x47, 0x27, 0x57, 0x43, 0xd4, 0xcc, 0x7b, 0x6d, 0x47, 0x86, 0x94, 0xcb, + 0x30, 0x10, 0x4c, 0x39, 0xe6, 0xe9, 0xb1, 0xf5, 0x8c, 0x88, 0xee, 0x39, 0xfb, 0x4c, 0xb7, 0xea, 0x4a, 0x02, 0xa2, + 0xe3, 0x37, 0x8f, 0x5f, 0x5e, 0xc5, 0x97, 0x06, 0x45, 0xa9, 0x61, 0x11, 0xa3, 0x4c, 0xfb, 0x2a, 0x09, 0x83, 0xf7, + 0xcb, 0xbb, 0x9f, 0x54, 0x96, 0xda, 0xef, 0xc1, 0xc6, 0x8a, 0xaa, 0x7e, 0x29, 0x79, 0xd1, 0x14, 0x60, 0xed, 0xb3, + 0x44, 0x81, 0xdc, 0xef, 0x6c, 0x9a, 0xf9, 0x26, 0x6a, 0xdc, 0x6c, 0x58, 0x6f, 0x5c, 0xb7, 0x4b, 0x6d, 0xc9, 0x8e, + 0xac, 0x44, 0xce, 0x2c, 0x06, 0x33, 0x7e, 0x54, 0x18, 0x94, 0x86, 0x0d, 0xaa, 0x52, 0xf1, 0x7b, 0x23, 0x82, 0x53, + 0xc7, 0xaa, 0xc2, 0x98, 0x06, 0xcc, 0xb6, 0xa2, 0xd6, 0xa0, 0x0e, 0x4a, 0x69, 0x6b, 0x02, 0xb2, 0xfd, 0xce, 0x0a, + 0x6a, 0x7e, 0xff, 0xd3, 0x18, 0xf2, 0x35, 0xa5, 0xa0, 0x92, 0x80, 0x9d, 0x41, 0xa3, 0xa7, 0x4a, 0x18, 0x48, 0x41, + 0xf0, 0x04, 0x28, 0x5f, 0x44, 0x8d, 0xd5, 0x6e, 0x5f, 0x9d, 0x1a, 0xa3, 0x2d, 0x20, 0xb4, 0x90, 0x1e, 0x5d, 0xf6, + 0x71, 0x1b, 0xeb, 0x40, 0xe2, 0xc1, 0x09, 0xb6, 0x73, 0x75, 0x8d, 0x46, 0x42, 0xf3, 0xfb, 0x46, 0x03, 0x5e, 0xd3, + 0x0a, 0x14, 0xea, 0x39, 0x8e, 0x86, 0xce, 0x0e, 0x29, 0x88, 0xd8, 0xa0, 0x85, 0x7d, 0x7b, 0x3e, 0x34, 0xfb, 0x7a, + 0x9e, 0x2c, 0x48, 0x4d, 0xa5, 0xfb, 0xdc, 0x2d, 0x21, 0x6b, 0xd5, 0xa1, 0xac, 0x3c, 0xc0, 0xf1, 0x42, 0xc9, 0xfc, + 0x1d, 0x26, 0x35, 0x4a, 0x63, 0x42, 0x63, 0xc4, 0x02, 0x96, 0x04, 0xed, 0xf5, 0x40, 0xfd, 0x32, 0x08, 0x15, 0xce, + 0xf4, 0x44, 0xe2, 0x53, 0xca, 0xd5, 0xa7, 0x05, 0xa9, 0xa7, 0x05, 0x73, 0xa0, 0x97, 0xbe, 0x95, 0x5f, 0xd9, 0xf8, + 0x68, 0x77, 0xef, 0x9a, 0x0b, 0xeb, 0x18, 0xe2, 0x62, 0x0b, 0xbf, 0x39, 0x35, 0x05, 0x60, 0xc3, 0x53, 0x5d, 0x96, + 0x6f, 0xd4, 0x44, 0x66, 0x71, 0x48, 0x22, 0x90, 0x6c, 0x37, 0x37, 0xb7, 0x11, 0x6c, 0x7b, 0x0b, 0xb5, 0xa1, 0xfe, + 0xf2, 0xb6, 0xfb, 0x9e, 0xe1, 0xe5, 0x9e, 0xdc, 0xbb, 0x69, 0x43, 0xf9, 0xc3, 0xfe, 0x55, 0xf2, 0x7f, 0x55, 0xc9, + 0x7e, 0xab, 0xcc, 0xba, 0x2d, 0xde, 0xef, 0x3a, 0x6e, 0x39, 0x46, 0x83, 0xc0, 0x9a, 0x02, 0x03, 0xe9, 0x49, 0x63, + 0x9a, 0xe8, 0xe8, 0xca, 0x8c, 0x19, 0x3c, 0xba, 0x00, 0xcd, 0x61, 0x3a, 0xcf, 0x63, 0x00, 0x0e, 0xf0, 0x8f, 0x3c, + 0x42, 0xfd, 0xd3, 0x79, 0x1e, 0x9c, 0x05, 0x83, 0x72, 0x10, 0xe8, 0x4f, 0x5c, 0x73, 0x82, 0x05, 0xe8, 0xdc, 0x62, + 0x06, 0x71, 0x27, 0xad, 0x99, 0x43, 0x7c, 0x9c, 0x4c, 0x07, 0x83, 0x98, 0x6c, 0x00, 0xa4, 0x2f, 0x5e, 0x58, 0xe7, + 0xa0, 0x42, 0x2f, 0xc8, 0x56, 0xdd, 0x45, 0xb3, 0x62, 0xaf, 0xda, 0x69, 0xde, 0xef, 0xe7, 0xf3, 0x72, 0x10, 0x34, + 0x2a, 0x2c, 0x8c, 0xf7, 0x1f, 0x6d, 0x7e, 0x69, 0x74, 0xd2, 0x04, 0x23, 0xd6, 0x9e, 0xa2, 0x7a, 0xc5, 0xd3, 0x8c, + 0x36, 0x6e, 0xc7, 0x4a, 0xf9, 0x02, 0xa2, 0x78, 0x60, 0xc8, 0x5a, 0x79, 0x77, 0x0e, 0x5e, 0x97, 0x1b, 0x6f, 0x8e, + 0x28, 0xc0, 0x6e, 0x0a, 0xe3, 0xa4, 0xe6, 0xa2, 0x8b, 0x9a, 0x78, 0x06, 0x3b, 0x5d, 0xbd, 0x95, 0x68, 0x35, 0xde, + 0x8b, 0x77, 0xcd, 0xc6, 0x5f, 0xcb, 0x03, 0x5d, 0xe6, 0xc1, 0x05, 0x20, 0xce, 0x1e, 0xc4, 0xd5, 0x01, 0x96, 0x7a, + 0x10, 0x0c, 0x2c, 0x72, 0x48, 0xbb, 0x5a, 0x3d, 0x14, 0x91, 0x3a, 0x8f, 0xc1, 0x80, 0xc9, 0x34, 0xa4, 0x26, 0xd3, + 0x5e, 0xac, 0x20, 0x6d, 0xac, 0xb5, 0x80, 0x36, 0x1c, 0x16, 0x3b, 0x76, 0xc3, 0xee, 0x74, 0xeb, 0x50, 0x28, 0x61, + 0x20, 0xeb, 0xba, 0x79, 0xa8, 0x35, 0x3c, 0x11, 0xf4, 0xa0, 0x1a, 0xed, 0xa7, 0x87, 0xf2, 0xa4, 0x3d, 0x16, 0xe0, + 0xa2, 0x87, 0x2f, 0x9f, 0x0b, 0xbc, 0x68, 0xef, 0x20, 0xcf, 0x99, 0x4f, 0x95, 0x0f, 0x62, 0xc3, 0x2d, 0xc3, 0x87, + 0xf6, 0xf1, 0xad, 0x40, 0x26, 0x75, 0x47, 0x53, 0x5b, 0xbb, 0xa3, 0x71, 0x4c, 0xa0, 0xdf, 0x94, 0xa3, 0x94, 0x89, + 0xa9, 0x65, 0xc9, 0x4e, 0x7a, 0xb9, 0xf2, 0x86, 0x4a, 0xd9, 0xc9, 0xb2, 0xcd, 0xf9, 0xa5, 0x8d, 0x84, 0x7e, 0x5f, + 0xbb, 0x03, 0xe1, 0x1b, 0xb5, 0xde, 0x90, 0x97, 0x0d, 0x11, 0xcb, 0x21, 0x66, 0xe0, 0x78, 0x21, 0x95, 0x6b, 0x77, + 0xd1, 0x54, 0xd5, 0xed, 0x6c, 0xe5, 0x82, 0x96, 0x78, 0x2b, 0x05, 0x56, 0x91, 0x3a, 0xbd, 0x9e, 0x4a, 0xdc, 0xf7, + 0x51, 0x6c, 0x3f, 0x02, 0xb6, 0xb1, 0x71, 0x34, 0x36, 0x6e, 0x11, 0x1b, 0x7c, 0x15, 0x55, 0xb4, 0xe0, 0x00, 0xc1, + 0xdd, 0x96, 0xd4, 0xd2, 0xcc, 0x21, 0xee, 0x2b, 0x1e, 0xa0, 0x7d, 0x17, 0x47, 0x9c, 0x0a, 0xb0, 0xad, 0x6b, 0x9d, + 0xb3, 0x5a, 0x0e, 0xd8, 0x4c, 0xf4, 0xfc, 0xd3, 0xaa, 0x91, 0x88, 0x61, 0x95, 0x8d, 0x94, 0x15, 0xda, 0xbd, 0xd2, + 0x25, 0x5c, 0x7c, 0x01, 0x5e, 0xb6, 0xf7, 0x2b, 0xbb, 0xcf, 0x96, 0xd8, 0x3f, 0xcc, 0xab, 0x26, 0x78, 0xe4, 0x35, + 0xde, 0xde, 0xc3, 0xc4, 0x97, 0x4a, 0x21, 0xbc, 0x4a, 0x69, 0x28, 0x01, 0x18, 0x24, 0x41, 0x0d, 0x57, 0xda, 0x36, + 0x83, 0x54, 0xc6, 0xb0, 0xbb, 0xd5, 0x5b, 0xfd, 0x9f, 0x56, 0xe1, 0xa2, 0x92, 0xc5, 0x98, 0x04, 0x3a, 0xa7, 0x5a, + 0x6e, 0x02, 0x0b, 0x9e, 0xed, 0x92, 0x23, 0x50, 0xd8, 0x09, 0xe0, 0x86, 0x12, 0xf6, 0x4f, 0xbc, 0x0d, 0xe5, 0xec, + 0xb5, 0x95, 0x3c, 0xb9, 0x7d, 0x49, 0x05, 0x4d, 0xc8, 0x54, 0xd8, 0xfd, 0xdb, 0xda, 0xb0, 0xcf, 0x42, 0x39, 0x92, + 0x02, 0x17, 0x07, 0x9d, 0x03, 0xd8, 0x1f, 0xe4, 0x32, 0x36, 0x9f, 0x49, 0xbf, 0xaf, 0xde, 0x3f, 0xcd, 0xb3, 0xe4, + 0xe3, 0xce, 0x7b, 0xc3, 0xd3, 0x2c, 0x19, 0x50, 0x89, 0x98, 0x5a, 0x57, 0xc5, 0x70, 0xa9, 0x5d, 0x8c, 0x1b, 0x24, + 0x23, 0xde, 0x4b, 0x1d, 0x62, 0xc4, 0xf8, 0x22, 0x3b, 0x24, 0x25, 0xa7, 0xcb, 0xba, 0xb3, 0xe7, 0x5a, 0x34, 0x83, + 0xc6, 0x70, 0x3b, 0xde, 0x4b, 0x7a, 0x05, 0xa8, 0x00, 0xd1, 0x3d, 0x0b, 0x5c, 0xc3, 0x9b, 0x4b, 0xa2, 0xb1, 0xa5, + 0xa7, 0x2d, 0xd1, 0xc0, 0x5e, 0x99, 0x90, 0x54, 0x1b, 0x07, 0x58, 0xc4, 0xba, 0xfe, 0x18, 0x16, 0x00, 0xd4, 0x6a, + 0x90, 0x5e, 0xe9, 0x0b, 0x42, 0x55, 0x12, 0x82, 0xd1, 0x89, 0x84, 0x97, 0x01, 0x8d, 0x33, 0x93, 0x68, 0x61, 0x83, + 0x03, 0xfa, 0xb2, 0x32, 0x89, 0xc6, 0x86, 0x3c, 0xa0, 0xdc, 0xa6, 0x01, 0x0c, 0x3e, 0x48, 0x92, 0xe8, 0x87, 0xa5, + 0x49, 0x02, 0x41, 0x09, 0xca, 0x37, 0xe8, 0xbf, 0x4a, 0xcf, 0xc7, 0xf2, 0x47, 0xef, 0x50, 0xfa, 0x21, 0x2c, 0x40, + 0xa6, 0xa8, 0x2b, 0xa6, 0x19, 0x3b, 0xc9, 0xba, 0x8d, 0x49, 0x3c, 0x4f, 0xbb, 0xeb, 0x42, 0xb9, 0x74, 0x81, 0x5f, + 0x59, 0x86, 0x38, 0xd6, 0x4f, 0xe3, 0x15, 0x3b, 0x0d, 0xb9, 0xc6, 0x4b, 0x7f, 0x1a, 0xaf, 0x70, 0x86, 0x68, 0xd5, + 0x4a, 0x20, 0xca, 0x7f, 0xd5, 0x06, 0x0e, 0x71, 0x9f, 0x60, 0x90, 0x8b, 0xca, 0x7b, 0x20, 0x90, 0xb7, 0x15, 0x44, + 0xa4, 0x99, 0x5d, 0x87, 0x11, 0xa9, 0x76, 0x92, 0xcc, 0x97, 0x3f, 0xca, 0x4c, 0x78, 0xdf, 0xc0, 0x63, 0xb3, 0x59, + 0x36, 0xc5, 0x7c, 0xa1, 0x82, 0x39, 0xb8, 0x4f, 0x54, 0x5c, 0x8a, 0xca, 0x7f, 0xc2, 0x2e, 0x78, 0x31, 0x1e, 0xbc, + 0x5e, 0x23, 0xc0, 0x7e, 0xe5, 0x3f, 0x79, 0x63, 0xf6, 0xa7, 0x75, 0xe3, 0xcb, 0x4c, 0xc4, 0x07, 0x3e, 0xba, 0xa5, + 0x7c, 0x74, 0xe7, 0x65, 0xfa, 0xae, 0x01, 0x25, 0x32, 0x2a, 0x2b, 0xbe, 0x5a, 0xf1, 0x74, 0x76, 0x9d, 0x44, 0xd9, + 0xa8, 0xe2, 0x02, 0xa6, 0x17, 0x1c, 0xef, 0x92, 0xf5, 0x79, 0x96, 0xbc, 0x84, 0xd8, 0x03, 0x2b, 0xa9, 0xb0, 0xf8, + 0x61, 0x99, 0xa9, 0xc5, 0x2c, 0x64, 0x25, 0x05, 0x0f, 0x66, 0x37, 0x49, 0xf4, 0xe7, 0xd2, 0x43, 0x52, 0x33, 0x53, + 0xb6, 0xa9, 0x1d, 0xa1, 0x36, 0xbe, 0x8e, 0x74, 0xa3, 0x2d, 0x00, 0xe0, 0x9e, 0x2d, 0xd2, 0x48, 0x32, 0x31, 0x9c, + 0xd4, 0x8c, 0x9b, 0xf4, 0x02, 0x53, 0xe3, 0x9a, 0x55, 0x34, 0x71, 0x16, 0x32, 0xa0, 0xf7, 0xa7, 0xb9, 0x7e, 0xce, + 0xe0, 0xfe, 0x83, 0xd6, 0xc0, 0xe5, 0x71, 0xd1, 0xef, 0xcb, 0xe3, 0x62, 0xbb, 0x2d, 0x4f, 0xe2, 0x7e, 0x5f, 0x9e, + 0xc4, 0x86, 0x7f, 0x50, 0x8a, 0x6d, 0x63, 0x6e, 0x90, 0xd0, 0x5c, 0x42, 0xd4, 0xa2, 0x11, 0xfc, 0xa1, 0x59, 0xce, + 0x45, 0x94, 0x1f, 0x27, 0xfd, 0x7e, 0x6f, 0x39, 0x13, 0x83, 0x7c, 0x98, 0x44, 0xf9, 0x30, 0xf1, 0x9c, 0x10, 0x7f, + 0xf5, 0x9c, 0x10, 0x15, 0x0d, 0x5c, 0xc1, 0x99, 0x01, 0x88, 0x02, 0x3e, 0xfd, 0xa3, 0xba, 0x96, 0x42, 0xd7, 0x12, + 0xab, 0x5a, 0x12, 0x5d, 0x41, 0xcd, 0x6e, 0x8a, 0xb0, 0xc4, 0x52, 0xe8, 0x92, 0x7d, 0xb7, 0x04, 0x9e, 0x28, 0xe7, + 0xd5, 0x06, 0x18, 0xd8, 0x08, 0xef, 0x1c, 0x26, 0x9c, 0xc4, 0xba, 0x06, 0xb4, 0xd3, 0x4d, 0x4d, 0x2f, 0xe8, 0x8a, + 0x5e, 0x22, 0x3f, 0x7b, 0x01, 0x06, 0x4b, 0xc7, 0x2c, 0x9f, 0x0e, 0x06, 0x17, 0x64, 0xc5, 0xca, 0x79, 0x18, 0x0f, + 0xc2, 0xf5, 0x2c, 0x1f, 0x5e, 0x44, 0x17, 0x84, 0x7c, 0x55, 0x2c, 0x68, 0x6f, 0x35, 0x2a, 0x3f, 0x66, 0x10, 0xde, + 0x2f, 0x9d, 0x85, 0x99, 0x89, 0xf3, 0xb1, 0x1a, 0xdd, 0xd2, 0x15, 0xc4, 0xaf, 0x81, 0x1b, 0x09, 0x89, 0xa0, 0x23, + 0x97, 0x74, 0x45, 0xd7, 0x54, 0x9a, 0x19, 0xc6, 0x68, 0xdd, 0xf6, 0x38, 0x49, 0xc0, 0x31, 0xd9, 0x15, 0x1f, 0x8d, + 0x55, 0xe1, 0x5d, 0xdf, 0x11, 0xda, 0xeb, 0x25, 0x6e, 0x90, 0x7e, 0x68, 0x0f, 0x12, 0x30, 0x22, 0x23, 0x35, 0x50, + 0x66, 0x64, 0x24, 0x35, 0x93, 0x8a, 0x43, 0x12, 0xfb, 0x43, 0xa2, 0xc6, 0x21, 0xf1, 0xc7, 0x21, 0xd7, 0xe3, 0x80, + 0xdc, 0xfd, 0x92, 0x8d, 0x69, 0xca, 0xc6, 0x74, 0xad, 0x46, 0x85, 0x5e, 0xd1, 0x73, 0x4d, 0x1d, 0xcf, 0xd8, 0x53, + 0x38, 0xb0, 0x07, 0x61, 0x3e, 0x8b, 0x87, 0x4f, 0xa3, 0xa7, 0x84, 0x7c, 0x25, 0xe9, 0xb5, 0xba, 0x94, 0x41, 0x20, + 0xc4, 0x2b, 0x70, 0x2e, 0x75, 0xa1, 0x4e, 0xae, 0xcc, 0x8e, 0xc3, 0xa7, 0xcb, 0xc6, 0xd3, 0x39, 0x44, 0xf4, 0x41, + 0x2b, 0x95, 0x7e, 0x3f, 0xbc, 0x60, 0xe5, 0xfc, 0x2c, 0x1c, 0x13, 0xc0, 0xe1, 0xd1, 0xc3, 0x79, 0x31, 0xba, 0xa5, + 0x17, 0xa3, 0x3b, 0x02, 0x16, 0x5e, 0xe3, 0xe9, 0xfa, 0x98, 0xc5, 0xd3, 0xc1, 0x60, 0x8d, 0x54, 0x5d, 0xe5, 0x5e, + 0x93, 0x05, 0xbd, 0xc0, 0x89, 0x20, 0xc0, 0xd0, 0x67, 0x62, 0x6d, 0x68, 0xf8, 0x53, 0x06, 0x1f, 0xdf, 0xb1, 0x8b, + 0xd1, 0x1d, 0xbd, 0x65, 0x4f, 0xb7, 0xe3, 0x29, 0x30, 0x53, 0xab, 0x59, 0x78, 0x77, 0x7c, 0x39, 0xbb, 0x64, 0x77, + 0xd1, 0xdd, 0x09, 0x34, 0xf4, 0x8a, 0xdd, 0x21, 0xe0, 0x52, 0xfa, 0x70, 0x39, 0x78, 0x4a, 0x0e, 0x07, 0x83, 0x94, + 0x44, 0xe1, 0x75, 0xe8, 0xb5, 0xf2, 0x29, 0xbd, 0x23, 0x74, 0xc5, 0x6e, 0x71, 0x34, 0x2e, 0x19, 0x7e, 0x70, 0xce, + 0xee, 0xea, 0xeb, 0xd0, 0xdb, 0xcd, 0x89, 0xe8, 0x04, 0x31, 0x42, 0x5f, 0x03, 0x47, 0xb3, 0x5c, 0x98, 0x09, 0x78, + 0x32, 0x17, 0x19, 0x2d, 0x0a, 0xcd, 0x40, 0x9c, 0x95, 0x80, 0x58, 0x12, 0x75, 0xbf, 0xd9, 0xe8, 0x0c, 0x96, 0x73, + 0xbf, 0xdf, 0xab, 0x0c, 0x3d, 0x40, 0xe4, 0xcc, 0x4e, 0x7a, 0xd0, 0xf3, 0xe9, 0x01, 0x7e, 0xa2, 0x57, 0x0d, 0xe2, + 0x64, 0xfe, 0xb0, 0x8c, 0x7e, 0xf5, 0xe8, 0xc3, 0x2f, 0xdd, 0x94, 0xa7, 0xcc, 0xff, 0x7d, 0xca, 0x23, 0xf3, 0xe8, + 0x55, 0xe5, 0x81, 0xe0, 0x79, 0x6b, 0x52, 0x69, 0x24, 0xaa, 0xd1, 0xd9, 0x2a, 0x06, 0x6d, 0x24, 0x6a, 0x1b, 0xf4, + 0x13, 0x5a, 0x58, 0x41, 0x84, 0x9c, 0xa3, 0x67, 0x60, 0x90, 0x0a, 0xa1, 0x72, 0xd4, 0xa2, 0x44, 0x43, 0x90, 0x5c, + 0x96, 0x5c, 0x85, 0xcf, 0x21, 0x54, 0x9d, 0x3e, 0xce, 0x44, 0xd8, 0xd0, 0xe3, 0xd0, 0x07, 0x80, 0xff, 0xe7, 0x0e, + 0xb9, 0x28, 0xf9, 0x25, 0x9e, 0xcd, 0x6d, 0x82, 0x51, 0xb0, 0x44, 0x34, 0x43, 0xdb, 0x20, 0xf6, 0x63, 0x49, 0xb0, + 0x1e, 0x49, 0xe3, 0x51, 0x69, 0x8e, 0x08, 0x3f, 0x8a, 0x8f, 0xa2, 0xa7, 0xb1, 0x21, 0x91, 0x1c, 0x49, 0x24, 0x1f, + 0x00, 0xe1, 0x24, 0xe8, 0x2f, 0xee, 0x9a, 0xec, 0x5a, 0x48, 0x0c, 0xfa, 0xd3, 0x92, 0x69, 0xd9, 0xbd, 0xea, 0xb1, + 0xaf, 0x08, 0x72, 0xc7, 0xf4, 0xef, 0x5e, 0x1f, 0xfe, 0x5a, 0xe2, 0x0c, 0x5a, 0xcf, 0x17, 0xd5, 0x99, 0x99, 0x37, + 0xb8, 0x91, 0xd7, 0x65, 0xed, 0xba, 0x7c, 0xc1, 0x0f, 0xf8, 0x6d, 0xc5, 0x45, 0x5a, 0x1e, 0xfc, 0x5c, 0xb5, 0xf1, + 0x9c, 0xca, 0xf5, 0xca, 0xc5, 0x59, 0x51, 0xc6, 0xa9, 0x9e, 0xd4, 0xc5, 0x58, 0xc3, 0x36, 0xfc, 0x1e, 0x51, 0x57, + 0xd2, 0x72, 0xf4, 0x94, 0x72, 0xd5, 0x4c, 0xb9, 0x58, 0xe7, 0xf9, 0x4f, 0x3b, 0xa9, 0x38, 0xc5, 0xcd, 0x14, 0xa4, + 0x4a, 0x2d, 0x17, 0x50, 0x3d, 0x47, 0x2d, 0x77, 0x4b, 0xb3, 0x03, 0x9c, 0xdb, 0xa6, 0xfa, 0x58, 0x99, 0x5d, 0x78, + 0xc9, 0x8d, 0xfb, 0x93, 0x29, 0xc3, 0x82, 0x51, 0x68, 0xb3, 0xea, 0x4a, 0xdb, 0x17, 0x5a, 0xa7, 0x61, 0xb8, 0xf2, + 0xe3, 0x05, 0xa4, 0x0b, 0x18, 0xc7, 0x8b, 0x92, 0x89, 0x71, 0x7b, 0xf4, 0x56, 0x10, 0x5f, 0xb2, 0x15, 0x48, 0xbf, + 0xdf, 0x13, 0xde, 0xae, 0xeb, 0x68, 0xbb, 0x27, 0x4e, 0x19, 0x95, 0xab, 0x58, 0x7c, 0x1f, 0xaf, 0x0c, 0x64, 0xb2, + 0x3a, 0x1e, 0x1b, 0x63, 0x3a, 0xfd, 0x3e, 0x09, 0xfd, 0x42, 0x28, 0xf8, 0xac, 0x97, 0x56, 0x9e, 0xdc, 0x1e, 0x96, + 0x71, 0x8d, 0x5e, 0x89, 0x2b, 0xdd, 0x37, 0x23, 0x85, 0xd4, 0x23, 0x5f, 0x35, 0x05, 0xf4, 0x66, 0xec, 0x9b, 0xa9, + 0x30, 0x6f, 0x7b, 0xc6, 0x5c, 0x21, 0x58, 0xa9, 0xb2, 0xdb, 0x77, 0x6a, 0x4c, 0xc5, 0x0c, 0xa6, 0xd8, 0x76, 0x16, + 0x93, 0x6e, 0xe5, 0x9f, 0x76, 0xee, 0xd3, 0xbc, 0xc3, 0x5d, 0x51, 0xbf, 0x05, 0x2e, 0x34, 0x2b, 0xca, 0xaa, 0x2d, + 0x1b, 0xb6, 0x8d, 0x37, 0xb2, 0x50, 0x6c, 0x80, 0x65, 0xcf, 0x7d, 0x0b, 0x0f, 0x10, 0x37, 0xe1, 0x9e, 0x5d, 0xd4, + 0x70, 0x63, 0xf8, 0xb2, 0x92, 0x7c, 0x57, 0x1a, 0x73, 0xe9, 0x53, 0xa5, 0x89, 0xe1, 0x64, 0x31, 0xe2, 0x22, 0x5d, + 0xd4, 0x99, 0x5d, 0x0b, 0x9f, 0xf1, 0x32, 0x9c, 0xf3, 0x85, 0xd1, 0x4d, 0xe9, 0xd2, 0x0b, 0x96, 0xe8, 0x4e, 0x6f, + 0x56, 0x1a, 0x2b, 0x25, 0xe2, 0xd6, 0x2c, 0x13, 0x28, 0x4b, 0x59, 0x2b, 0xe1, 0x4d, 0xd1, 0xb2, 0x95, 0x34, 0xf2, + 0x9e, 0x39, 0xb8, 0x8f, 0xfd, 0x82, 0x98, 0xc8, 0x26, 0x30, 0x29, 0x1a, 0x3a, 0xa0, 0x5d, 0x75, 0xe1, 0x9b, 0x51, + 0x0f, 0x06, 0xb9, 0x25, 0x89, 0x58, 0x41, 0x8a, 0x15, 0xac, 0x6b, 0x56, 0xcc, 0xf3, 0x05, 0xbd, 0x60, 0x72, 0x9e, + 0x2e, 0xe8, 0x8a, 0xc9, 0xf9, 0x1a, 0x6f, 0x42, 0x17, 0x70, 0x42, 0x92, 0x4d, 0xac, 0x14, 0xb0, 0x17, 0x78, 0x79, + 0xc3, 0x33, 0x55, 0xd3, 0xb2, 0x4b, 0xc5, 0x01, 0xc6, 0xe7, 0x65, 0x18, 0x96, 0xc3, 0x0b, 0xb0, 0x96, 0x38, 0x0c, + 0x57, 0x73, 0xbe, 0x50, 0xbf, 0x21, 0xea, 0x7c, 0x12, 0x2a, 0x76, 0xc1, 0xee, 0x05, 0x32, 0xbd, 0x9a, 0xf3, 0x85, + 0x1a, 0x09, 0x5d, 0xf0, 0x95, 0x35, 0x36, 0x89, 0x3d, 0x41, 0xcb, 0x2c, 0x9e, 0x8f, 0x17, 0x51, 0x5c, 0xc3, 0x32, + 0x7c, 0xaf, 0x66, 0xa6, 0x25, 0xff, 0x49, 0xd4, 0x86, 0x26, 0xfa, 0x06, 0xab, 0xc8, 0x1f, 0x1e, 0x1f, 0x5d, 0x02, + 0x19, 0x3b, 0xbb, 0x92, 0x99, 0x0f, 0x7d, 0x1f, 0x19, 0xdc, 0x73, 0x53, 0xce, 0xb8, 0x0a, 0x12, 0x65, 0xe0, 0xee, + 0xd5, 0x2c, 0x19, 0x6b, 0x11, 0xbe, 0x7b, 0x54, 0x14, 0x7d, 0x26, 0x4d, 0x03, 0xba, 0x8f, 0x04, 0x73, 0xa0, 0xf7, + 0x0a, 0x1d, 0x2e, 0xab, 0x6d, 0x26, 0xe0, 0x2f, 0x12, 0xe4, 0xb7, 0x42, 0xaf, 0x6a, 0x0c, 0xaa, 0x68, 0x17, 0xb1, + 0xf4, 0xef, 0x23, 0x7e, 0x94, 0xcd, 0xdf, 0xcd, 0x3d, 0x5e, 0x49, 0x18, 0xfc, 0x90, 0x9a, 0x4d, 0x32, 0x6f, 0xaf, + 0xd8, 0x7b, 0xe8, 0xa8, 0x47, 0xad, 0xf1, 0xbe, 0x7a, 0xc1, 0x29, 0xc4, 0x28, 0xa1, 0xe8, 0x24, 0x18, 0xc0, 0xed, + 0x12, 0x52, 0xdc, 0x0d, 0x76, 0xd3, 0xbc, 0xe6, 0x45, 0xc1, 0xf9, 0xba, 0xaa, 0x02, 0x3f, 0xa0, 0xe1, 0x7c, 0xb1, + 0x1b, 0xc2, 0x70, 0x4c, 0x5b, 0xd7, 0x30, 0x08, 0x33, 0x86, 0x91, 0x10, 0xbc, 0xfe, 0x45, 0x8f, 0x68, 0x12, 0xaf, + 0xbe, 0xe3, 0x9f, 0x32, 0x5e, 0x28, 0x22, 0x0d, 0x22, 0xa4, 0x6e, 0xe2, 0x1b, 0x99, 0x26, 0x05, 0x14, 0x02, 0x8c, + 0x02, 0x2a, 0xb1, 0xa1, 0xa9, 0xf8, 0x5b, 0x2d, 0x3e, 0xf8, 0xa9, 0xe9, 0x78, 0x34, 0xae, 0x5b, 0x9d, 0x51, 0x41, + 0x67, 0xa0, 0x47, 0xad, 0xa8, 0xa7, 0x41, 0x2b, 0xc1, 0x34, 0xd2, 0xbc, 0x75, 0x0f, 0x81, 0x57, 0xa6, 0xc5, 0x3b, + 0x0f, 0xe8, 0xe6, 0xcc, 0x07, 0x4f, 0x1e, 0xd3, 0x33, 0x87, 0x9e, 0x5c, 0xb1, 0x93, 0xaa, 0x87, 0xda, 0x7b, 0x33, + 0x42, 0x41, 0xbf, 0x8f, 0x29, 0xd0, 0x8d, 0xa0, 0xf6, 0xae, 0xee, 0x95, 0xdc, 0xe5, 0xf0, 0x1d, 0x67, 0xb9, 0x01, + 0x2c, 0x15, 0x59, 0x2b, 0xf0, 0x28, 0x40, 0x5d, 0x2a, 0x43, 0xd8, 0x62, 0x0e, 0x87, 0xca, 0x6e, 0xd5, 0x6a, 0x28, + 0xc9, 0x71, 0x39, 0x02, 0x87, 0xd0, 0x75, 0x39, 0x28, 0x47, 0xcb, 0xac, 0x7a, 0x87, 0xbf, 0x35, 0xeb, 0x90, 0x64, + 0xfb, 0x58, 0x07, 0x6e, 0x59, 0x87, 0xe9, 0x47, 0x83, 0x14, 0x80, 0x26, 0x1b, 0x81, 0x4b, 0x00, 0xde, 0xdb, 0x7f, + 0x44, 0xa8, 0x95, 0xe9, 0x5e, 0xc6, 0x42, 0x7d, 0xdf, 0x48, 0x82, 0x12, 0x9a, 0x09, 0x95, 0x63, 0x29, 0x78, 0xe7, + 0x91, 0xce, 0x49, 0x9d, 0x89, 0x77, 0x20, 0x4e, 0x0b, 0xef, 0xd9, 0x5b, 0x10, 0x9c, 0xb3, 0xa0, 0x77, 0x78, 0x9b, + 0xd5, 0x52, 0x1b, 0x3d, 0x50, 0x00, 0xbf, 0x1b, 0xdc, 0x21, 0xc8, 0x57, 0x63, 0xb8, 0x56, 0xf2, 0x26, 0xe4, 0xc3, + 0x82, 0x1e, 0x91, 0x81, 0x7d, 0x16, 0xc3, 0x98, 0x1e, 0x91, 0x63, 0xfb, 0x2c, 0xdd, 0x00, 0x0e, 0xa4, 0x1e, 0x55, + 0x7a, 0x04, 0x0d, 0xfa, 0xdd, 0xb6, 0xc8, 0x1d, 0x80, 0xd2, 0x28, 0x62, 0xa0, 0x4a, 0x10, 0x51, 0x8b, 0x7f, 0xde, + 0x9b, 0xeb, 0x0e, 0x73, 0x81, 0x30, 0x07, 0x03, 0x0e, 0xe2, 0x36, 0x08, 0xcd, 0x01, 0xb3, 0xb9, 0x8d, 0x04, 0xbd, + 0xb3, 0x86, 0x99, 0x1d, 0xfd, 0xe1, 0x56, 0x82, 0x6f, 0xb2, 0xd6, 0xa8, 0xf3, 0xe2, 0x10, 0x08, 0x82, 0x37, 0x85, + 0xaa, 0xf6, 0xaa, 0x07, 0x36, 0xde, 0xaa, 0x1f, 0xdb, 0xed, 0x78, 0x2a, 0xdc, 0xb5, 0x5f, 0x50, 0x38, 0xf9, 0x94, + 0xfc, 0xeb, 0x9d, 0xc9, 0xe0, 0xc0, 0xc8, 0xf0, 0xa5, 0xb7, 0x7f, 0xe1, 0x6b, 0x2d, 0xdd, 0x13, 0x83, 0x92, 0x3c, + 0x3c, 0x52, 0xf4, 0x6f, 0xaf, 0xac, 0x7c, 0x6a, 0xa7, 0x7f, 0xbb, 0x35, 0xeb, 0xf3, 0x78, 0x34, 0xd9, 0x6e, 0x7b, + 0x71, 0xa5, 0x3d, 0xd6, 0xf4, 0x82, 0x40, 0xe7, 0x7a, 0x72, 0x78, 0x04, 0x51, 0x11, 0x9a, 0x71, 0x37, 0xcb, 0x86, + 0x44, 0xc6, 0x8f, 0xd3, 0x59, 0x36, 0x04, 0x3b, 0xdc, 0x8b, 0x4a, 0x5c, 0x8e, 0x5a, 0x1b, 0x9c, 0xde, 0x25, 0x21, + 0x84, 0x72, 0xc0, 0xca, 0x6e, 0xd5, 0x9f, 0x3b, 0x65, 0x26, 0xa4, 0x26, 0xab, 0xdb, 0x29, 0xdd, 0xc3, 0x34, 0x3f, + 0x30, 0x23, 0x38, 0xe0, 0xde, 0xfe, 0xaa, 0x3f, 0x86, 0x49, 0xa6, 0xc9, 0x29, 0x92, 0x5f, 0xa4, 0xa7, 0x90, 0xb4, + 0x43, 0x4f, 0x15, 0x01, 0x9c, 0x50, 0xfb, 0x31, 0xfc, 0x86, 0x71, 0xff, 0xae, 0xf9, 0xda, 0x4d, 0x45, 0xf4, 0x98, + 0x62, 0x99, 0x9a, 0x9c, 0x26, 0x59, 0x91, 0x40, 0xd4, 0x46, 0xd5, 0x8c, 0xe8, 0x91, 0x8b, 0xf9, 0xa8, 0x08, 0x9f, + 0x57, 0xeb, 0xff, 0x0c, 0xe1, 0x33, 0x0a, 0x37, 0x80, 0xcb, 0x2b, 0x2e, 0xcf, 0xc3, 0x27, 0x8f, 0xe9, 0xc1, 0xe4, + 0x9b, 0x23, 0x7a, 0x70, 0xf4, 0xe8, 0x09, 0x01, 0x58, 0xb4, 0xcb, 0xf3, 0xf0, 0xe8, 0xc9, 0x13, 0x7a, 0xf0, 0xed, + 0xb7, 0xf4, 0x60, 0xf2, 0xe8, 0xa8, 0x91, 0x36, 0x79, 0xf2, 0x2d, 0x3d, 0xf8, 0xe6, 0x71, 0x23, 0xed, 0x68, 0xfc, + 0x84, 0x1e, 0xfc, 0xfd, 0x1b, 0x93, 0xf6, 0x37, 0xc8, 0xf6, 0xed, 0x11, 0xfe, 0x67, 0xd2, 0x26, 0x4f, 0x1e, 0xd1, + 0x83, 0xc9, 0x18, 0x2a, 0x79, 0xe2, 0x2a, 0x19, 0x4f, 0xe0, 0xe3, 0x47, 0xf0, 0xdf, 0xdf, 0x08, 0x6c, 0x02, 0xc9, + 0x72, 0x81, 0xfa, 0x33, 0x14, 0x71, 0xa2, 0x6a, 0x22, 0xe1, 0x21, 0x66, 0x56, 0xdf, 0xc4, 0x61, 0x40, 0x5c, 0x3a, + 0x14, 0x44, 0x0f, 0xc6, 0xa3, 0x27, 0x24, 0xf0, 0xe1, 0xe9, 0x3e, 0xfa, 0x20, 0x63, 0xb9, 0x98, 0x67, 0x5f, 0xe5, + 0x26, 0xb6, 0x82, 0x07, 0x60, 0xf5, 0xde, 0xcf, 0xc5, 0xe5, 0x3c, 0xfb, 0x8a, 0xcb, 0xdd, 0x5c, 0xff, 0x68, 0x01, + 0xca, 0xfb, 0xab, 0x96, 0x7d, 0x2c, 0x54, 0xe8, 0xb4, 0xd6, 0xe8, 0xb3, 0xf7, 0x98, 0x3e, 0x18, 0x78, 0x37, 0xec, + 0xef, 0x77, 0xca, 0x69, 0x7d, 0xa3, 0x51, 0xa8, 0x51, 0x79, 0x48, 0xd8, 0x09, 0x14, 0x3d, 0x18, 0x00, 0x4f, 0xe0, + 0xe1, 0xbe, 0xfd, 0x9b, 0x65, 0xbc, 0xef, 0x28, 0xe3, 0x5f, 0x28, 0x43, 0x40, 0xa3, 0x1e, 0x66, 0x37, 0x3d, 0x6c, + 0x74, 0xab, 0x97, 0x2c, 0xd5, 0xc9, 0xd4, 0xf4, 0x0c, 0xf6, 0xb5, 0xae, 0xe5, 0x81, 0x11, 0x45, 0xcb, 0x8b, 0x83, + 0x94, 0xcf, 0x2a, 0xf6, 0xfd, 0x12, 0xd5, 0x5b, 0x51, 0xe3, 0x8d, 0xcc, 0x66, 0x15, 0xfb, 0xd9, 0xbc, 0x01, 0x6e, + 0x86, 0xfd, 0x43, 0x3d, 0xf9, 0x81, 0x33, 0x32, 0x69, 0xdb, 0xa3, 0x4c, 0x8c, 0x00, 0x2b, 0x20, 0x03, 0x07, 0x1e, + 0x00, 0x1d, 0xf4, 0x47, 0x7b, 0xbb, 0x55, 0x29, 0xcd, 0x3e, 0x5b, 0x18, 0x40, 0xc3, 0xbc, 0x4d, 0x3c, 0x54, 0xb3, + 0x86, 0xbc, 0x04, 0x85, 0x5b, 0xcd, 0xf2, 0x76, 0x0a, 0x43, 0x08, 0xc1, 0x2a, 0x65, 0x00, 0x38, 0x10, 0x60, 0x30, + 0xd6, 0x32, 0xa0, 0x66, 0xcb, 0x47, 0x1b, 0xae, 0xd4, 0x93, 0xc0, 0x19, 0x5c, 0xc8, 0x22, 0xe1, 0x6f, 0xb4, 0xd8, + 0x1f, 0xad, 0x1f, 0x7d, 0xdf, 0x1e, 0x0f, 0xd6, 0xbe, 0xc7, 0x47, 0xfa, 0xb3, 0xc6, 0x75, 0x60, 0xd3, 0xf2, 0x8d, + 0x17, 0xb5, 0x95, 0x78, 0x94, 0xc0, 0x1b, 0x98, 0x88, 0x14, 0x06, 0xa9, 0x16, 0x38, 0x06, 0xe5, 0x8d, 0x85, 0x58, + 0xaa, 0xae, 0x6e, 0xe8, 0x96, 0x0c, 0xc1, 0xc3, 0xed, 0xc7, 0xa5, 0x0a, 0x1c, 0xd5, 0xef, 0x67, 0xd2, 0x77, 0x7b, + 0x32, 0x76, 0xe4, 0x38, 0xf5, 0x53, 0xe1, 0xe0, 0xbf, 0x49, 0x5d, 0x1b, 0xbb, 0xfb, 0x94, 0x59, 0x96, 0x85, 0x9d, + 0x84, 0x5a, 0xee, 0x51, 0x79, 0x90, 0x7c, 0x21, 0x87, 0x48, 0x16, 0x18, 0x85, 0x82, 0x0c, 0x27, 0x54, 0x8c, 0xd6, + 0xa2, 0x5c, 0x66, 0x17, 0x55, 0xb8, 0x51, 0x0a, 0x65, 0x4e, 0xd1, 0xb7, 0x1b, 0x1c, 0x48, 0x48, 0x94, 0x95, 0xaf, + 0xe3, 0xd7, 0x21, 0x82, 0xd5, 0x71, 0x6d, 0x0b, 0xc5, 0xbd, 0xfd, 0x99, 0xa5, 0x5d, 0xfc, 0x91, 0x71, 0x01, 0x75, + 0xb1, 0x98, 0x86, 0x13, 0xab, 0xdf, 0x71, 0x5f, 0x58, 0x4d, 0x0f, 0x40, 0x7d, 0x97, 0x4a, 0x8c, 0xa0, 0xbe, 0x32, + 0xf6, 0xb1, 0x3d, 0xc6, 0xe4, 0x0c, 0x62, 0x0d, 0xeb, 0xbb, 0x9d, 0xea, 0x1b, 0x61, 0x27, 0x00, 0xdc, 0x08, 0xad, + 0xd1, 0x91, 0x49, 0xaa, 0x10, 0xcf, 0x4b, 0x15, 0xbe, 0x35, 0x23, 0x74, 0x0c, 0xde, 0x54, 0xb6, 0x91, 0x42, 0xfa, + 0x82, 0x41, 0x73, 0x6c, 0xeb, 0x28, 0xac, 0xb6, 0xb2, 0xec, 0x04, 0xe0, 0x06, 0xb2, 0x63, 0x73, 0xf1, 0x9c, 0x55, + 0xf3, 0x6c, 0x11, 0x99, 0xa0, 0x80, 0x4b, 0x61, 0x19, 0xb4, 0xd7, 0x7b, 0x64, 0x3b, 0x0e, 0xa1, 0x1b, 0xee, 0x23, + 0x18, 0x4f, 0xbb, 0x29, 0x58, 0x41, 0x34, 0x42, 0x3c, 0xcc, 0x98, 0xc5, 0xf7, 0x4a, 0x53, 0x9e, 0xaa, 0x96, 0x40, + 0xe0, 0x28, 0x84, 0xba, 0xd8, 0x35, 0x4a, 0x70, 0x99, 0x1a, 0xc1, 0x0c, 0x76, 0xec, 0x48, 0x6d, 0x97, 0x9c, 0xd3, + 0xa1, 0x9a, 0xd2, 0x52, 0x4f, 0xa9, 0xf6, 0x35, 0x14, 0xf3, 0x12, 0x3d, 0xf4, 0xc0, 0xf5, 0x40, 0x3b, 0xe4, 0x95, + 0x74, 0x62, 0x22, 0xe8, 0xb4, 0xda, 0x84, 0x9d, 0x1b, 0xe9, 0x96, 0xd5, 0xc8, 0x3b, 0x86, 0x66, 0x47, 0xbc, 0xf4, + 0x03, 0x75, 0x01, 0x44, 0xc8, 0xde, 0x16, 0x99, 0xd9, 0x67, 0x59, 0xf9, 0x02, 0xca, 0xe2, 0x88, 0xad, 0x2b, 0xe0, + 0x5a, 0x0a, 0x26, 0x97, 0x3c, 0xca, 0x52, 0x44, 0x04, 0x3c, 0x55, 0xda, 0xf5, 0x9d, 0x96, 0x10, 0x2a, 0x52, 0x20, + 0x6e, 0x2e, 0x8a, 0x73, 0x6d, 0x03, 0x59, 0x00, 0x7d, 0xfb, 0x29, 0xbb, 0xf2, 0xc2, 0xc1, 0x6e, 0xae, 0x32, 0xf1, + 0x8c, 0x5f, 0x64, 0x82, 0xa7, 0x08, 0x76, 0x75, 0x6b, 0x1e, 0xb8, 0x63, 0xdb, 0xc0, 0xf2, 0xed, 0x3b, 0x58, 0x30, + 0x65, 0xa8, 0x95, 0x12, 0x99, 0x88, 0x04, 0x64, 0xf6, 0x99, 0xbb, 0x57, 0x99, 0x78, 0x15, 0xdf, 0x82, 0x37, 0x45, + 0x83, 0x9f, 0x1e, 0x9d, 0xe3, 0x97, 0x88, 0x24, 0x0a, 0x31, 0x6c, 0x31, 0x22, 0x16, 0x22, 0xc7, 0x8e, 0x09, 0xe5, + 0x4a, 0xd0, 0xda, 0x1a, 0x02, 0x2f, 0xfe, 0xb4, 0xea, 0xde, 0x55, 0x26, 0x8c, 0x7d, 0xc6, 0x55, 0x7c, 0xcb, 0x4a, + 0x05, 0x66, 0x81, 0x71, 0xee, 0xdb, 0x52, 0x92, 0xab, 0x4c, 0x18, 0x01, 0xc9, 0x55, 0x7c, 0x4b, 0x9b, 0x32, 0x0e, + 0x6d, 0x45, 0xe7, 0xc5, 0xf9, 0xdd, 0x1d, 0x7e, 0x89, 0xa1, 0x56, 0xc6, 0xfd, 0x3e, 0x48, 0xcc, 0xa4, 0x6d, 0xca, + 0x4c, 0x46, 0x52, 0xa3, 0x85, 0x54, 0x94, 0x0f, 0x26, 0x64, 0x77, 0xa5, 0x5a, 0x46, 0xd4, 0x7e, 0x15, 0x8a, 0xd9, + 0x38, 0x9a, 0x10, 0x3a, 0xe9, 0x58, 0xef, 0xa6, 0xb5, 0x90, 0x69, 0xf4, 0x24, 0xf2, 0x7c, 0x3a, 0x0b, 0x56, 0x4d, + 0x8b, 0x63, 0xc6, 0xa7, 0xc5, 0x60, 0x40, 0xb4, 0x4b, 0xe1, 0x06, 0xeb, 0x01, 0x53, 0x1a, 0x17, 0x6f, 0xcd, 0xb4, + 0xfa, 0x85, 0x54, 0x21, 0xe9, 0x3d, 0x03, 0x12, 0x21, 0x5d, 0xb0, 0x5b, 0x90, 0x28, 0x7a, 0xfe, 0x77, 0x6a, 0x0b, + 0xee, 0x7a, 0x30, 0x36, 0xa3, 0xfb, 0x7a, 0xc6, 0x7f, 0xa8, 0x6d, 0x41, 0xd4, 0xa7, 0x92, 0xf5, 0x3a, 0x12, 0x55, + 0xc8, 0x45, 0xf8, 0xd9, 0xd1, 0x10, 0x43, 0x54, 0x7b, 0x2c, 0x10, 0xeb, 0xab, 0x73, 0x5e, 0xe0, 0xf4, 0x33, 0x77, + 0xb9, 0x82, 0x6d, 0x41, 0x2b, 0x43, 0xa3, 0x5e, 0xc7, 0xaf, 0x23, 0x7b, 0x59, 0xd0, 0x45, 0x3e, 0x43, 0x21, 0x6b, + 0x1e, 0x86, 0xd5, 0xb0, 0x3d, 0x88, 0xe4, 0xb0, 0x3d, 0x09, 0x8d, 0xc6, 0xc0, 0x02, 0xd9, 0xa1, 0x11, 0xb8, 0x08, + 0xad, 0xfc, 0xed, 0x18, 0x5c, 0xb8, 0x2c, 0x22, 0xcb, 0x50, 0xc7, 0x6f, 0x6a, 0x37, 0x41, 0xf5, 0x0a, 0x9d, 0xa6, + 0xb0, 0x2a, 0x65, 0x92, 0x0f, 0xbf, 0x5e, 0xc8, 0x02, 0x33, 0x79, 0x5d, 0xf6, 0xe8, 0x6b, 0xbb, 0xbd, 0x03, 0x53, + 0xb0, 0xee, 0x93, 0xf7, 0xf5, 0xc3, 0xce, 0x9e, 0x80, 0x51, 0xac, 0xca, 0xd1, 0x14, 0x52, 0x6a, 0x1f, 0x94, 0xfa, + 0x63, 0xb8, 0x14, 0x9a, 0x63, 0xb7, 0x80, 0x49, 0xc0, 0x3e, 0x43, 0xaa, 0xc7, 0xb4, 0x63, 0x9f, 0xa3, 0x0d, 0x2c, + 0x09, 0x38, 0xfc, 0x23, 0x21, 0x6b, 0xff, 0xea, 0x5e, 0xa6, 0xcd, 0x90, 0x2d, 0xf3, 0x05, 0xf0, 0xf9, 0xb0, 0x6b, + 0xa3, 0x12, 0x65, 0x13, 0x91, 0xa4, 0xb0, 0xe5, 0x31, 0x48, 0x7b, 0x14, 0xd3, 0x55, 0xc1, 0x93, 0x0c, 0xa5, 0x14, + 0x89, 0xf6, 0x09, 0xce, 0xe1, 0x0d, 0xee, 0x47, 0x15, 0x10, 0x5e, 0x85, 0x9c, 0x8e, 0x52, 0xaa, 0x2d, 0x60, 0x14, + 0xf5, 0x00, 0x51, 0x5e, 0x06, 0x72, 0xbc, 0xed, 0x76, 0x42, 0x57, 0x6c, 0x39, 0x9c, 0x50, 0x24, 0x25, 0x97, 0x58, + 0xee, 0x15, 0xe8, 0x3c, 0xce, 0x59, 0xef, 0x25, 0x60, 0x11, 0x9c, 0xc1, 0xdf, 0x98, 0xd0, 0x6b, 0xf8, 0x9b, 0x13, + 0xfa, 0x94, 0x85, 0x57, 0xc3, 0x4b, 0x72, 0x18, 0xa6, 0x83, 0x89, 0x12, 0x8c, 0xdd, 0xb1, 0xb4, 0x0c, 0x55, 0xe2, + 0xea, 0xf0, 0x82, 0x3c, 0xbc, 0xa0, 0xb7, 0xf4, 0x86, 0xbe, 0xa2, 0x6f, 0x80, 0xf0, 0xdf, 0x1d, 0x4f, 0xf8, 0x70, + 0xf2, 0xb8, 0xdf, 0xef, 0x9d, 0xf7, 0xfb, 0xbd, 0x33, 0x63, 0x40, 0xa1, 0x77, 0xd1, 0x65, 0x4d, 0xf5, 0xaf, 0xab, + 0x7a, 0x31, 0x7d, 0xa3, 0x36, 0x6e, 0xc2, 0xb3, 0x3c, 0xbc, 0x3a, 0xbc, 0x23, 0x43, 0x7c, 0xbc, 0xc8, 0xa5, 0x2c, + 0xc2, 0xcb, 0xc3, 0x3b, 0x42, 0xdf, 0x9c, 0x80, 0xde, 0x14, 0xeb, 0x7b, 0xf3, 0xf0, 0x4e, 0xd7, 0x46, 0xe8, 0xcb, + 0x30, 0x81, 0x6d, 0x72, 0xcb, 0xec, 0x5d, 0x7b, 0x32, 0x86, 0x58, 0x26, 0x77, 0x5e, 0x79, 0x77, 0x0f, 0x6f, 0xc9, + 0xe1, 0x2d, 0x78, 0x8a, 0x5a, 0xf2, 0x37, 0x0b, 0x6f, 0x58, 0xab, 0x86, 0x87, 0x77, 0xf4, 0x55, 0xab, 0x11, 0x0f, + 0xef, 0x48, 0x14, 0xde, 0xb0, 0x4b, 0xfa, 0x8a, 0x5d, 0x11, 0x7a, 0xde, 0xef, 0x9f, 0xf5, 0xfb, 0xb2, 0xdf, 0xff, + 0x3e, 0x0e, 0xc3, 0x78, 0x58, 0x90, 0x43, 0x49, 0xef, 0x0e, 0x27, 0xfc, 0x11, 0x99, 0x85, 0xba, 0xf9, 0x6a, 0xc1, + 0x59, 0x95, 0xb7, 0xca, 0x75, 0x47, 0xc1, 0x5a, 0xe1, 0x8e, 0xa9, 0xa7, 0x37, 0xf4, 0x86, 0x15, 0xf4, 0x15, 0x8b, + 0x49, 0x74, 0x0d, 0xad, 0x38, 0x9f, 0x15, 0xd1, 0x0d, 0x7d, 0xc5, 0xce, 0x66, 0x71, 0xf4, 0x8a, 0xbe, 0x61, 0xf9, + 0x70, 0x02, 0x79, 0x5f, 0x0d, 0x6f, 0xc8, 0xe1, 0x1b, 0x12, 0x85, 0x6f, 0xf4, 0xef, 0x3b, 0x7a, 0xc9, 0xc3, 0x37, + 0xd4, 0xab, 0xe6, 0x0d, 0x31, 0xd5, 0x37, 0x6a, 0x7f, 0x43, 0x22, 0x7f, 0x30, 0xdf, 0x58, 0x7b, 0x9a, 0xb7, 0x8e, + 0x36, 0xae, 0xcb, 0xf0, 0x8e, 0xd0, 0x75, 0x19, 0xde, 0x10, 0x32, 0x6d, 0x8e, 0x1d, 0x0c, 0xe8, 0xec, 0x6d, 0x94, + 0x10, 0x7a, 0xe3, 0x97, 0x7a, 0x83, 0x63, 0x68, 0x46, 0x48, 0xf7, 0x13, 0xd3, 0x70, 0x1d, 0x7c, 0xd0, 0x60, 0x1d, + 0xe7, 0xfd, 0x7e, 0xb8, 0xee, 0xf7, 0x21, 0xd2, 0x7d, 0x31, 0x33, 0xb1, 0xdd, 0x1c, 0xd9, 0xa4, 0x37, 0xa0, 0xfd, + 0xff, 0x30, 0x18, 0x40, 0x67, 0xbc, 0x92, 0xc2, 0x9b, 0xc1, 0x87, 0x87, 0x77, 0x44, 0xd5, 0x51, 0xd0, 0x52, 0x86, + 0x05, 0x7d, 0x4a, 0x33, 0x00, 0xfc, 0xfa, 0x30, 0x18, 0x90, 0xc8, 0x7c, 0x46, 0xa6, 0x1f, 0x8e, 0xdf, 0x4c, 0x07, + 0x83, 0x0f, 0x66, 0x9b, 0x7c, 0x62, 0x7b, 0x4a, 0x81, 0xf5, 0x77, 0xd6, 0xef, 0x7f, 0x3a, 0x89, 0xc9, 0x79, 0xc1, + 0xe3, 0x8f, 0xd3, 0x66, 0x5b, 0x3e, 0xb9, 0xa8, 0x6a, 0x67, 0xfd, 0xfe, 0xba, 0xdf, 0x7f, 0x05, 0xd8, 0x45, 0x33, + 0xe7, 0xeb, 0x09, 0xd2, 0x96, 0xb9, 0xa3, 0x48, 0x9a, 0xe4, 0xd0, 0x18, 0xda, 0x16, 0xab, 0xb6, 0xcd, 0x3a, 0x32, + 0xb0, 0x38, 0x6a, 0x56, 0x14, 0xd7, 0x24, 0x0a, 0x7b, 0x67, 0xdb, 0xed, 0x2b, 0xc6, 0x58, 0x4c, 0x40, 0xfa, 0xe1, + 0xbf, 0x7e, 0x55, 0x37, 0x62, 0x88, 0x95, 0x4a, 0x7c, 0xb7, 0x59, 0xda, 0x43, 0x20, 0xe2, 0xb0, 0xe9, 0xdf, 0x99, + 0x7b, 0xb9, 0xa8, 0x1d, 0xdf, 0xfa, 0x2f, 0x00, 0x21, 0x92, 0x2c, 0xe4, 0x33, 0x1c, 0x83, 0x32, 0x03, 0x20, 0xf3, + 0x48, 0xcd, 0xbc, 0x04, 0x10, 0x60, 0xb2, 0xdd, 0x8e, 0xc6, 0xe3, 0x09, 0x2d, 0xd8, 0xe8, 0x6f, 0x4f, 0x1e, 0x56, + 0x0f, 0xc3, 0x20, 0x18, 0x64, 0xa4, 0xa5, 0xa7, 0xb0, 0x8b, 0xb5, 0x3a, 0x04, 0x23, 0x78, 0xcd, 0x3e, 0x5e, 0x67, + 0x5f, 0xcc, 0x3e, 0x22, 0x61, 0x6d, 0x30, 0x8e, 0x5c, 0xa4, 0x2d, 0xbd, 0xdd, 0x1e, 0x06, 0x93, 0x8b, 0xf4, 0x33, + 0x6c, 0xa7, 0xcf, 0xbf, 0x79, 0x30, 0x9e, 0x70, 0x30, 0xba, 0x8b, 0x82, 0x3e, 0xd3, 0xb6, 0xdb, 0xca, 0xbf, 0x04, + 0xbe, 0xc6, 0x54, 0xd0, 0xb1, 0x59, 0x16, 0x6e, 0x50, 0x11, 0x75, 0xb4, 0x0c, 0xaa, 0x5a, 0xd9, 0xce, 0x01, 0xb5, + 0xc4, 0xaa, 0x4c, 0xdc, 0x02, 0xc3, 0x90, 0xa1, 0x2e, 0xf7, 0xb4, 0xfa, 0x17, 0x2f, 0xa4, 0x81, 0xcf, 0x70, 0x22, + 0x42, 0x8f, 0x5b, 0xe3, 0x3e, 0xb7, 0x26, 0x3e, 0xc3, 0xad, 0x95, 0x48, 0x62, 0x0d, 0x2c, 0xa9, 0xb9, 0x1c, 0x25, + 0xec, 0xa4, 0x64, 0x7c, 0x56, 0x46, 0x09, 0x8d, 0xe1, 0x41, 0x32, 0x31, 0x93, 0x51, 0x82, 0xf6, 0x89, 0x2e, 0xc2, + 0xe0, 0x5f, 0x80, 0xd9, 0x4f, 0x73, 0xf8, 0x2b, 0xc9, 0x34, 0x39, 0x86, 0x80, 0x10, 0xc7, 0xe3, 0x59, 0x1c, 0x8e, + 0x49, 0x94, 0x9c, 0xc0, 0x13, 0xfc, 0x57, 0x84, 0x63, 0x52, 0xeb, 0x3b, 0x8c, 0x54, 0x97, 0xdb, 0x84, 0x01, 0x5c, + 0xd9, 0x78, 0x36, 0x89, 0xac, 0x74, 0x57, 0x3e, 0x1c, 0x8d, 0x9f, 0x90, 0x69, 0x1c, 0xca, 0x41, 0x42, 0x28, 0x78, + 0xf7, 0x86, 0xe5, 0x30, 0xd1, 0xf0, 0x6c, 0xc0, 0xe6, 0x95, 0x8e, 0xcd, 0x93, 0x70, 0x02, 0xc2, 0x30, 0x21, 0xc7, + 0xba, 0x07, 0x29, 0x45, 0x9f, 0xe7, 0xd8, 0x4f, 0x7d, 0x04, 0x61, 0x76, 0xd4, 0x52, 0xf1, 0x15, 0x00, 0x5d, 0xe2, + 0xe0, 0x50, 0x7b, 0xe6, 0x8b, 0x59, 0x58, 0x7a, 0x54, 0xca, 0x54, 0x77, 0x28, 0x1a, 0x94, 0xdf, 0x34, 0xe8, 0x50, + 0x90, 0xc1, 0x84, 0x96, 0x27, 0x13, 0xfe, 0x08, 0x02, 0x78, 0x34, 0x22, 0x7e, 0x29, 0x9c, 0x18, 0x08, 0xaf, 0x82, + 0x0c, 0x54, 0x5a, 0xab, 0xc6, 0x8c, 0x6c, 0xc5, 0x07, 0x10, 0x26, 0xe5, 0xe0, 0x46, 0xae, 0xf3, 0x14, 0xa2, 0x82, + 0xad, 0xf3, 0xea, 0xe0, 0x12, 0x2c, 0xd9, 0xe3, 0x0a, 0xe2, 0x84, 0xad, 0x57, 0x80, 0x9d, 0xfb, 0x60, 0x53, 0xd6, + 0x07, 0xea, 0xbb, 0x03, 0x6c, 0x39, 0xbc, 0xaa, 0xe4, 0xc1, 0x64, 0x3c, 0x1e, 0x8f, 0xfe, 0x80, 0xa3, 0x03, 0x08, + 0x2d, 0x89, 0x0c, 0x9f, 0x0c, 0xd0, 0xb8, 0xeb, 0x8a, 0x7b, 0xe3, 0x42, 0x51, 0x56, 0x3a, 0x99, 0x10, 0x10, 0x3f, + 0x9b, 0xbe, 0xc1, 0xbe, 0xe2, 0x3a, 0xfe, 0xc9, 0xee, 0x27, 0x66, 0x45, 0xab, 0x95, 0x3a, 0x7a, 0xfb, 0xe6, 0xfd, + 0xcb, 0x0f, 0x2f, 0x7f, 0x7d, 0x7e, 0xf6, 0xf2, 0xf5, 0x8b, 0x97, 0xaf, 0x5f, 0x7e, 0xf8, 0xe7, 0x3d, 0x0c, 0xb6, + 0x6f, 0x2b, 0x62, 0xc7, 0xde, 0xbb, 0xc7, 0x78, 0xb5, 0xf8, 0xc2, 0xd9, 0x23, 0x77, 0x8b, 0x05, 0xd8, 0x04, 0xc3, + 0x2d, 0x08, 0xaa, 0x19, 0x8d, 0x4a, 0xdf, 0x13, 0x90, 0xd1, 0xa8, 0x90, 0x8d, 0x87, 0x15, 0x5b, 0x21, 0x17, 0xef, + 0x18, 0x0e, 0x3e, 0xb2, 0xbf, 0x15, 0x67, 0xc2, 0xed, 0x68, 0x6b, 0x56, 0x04, 0x7c, 0xbe, 0xd6, 0xa2, 0xf2, 0xb8, + 0x10, 0xb5, 0xb7, 0xed, 0x73, 0x48, 0xa8, 0x47, 0xe4, 0x3a, 0x78, 0xdf, 0x06, 0xd9, 0xe3, 0x23, 0xef, 0x49, 0x79, + 0x86, 0xfa, 0x1c, 0x0d, 0x1f, 0x35, 0x9e, 0xd1, 0x89, 0xb9, 0x36, 0x3a, 0xd4, 0xb3, 0x02, 0xf6, 0xb7, 0x12, 0x63, + 0xd3, 0x82, 0x95, 0x29, 0x62, 0x7d, 0x38, 0xdd, 0xef, 0xee, 0xcd, 0xe8, 0x67, 0x38, 0x7e, 0x94, 0x6a, 0x02, 0x69, + 0x51, 0xa0, 0x74, 0x65, 0xc8, 0x6d, 0xcf, 0xc2, 0xc2, 0xfc, 0x0c, 0x1b, 0x04, 0xd0, 0x5e, 0x76, 0x2c, 0x09, 0x34, + 0x8b, 0xd7, 0xba, 0xfe, 0x79, 0xf9, 0x32, 0xd1, 0xce, 0x17, 0xdf, 0x42, 0x88, 0x61, 0xff, 0x8a, 0xd0, 0x98, 0x70, + 0x37, 0xc9, 0xee, 0xd2, 0x62, 0xee, 0x55, 0x57, 0x31, 0x1e, 0x77, 0x7b, 0xae, 0x14, 0xcd, 0x5b, 0x17, 0xd8, 0x03, + 0x35, 0xaf, 0xe3, 0x25, 0x0b, 0x01, 0x9b, 0xf1, 0xd0, 0x2e, 0x12, 0xe7, 0xf7, 0x4e, 0x27, 0xe4, 0xf0, 0x68, 0xca, + 0x87, 0xac, 0xa4, 0x62, 0xc0, 0xca, 0x7a, 0x87, 0x9a, 0xf3, 0x36, 0x21, 0x17, 0xbb, 0x34, 0x5c, 0x0c, 0xf9, 0x7d, + 0x97, 0xa4, 0x0f, 0xbc, 0xe1, 0x50, 0x6d, 0x9b, 0x8b, 0x21, 0x4d, 0x39, 0xdd, 0xa5, 0x32, 0x20, 0x44, 0xba, 0x8a, + 0x2b, 0x52, 0xeb, 0xa3, 0x2a, 0x75, 0x92, 0x8e, 0xeb, 0x6c, 0xf3, 0x99, 0x4b, 0xb6, 0xba, 0x5d, 0xfb, 0xd7, 0xea, + 0xf6, 0x85, 0x19, 0xc8, 0xdf, 0x9f, 0x88, 0x6a, 0x62, 0x20, 0xba, 0x80, 0x0a, 0xfe, 0x09, 0x5e, 0x9e, 0x3c, 0xd2, + 0x0a, 0xd0, 0x7d, 0x67, 0x47, 0xd7, 0x1e, 0x6f, 0xcc, 0x62, 0x6b, 0x89, 0x73, 0x56, 0xf9, 0xce, 0xf2, 0xaa, 0x6c, + 0x85, 0xae, 0x23, 0xd8, 0xbf, 0xc2, 0x8e, 0xbe, 0x7b, 0xdb, 0x00, 0x88, 0x52, 0x58, 0xb9, 0xb3, 0x5f, 0x78, 0x67, + 0xbf, 0xb0, 0x67, 0xbf, 0xdd, 0x04, 0xca, 0x87, 0x15, 0x5a, 0xf6, 0x42, 0x8a, 0xca, 0x34, 0x79, 0xdc, 0xd4, 0x65, + 0x21, 0x2d, 0xe6, 0x87, 0x96, 0x76, 0x3d, 0x1e, 0x53, 0x89, 0xea, 0x91, 0x1f, 0xb0, 0x55, 0x87, 0x25, 0xb9, 0xff, + 0x9e, 0xf9, 0x3f, 0x7b, 0x83, 0xdc, 0x77, 0xb7, 0xfb, 0xbf, 0xb9, 0xd0, 0xc1, 0x6d, 0x2d, 0x15, 0x9e, 0xba, 0x3a, + 0x2e, 0xf0, 0xae, 0x96, 0xde, 0x7f, 0x57, 0x7b, 0x90, 0xe9, 0x65, 0x57, 0x01, 0x6a, 0x90, 0x58, 0x5f, 0xf1, 0x22, + 0x4b, 0x6a, 0xab, 0xd0, 0x78, 0xc3, 0x21, 0xb4, 0x87, 0x77, 0x70, 0x81, 0x1c, 0x96, 0x10, 0xfa, 0xb1, 0x32, 0x02, + 0x40, 0x9f, 0xc5, 0x7e, 0xc3, 0xc3, 0x8c, 0x0c, 0x7c, 0x89, 0x9f, 0x94, 0xbe, 0xb8, 0xf8, 0x70, 0x27, 0x33, 0x41, + 0xaf, 0x12, 0x17, 0x35, 0x57, 0xb6, 0x63, 0x7e, 0xf8, 0x5f, 0x60, 0x34, 0x08, 0xaf, 0x2d, 0xd9, 0xa1, 0xe8, 0x98, + 0xe5, 0x0a, 0x8e, 0xda, 0xd2, 0x95, 0x29, 0x5b, 0xd7, 0xcf, 0x6a, 0x98, 0xe9, 0x33, 0xe5, 0x0d, 0xc8, 0xbe, 0x90, + 0xbb, 0x9f, 0xea, 0x8a, 0x05, 0x39, 0x99, 0x8c, 0xa7, 0x44, 0x0c, 0x06, 0xad, 0xe4, 0x63, 0x4c, 0x1e, 0x0e, 0x77, + 0x98, 0x4b, 0xa1, 0xfb, 0xe1, 0xf5, 0x01, 0xea, 0x6b, 0x6c, 0x49, 0xb2, 0xa9, 0xd8, 0x5f, 0x60, 0x16, 0x0b, 0xc4, + 0xd1, 0xc1, 0x2f, 0xce, 0x17, 0x00, 0xb2, 0x0c, 0xcb, 0x4c, 0x0b, 0x8b, 0x64, 0xaa, 0x7c, 0x64, 0x0b, 0x26, 0x8f, + 0xc7, 0x33, 0xbf, 0xe7, 0x8e, 0xc1, 0x21, 0x24, 0x9a, 0x58, 0xe3, 0x17, 0x3f, 0x0b, 0xc6, 0x71, 0x28, 0x4f, 0x64, + 0xe3, 0xbb, 0x92, 0x44, 0x63, 0x63, 0xaa, 0xac, 0xaf, 0x12, 0xd5, 0x30, 0x21, 0x0f, 0x0b, 0x72, 0x58, 0xd0, 0xa5, + 0x3f, 0x96, 0x98, 0x7e, 0x18, 0x1f, 0x4e, 0xc6, 0xe4, 0x61, 0xfc, 0x70, 0x62, 0xe0, 0x86, 0xfd, 0x1c, 0xf9, 0x70, + 0x49, 0x0e, 0x9b, 0x55, 0x82, 0x29, 0xaa, 0xe9, 0x99, 0x5f, 0x49, 0x32, 0x58, 0x0e, 0xd2, 0x87, 0xad, 0xbc, 0x58, + 0xab, 0x1e, 0xef, 0xf5, 0x31, 0x9f, 0x12, 0xd1, 0xb8, 0x31, 0xac, 0xe9, 0x55, 0xfc, 0xa7, 0x2c, 0x22, 0x29, 0x01, + 0x91, 0x10, 0xd4, 0xdb, 0xd9, 0x45, 0x96, 0xc4, 0x22, 0x8d, 0xd2, 0x9a, 0xd0, 0xf4, 0x84, 0x4d, 0xc6, 0xb3, 0x94, + 0xa5, 0xc7, 0x93, 0x27, 0xb3, 0xc9, 0x93, 0xe8, 0x68, 0x1c, 0xa5, 0x83, 0x01, 0x24, 0x1f, 0x8d, 0xc1, 0xc5, 0x0e, + 0x7e, 0xb3, 0x23, 0x18, 0xba, 0x13, 0x64, 0x09, 0x0b, 0x68, 0xda, 0x97, 0x35, 0x49, 0x0f, 0xe7, 0x85, 0xea, 0x49, + 0x7c, 0x4b, 0xd7, 0x9e, 0x83, 0x8b, 0xdf, 0xc2, 0x0b, 0xd7, 0xc2, 0x8b, 0xdd, 0x16, 0x0a, 0x4d, 0xb6, 0x63, 0xf9, + 0xff, 0xe3, 0x86, 0xb1, 0xef, 0x2e, 0x61, 0x16, 0xd7, 0x75, 0x36, 0x5a, 0x15, 0xb2, 0x92, 0x70, 0x9b, 0x50, 0xa2, + 0xb0, 0x51, 0xbc, 0x5a, 0xe5, 0xda, 0x45, 0x6c, 0x5e, 0x51, 0x00, 0x77, 0x81, 0x38, 0xc5, 0xc0, 0x42, 0x1b, 0x03, + 0xb9, 0x4f, 0xbc, 0x90, 0xcc, 0xaa, 0x7d, 0xcc, 0x3d, 0xf2, 0xcf, 0x10, 0x8c, 0x51, 0xc5, 0xc9, 0x78, 0xa6, 0xb0, + 0x2e, 0x3e, 0x27, 0xef, 0xfd, 0x37, 0x8e, 0x22, 0x7b, 0x34, 0x83, 0x9e, 0x20, 0x72, 0x1e, 0x71, 0xf6, 0x64, 0xf2, + 0x32, 0x70, 0x3f, 0x83, 0x95, 0xfe, 0xba, 0xdb, 0x8c, 0xb5, 0xed, 0xd1, 0xbd, 0x30, 0x42, 0xd1, 0x4f, 0xf8, 0xce, + 0xd4, 0x0b, 0xb8, 0x84, 0x6a, 0x60, 0xd7, 0x97, 0x97, 0xbc, 0x04, 0x10, 0xa1, 0x4c, 0xf4, 0xfb, 0xbd, 0x3f, 0x0d, + 0x34, 0x69, 0xc9, 0x8b, 0x57, 0x99, 0xb0, 0xce, 0x38, 0xd0, 0x54, 0xa0, 0xfe, 0x1f, 0x2b, 0xfb, 0x4c, 0xc7, 0x64, + 0xe6, 0x3f, 0x0e, 0x27, 0x24, 0x6a, 0xbe, 0x26, 0x9f, 0x39, 0x4d, 0x3f, 0x73, 0x45, 0xfb, 0x0f, 0x64, 0xe6, 0x86, + 0x43, 0x86, 0xfa, 0x4b, 0xc7, 0x3c, 0x19, 0xbd, 0x4e, 0xcc, 0x4e, 0x04, 0xab, 0x66, 0x10, 0x85, 0xbd, 0x80, 0x07, + 0x75, 0x2d, 0x8b, 0xa7, 0x30, 0xfb, 0xa0, 0x46, 0x14, 0xc7, 0x6c, 0x3c, 0x0b, 0x65, 0x38, 0x01, 0xfb, 0xde, 0xc9, + 0x18, 0xee, 0x03, 0x32, 0xfc, 0x58, 0x85, 0xd8, 0x39, 0x48, 0xfb, 0x58, 0xa1, 0x62, 0x02, 0x20, 0x02, 0x21, 0x6f, + 0xbf, 0x2f, 0x55, 0x12, 0xbe, 0x2e, 0x31, 0xa5, 0x50, 0x1f, 0xfc, 0x27, 0x52, 0x75, 0xc7, 0xf4, 0xab, 0xf5, 0xe3, + 0xcf, 0x84, 0xe2, 0xd3, 0x5d, 0x4a, 0x7c, 0x0b, 0xc1, 0x9d, 0x0b, 0xd0, 0x41, 0x54, 0x68, 0xc6, 0x76, 0x3f, 0xbf, + 0x2b, 0xf6, 0xf3, 0xbb, 0xe2, 0xff, 0x1d, 0xbf, 0x2b, 0xee, 0x63, 0x0c, 0x2b, 0x0b, 0x0d, 0x3f, 0x0b, 0xc6, 0x41, + 0xf4, 0x9f, 0xf3, 0x89, 0x7b, 0x79, 0xea, 0xab, 0x4c, 0x4c, 0xf7, 0x30, 0xcd, 0x3e, 0x41, 0x41, 0x58, 0xc5, 0x5d, + 0x7a, 0xb2, 0xae, 0xec, 0xad, 0x95, 0x0c, 0x31, 0xcf, 0x3d, 0xac, 0x51, 0x58, 0x79, 0x40, 0xf7, 0xa8, 0xda, 0x20, + 0x4e, 0x04, 0x0f, 0x63, 0x66, 0xa5, 0xef, 0xdb, 0xad, 0x51, 0x61, 0xde, 0xcb, 0x45, 0x41, 0x76, 0xf3, 0xf1, 0x6c, + 0x1c, 0x85, 0xd8, 0x80, 0xff, 0x98, 0xb1, 0x6a, 0xc8, 0xe6, 0x3b, 0x19, 0xa9, 0x1d, 0x93, 0xa7, 0xc9, 0x2e, 0xe9, + 0x1d, 0xf0, 0x0e, 0xf9, 0x79, 0xfd, 0x31, 0x8c, 0xa5, 0xe1, 0xb7, 0xe4, 0x65, 0x5c, 0x64, 0xd5, 0xf2, 0x2a, 0x4b, + 0x90, 0xe9, 0x82, 0x17, 0x5f, 0xcc, 0x74, 0x79, 0x1f, 0xeb, 0x03, 0xc6, 0x53, 0x8a, 0xd7, 0x0d, 0x51, 0xfa, 0xba, + 0xe5, 0x59, 0xa1, 0x2e, 0x4f, 0x2a, 0x66, 0x7b, 0x56, 0x82, 0xd3, 0x29, 0x98, 0xe0, 0xeb, 0x9f, 0xae, 0xf7, 0x09, + 0xe0, 0x82, 0x42, 0xcd, 0x69, 0x21, 0x57, 0x06, 0xcb, 0xc9, 0x42, 0x77, 0x02, 0x66, 0xa8, 0x14, 0x78, 0x81, 0x82, + 0xbf, 0x68, 0x60, 0x44, 0x5f, 0xb8, 0xdf, 0x64, 0x60, 0x90, 0x2e, 0xcd, 0x89, 0x30, 0x76, 0xdc, 0x4e, 0x9c, 0xb6, + 0xa2, 0x9c, 0x71, 0xf6, 0x4e, 0x5d, 0x29, 0xc0, 0x00, 0x6f, 0x73, 0x13, 0x9d, 0x25, 0xe8, 0xb5, 0xa0, 0x74, 0xde, + 0xc0, 0xdd, 0x2c, 0x23, 0x23, 0x5c, 0x7c, 0x58, 0x79, 0x2c, 0xb8, 0x67, 0xbf, 0x90, 0x58, 0x5b, 0x3f, 0x30, 0x66, + 0xf3, 0x82, 0x05, 0x0a, 0x15, 0x28, 0xb0, 0x9c, 0x69, 0x4b, 0xd3, 0x6a, 0xc8, 0x0f, 0x8f, 0xd0, 0xda, 0xb4, 0x1a, + 0xf0, 0xc3, 0xa3, 0x3a, 0xca, 0x8e, 0x21, 0xcb, 0x89, 0x9f, 0x41, 0xbd, 0xae, 0x23, 0x93, 0x62, 0xb2, 0xfb, 0xf5, + 0xa5, 0xfe, 0xa8, 0x6e, 0xc0, 0xf5, 0x03, 0x10, 0xc0, 0x06, 0xe0, 0x10, 0xa8, 0x06, 0x4b, 0x23, 0x82, 0x45, 0x99, + 0x42, 0xfb, 0x1a, 0x7a, 0x6f, 0x34, 0xfc, 0x17, 0xb8, 0x8b, 0xc8, 0x95, 0xff, 0x09, 0x02, 0x7f, 0x45, 0x99, 0x56, + 0xa6, 0xf8, 0x9f, 0x68, 0xf5, 0x0a, 0xe5, 0xac, 0x69, 0xcd, 0x07, 0xd1, 0x9a, 0x08, 0xd5, 0x8c, 0x21, 0xf8, 0xb7, + 0xb2, 0x4c, 0x5b, 0xaa, 0x2a, 0xf5, 0xa1, 0xf1, 0x5a, 0x2b, 0x9c, 0xe5, 0xe3, 0xc8, 0x7b, 0x8d, 0xa1, 0x63, 0x13, + 0x67, 0x29, 0xa7, 0x52, 0x67, 0xaf, 0x0f, 0x65, 0xe4, 0x00, 0xa7, 0x13, 0x36, 0x9e, 0x26, 0xc7, 0x72, 0x9a, 0x38, + 0xc8, 0xfc, 0x9c, 0x61, 0x64, 0x55, 0x03, 0xc2, 0xa2, 0x6c, 0x28, 0x6d, 0x01, 0x26, 0x39, 0x21, 0x64, 0x8a, 0xa1, + 0x28, 0xf2, 0x91, 0xee, 0x87, 0xf5, 0x66, 0x75, 0x5f, 0xbc, 0xd5, 0x00, 0xa7, 0x61, 0x02, 0x81, 0xc0, 0x8b, 0xf8, + 0x26, 0x13, 0x97, 0xe0, 0x31, 0x3c, 0x80, 0x2f, 0xc1, 0x4d, 0x2e, 0x65, 0xbf, 0x57, 0x61, 0x8e, 0x6b, 0x0b, 0x18, + 0x34, 0x58, 0x3d, 0x88, 0x0e, 0x97, 0xd2, 0x66, 0x57, 0x01, 0x62, 0x63, 0x0a, 0xb1, 0x2c, 0xd8, 0xda, 0xb2, 0x67, + 0x3f, 0xab, 0xa6, 0xa1, 0x75, 0xc2, 0xa9, 0xb8, 0xcc, 0x21, 0x8a, 0xca, 0x20, 0x06, 0x77, 0x24, 0x8f, 0xcf, 0x7b, + 0x2b, 0xc2, 0x0b, 0x02, 0x6e, 0x65, 0x89, 0x0c, 0x57, 0x74, 0x39, 0xba, 0xa5, 0xeb, 0xd1, 0x0d, 0x1d, 0xd3, 0xc9, + 0xdf, 0xc7, 0x68, 0x91, 0xad, 0x52, 0xef, 0xe8, 0x7a, 0xb4, 0xa4, 0xdf, 0x8e, 0xe9, 0xd1, 0xdf, 0xc6, 0x64, 0x9a, + 0xe3, 0x61, 0x42, 0x2f, 0xc0, 0xb1, 0x8b, 0xd4, 0xe8, 0xa9, 0xe9, 0x1b, 0x1c, 0x56, 0xa3, 0x7c, 0xc8, 0x47, 0x39, + 0xe5, 0xa3, 0x62, 0x58, 0x8d, 0xc0, 0xd3, 0xb1, 0x1a, 0xf2, 0x51, 0x45, 0xf9, 0xe8, 0x7c, 0x58, 0x8d, 0xce, 0x49, + 0xb3, 0xe9, 0x2f, 0x2b, 0x7e, 0x55, 0xb2, 0x35, 0x6c, 0x0b, 0x58, 0xbe, 0x6e, 0x95, 0xe5, 0xa9, 0xbf, 0xaa, 0xcd, + 0xc9, 0x6c, 0x39, 0x7b, 0x7b, 0xdd, 0xe5, 0xc4, 0xe2, 0x71, 0xdb, 0x74, 0xb8, 0xfa, 0x72, 0xa2, 0x4e, 0x7a, 0x85, + 0xfc, 0x30, 0x9e, 0x0a, 0x75, 0x0e, 0x81, 0x99, 0xc4, 0x2c, 0x8c, 0x19, 0x36, 0x53, 0xa7, 0x81, 0x02, 0x27, 0x1b, + 0x79, 0x2e, 0x8a, 0xd9, 0x28, 0xa7, 0xf0, 0x3e, 0x26, 0x24, 0x12, 0x70, 0x56, 0x9d, 0x54, 0xa3, 0x02, 0x62, 0x8e, + 0xb0, 0x10, 0x1f, 0xa1, 0x5f, 0xea, 0x23, 0x0f, 0x09, 0x3c, 0xc3, 0xbe, 0x16, 0x83, 0x18, 0x8e, 0x78, 0x5b, 0x59, + 0x35, 0x0b, 0x13, 0xa8, 0xac, 0x1a, 0x96, 0xa6, 0xb2, 0x82, 0x66, 0xa3, 0xca, 0xaf, 0xac, 0xc2, 0x31, 0x4a, 0x08, + 0x89, 0x4a, 0x5d, 0x19, 0xa8, 0x4f, 0x12, 0x16, 0x96, 0xba, 0xb2, 0x73, 0xf5, 0xd1, 0xb9, 0x5f, 0xd9, 0x39, 0xb8, + 0x90, 0x0e, 0x12, 0xff, 0x2a, 0xb5, 0x4c, 0xdb, 0xd7, 0xc1, 0xc6, 0xaa, 0xa2, 0x1b, 0x7e, 0x5b, 0x15, 0x71, 0x54, + 0x52, 0x17, 0x03, 0x1a, 0x17, 0x46, 0x24, 0xa9, 0x5e, 0xa3, 0xe0, 0x0f, 0x09, 0xa2, 0xd2, 0x18, 0xbc, 0x3a, 0x93, + 0xae, 0x95, 0x5a, 0x51, 0x31, 0x28, 0x07, 0x05, 0xdc, 0x9f, 0xf2, 0xd6, 0x42, 0xfa, 0x19, 0x22, 0x2a, 0x43, 0x79, + 0x83, 0x5f, 0x30, 0x78, 0x32, 0xbb, 0x4c, 0xc3, 0x64, 0x74, 0x47, 0xe3, 0xd1, 0x12, 0xe1, 0x60, 0xd8, 0x45, 0xaa, + 0xf0, 0xd6, 0x57, 0x90, 0x7e, 0x4b, 0xe3, 0xd1, 0x0d, 0x4d, 0xad, 0xcd, 0xa9, 0x81, 0xba, 0xea, 0x8d, 0xe9, 0x6d, + 0x04, 0xaf, 0xef, 0xa2, 0x25, 0x85, 0xad, 0x74, 0x9a, 0x67, 0x97, 0x22, 0x4a, 0x29, 0x22, 0x10, 0xae, 0x11, 0x39, + 0x70, 0xa9, 0xd1, 0x06, 0xd7, 0x03, 0x28, 0x43, 0xc3, 0x05, 0x2e, 0x07, 0xf1, 0x68, 0xe9, 0x91, 0xa9, 0x54, 0x5f, + 0x64, 0x11, 0x3e, 0xda, 0xd9, 0x68, 0x29, 0x9e, 0x11, 0x0b, 0xe3, 0x0a, 0x86, 0x50, 0x17, 0x56, 0x9a, 0x82, 0xa4, + 0x0b, 0x1c, 0xd9, 0x0b, 0xe3, 0x2a, 0xdc, 0x80, 0x69, 0xd1, 0x1d, 0x98, 0x47, 0x81, 0xc2, 0xc1, 0x25, 0x48, 0x3f, + 0xa1, 0x6c, 0xe7, 0x28, 0x4d, 0x0e, 0x6f, 0x82, 0xd6, 0x3b, 0x13, 0x84, 0xb4, 0xab, 0x9b, 0x6c, 0x49, 0xdf, 0x60, + 0x7b, 0x87, 0x4e, 0x45, 0x05, 0xd5, 0xe7, 0x16, 0x4c, 0x96, 0x6c, 0x10, 0xb6, 0x84, 0xe9, 0x99, 0x5e, 0x03, 0xf6, + 0xf4, 0xe1, 0xd1, 0xce, 0x7c, 0x17, 0xb3, 0xd7, 0x87, 0x65, 0x34, 0x56, 0x16, 0xbc, 0xb9, 0x25, 0x76, 0x4b, 0x36, + 0x9e, 0x2e, 0x8f, 0xcb, 0xe9, 0x12, 0x89, 0x9d, 0xa1, 0x5b, 0x8c, 0xcf, 0x97, 0x0b, 0x9a, 0xe0, 0xd9, 0xc6, 0xaa, + 0xf9, 0xd2, 0xa0, 0xa5, 0xa4, 0x0c, 0xd7, 0xdb, 0x12, 0xfd, 0xff, 0xd5, 0xc5, 0x2f, 0x05, 0x78, 0x09, 0xc6, 0x02, + 0x40, 0xb8, 0x07, 0xd3, 0x82, 0xd4, 0x46, 0xd9, 0x48, 0xd3, 0x30, 0xc5, 0x45, 0x60, 0x52, 0xfa, 0xfd, 0x30, 0x67, + 0x29, 0xf1, 0xa0, 0x43, 0xed, 0x28, 0x5d, 0xa4, 0xbe, 0x10, 0x04, 0x78, 0x24, 0x75, 0x8e, 0x4d, 0xfe, 0x3e, 0x9e, + 0x05, 0x6a, 0x20, 0x82, 0x28, 0x3b, 0xc6, 0x47, 0x0c, 0x5c, 0x14, 0xe9, 0xb8, 0x9d, 0xae, 0x88, 0xd5, 0xee, 0x31, + 0x0b, 0x71, 0x92, 0x30, 0xd7, 0x2c, 0x1b, 0xb2, 0x2a, 0xc2, 0x04, 0x5d, 0x18, 0xd8, 0xaf, 0x0d, 0x59, 0x75, 0x78, + 0x04, 0x91, 0x5a, 0x6d, 0x19, 0x97, 0x5d, 0x65, 0x7c, 0x0b, 0x40, 0xd6, 0x8c, 0xb1, 0xa3, 0xbf, 0x8d, 0x67, 0xea, + 0x9b, 0x28, 0xe4, 0x27, 0x47, 0x7f, 0x83, 0xe4, 0xe3, 0x6f, 0x91, 0x99, 0x83, 0xe4, 0x46, 0x41, 0x57, 0xcd, 0x59, + 0xd7, 0x50, 0x9a, 0xb8, 0xf6, 0x4a, 0xbd, 0xf6, 0xa4, 0x59, 0x7b, 0x05, 0xba, 0x53, 0x1b, 0xde, 0x43, 0xd9, 0xce, + 0x82, 0x09, 0x3a, 0x9a, 0xdd, 0x81, 0x0e, 0xde, 0x29, 0x82, 0x5e, 0x26, 0xa1, 0xf1, 0x08, 0x55, 0x46, 0xbd, 0x18, + 0x0f, 0xaa, 0x93, 0x75, 0xc9, 0x3c, 0x03, 0xe6, 0xd8, 0x9e, 0x43, 0x62, 0x98, 0xab, 0x83, 0x3a, 0x65, 0xe5, 0x30, + 0xc7, 0x03, 0x78, 0xcd, 0xe4, 0x50, 0x0c, 0x72, 0x8d, 0xf2, 0x7d, 0xc1, 0x8a, 0x61, 0x39, 0xc8, 0x35, 0x37, 0x33, + 0x6d, 0xc6, 0xa6, 0x4d, 0x74, 0x78, 0xe6, 0x15, 0x3b, 0x59, 0xf5, 0x80, 0x8f, 0x05, 0x4f, 0x66, 0xdf, 0xf3, 0xf1, + 0x29, 0x70, 0x32, 0x9b, 0xdb, 0x68, 0x49, 0xef, 0xa2, 0x94, 0xde, 0x44, 0x6b, 0xba, 0x8c, 0x2e, 0x8c, 0x89, 0x71, + 0x52, 0xc3, 0x39, 0x00, 0xad, 0x02, 0x48, 0x3c, 0xf5, 0xeb, 0x1d, 0x4f, 0xaa, 0x70, 0x49, 0x53, 0x70, 0x1b, 0xf6, + 0xed, 0x33, 0xcf, 0x7d, 0x89, 0xd4, 0x06, 0x31, 0xd6, 0xac, 0xa1, 0xe2, 0xc6, 0x5b, 0xf7, 0x91, 0xa8, 0x61, 0xe7, + 0xba, 0xd8, 0x44, 0xd5, 0x70, 0x32, 0x2d, 0x01, 0xb1, 0xb5, 0x1c, 0x0e, 0xdd, 0x11, 0xb2, 0x7b, 0xfc, 0xe8, 0x40, + 0xcf, 0x3d, 0x69, 0xb1, 0x6d, 0x5b, 0xfe, 0xc0, 0x10, 0xa6, 0xf4, 0xf3, 0x47, 0x3e, 0x20, 0x56, 0x5c, 0xc1, 0xd9, + 0x08, 0xd4, 0xd1, 0x0a, 0x9d, 0x7e, 0xaf, 0xc2, 0x42, 0x1f, 0xe0, 0x9b, 0xdb, 0x28, 0xa1, 0x77, 0x51, 0xee, 0x91, + 0xb5, 0x65, 0xcd, 0xe4, 0xf4, 0x2c, 0x0b, 0x79, 0xfb, 0x40, 0x2f, 0x17, 0x00, 0xa2, 0x35, 0x88, 0x7d, 0xa9, 0xeb, + 0x11, 0x38, 0x0d, 0xa1, 0x49, 0x68, 0x04, 0x57, 0x15, 0x84, 0x11, 0x70, 0x25, 0xe1, 0x6f, 0x30, 0x51, 0x81, 0x2f, + 0xc0, 0x45, 0x26, 0x4d, 0x73, 0x1e, 0xd4, 0xfe, 0x48, 0xbe, 0x2a, 0xda, 0xde, 0xae, 0x30, 0x9a, 0x60, 0xec, 0x89, + 0xf6, 0x79, 0xa4, 0x1c, 0xc5, 0x45, 0x12, 0x66, 0xa3, 0x5b, 0x75, 0x9e, 0xd3, 0x6c, 0x74, 0xa7, 0x7f, 0x55, 0x74, + 0x4c, 0x7f, 0xd5, 0x01, 0x6d, 0x94, 0xf4, 0xad, 0xe3, 0x6c, 0x40, 0xeb, 0xc5, 0xd2, 0xf8, 0x5f, 0xcb, 0xd1, 0x2d, + 0x95, 0xa3, 0x3b, 0xdf, 0x92, 0x6a, 0x32, 0x2d, 0x8e, 0x05, 0x1a, 0x52, 0x75, 0x7e, 0x5f, 0x00, 0x3f, 0x57, 0x1a, + 0xdf, 0x69, 0xf3, 0xbd, 0xd7, 0xfe, 0xb3, 0x4e, 0x9e, 0x40, 0xb1, 0x44, 0x05, 0xab, 0x46, 0x60, 0xc7, 0xbe, 0xce, + 0xe3, 0xc2, 0x8c, 0x52, 0x4c, 0xad, 0x49, 0x3f, 0x06, 0xae, 0x98, 0xf6, 0x0a, 0x70, 0xb5, 0x04, 0x27, 0x01, 0x88, + 0xa1, 0x09, 0x7b, 0x76, 0x0c, 0x51, 0xcf, 0x8d, 0x63, 0x94, 0x6c, 0xb8, 0x07, 0xc4, 0x5a, 0xe6, 0xad, 0x5c, 0x02, + 0x12, 0x78, 0xeb, 0x61, 0x52, 0x00, 0xc6, 0x60, 0xb9, 0x24, 0x3a, 0x8f, 0x87, 0x3e, 0xa1, 0x5e, 0x68, 0xd4, 0x09, + 0xd9, 0xd8, 0x12, 0x38, 0xfe, 0xb0, 0x3e, 0x04, 0x82, 0x57, 0x79, 0xae, 0xbf, 0xd2, 0xba, 0xfe, 0x52, 0xe9, 0xb9, + 0x63, 0xb9, 0xae, 0xdf, 0xb5, 0xa9, 0xd1, 0x0b, 0xb0, 0xf0, 0xdd, 0x28, 0xf3, 0x48, 0x6e, 0x11, 0x52, 0x15, 0x58, + 0xa9, 0x5b, 0x48, 0x30, 0xff, 0x4a, 0xce, 0x56, 0x65, 0xbe, 0x7a, 0xe4, 0x5e, 0x39, 0x9b, 0x9e, 0xfe, 0x86, 0x04, + 0xed, 0xae, 0x23, 0xcd, 0xe3, 0x2d, 0x3a, 0x7c, 0x76, 0xad, 0x25, 0xe6, 0x4e, 0xa2, 0xe2, 0xf9, 0x14, 0xb0, 0xd5, + 0xb3, 0xec, 0x4a, 0xf9, 0x58, 0xed, 0xe2, 0xf8, 0x99, 0xf3, 0x27, 0xa9, 0xc2, 0xb5, 0x68, 0x28, 0x41, 0xc0, 0x9b, + 0xc3, 0xd8, 0x15, 0xaa, 0x80, 0x86, 0xe6, 0x06, 0x8e, 0x73, 0x35, 0xac, 0x34, 0x01, 0xd3, 0x52, 0x1e, 0x1d, 0xe0, + 0xd0, 0xe4, 0x51, 0xbb, 0x69, 0x58, 0x19, 0xba, 0xd6, 0xe8, 0x73, 0x5b, 0xe9, 0x8c, 0x37, 0x1b, 0x7e, 0x78, 0x34, + 0xa8, 0xf0, 0x27, 0x69, 0x8e, 0x46, 0x3b, 0x37, 0xdc, 0x69, 0x04, 0x66, 0xae, 0xe4, 0x8a, 0xec, 0x8e, 0x92, 0x97, + 0xdf, 0xd3, 0x0b, 0x0b, 0xe8, 0xcf, 0x7f, 0x2e, 0x26, 0x9c, 0xb4, 0xc4, 0x84, 0x68, 0xe9, 0xa0, 0x45, 0x07, 0x3b, + 0xca, 0x2b, 0xfb, 0x12, 0x2f, 0x9d, 0xe3, 0x7f, 0x5f, 0x8f, 0xb5, 0xab, 0x40, 0x68, 0x75, 0xf2, 0xb0, 0x3d, 0x59, + 0x20, 0x6a, 0x40, 0x35, 0xbb, 0x2a, 0x47, 0x99, 0x76, 0x56, 0x64, 0xd3, 0x90, 0xb9, 0xee, 0x66, 0x69, 0xd8, 0x4c, + 0x76, 0x2c, 0x2c, 0x33, 0x0c, 0xd6, 0x4e, 0x15, 0x7d, 0x0e, 0x5a, 0x7e, 0x04, 0xcf, 0x9a, 0xca, 0x33, 0x9f, 0xcd, + 0x32, 0xe2, 0x05, 0x3a, 0xe7, 0x54, 0x2c, 0x9a, 0xd2, 0xb1, 0x72, 0xbb, 0x2d, 0xd1, 0x58, 0xa2, 0x8c, 0x82, 0xa0, + 0xb6, 0x41, 0xd8, 0x75, 0xe9, 0x9e, 0xf4, 0x69, 0x17, 0x9f, 0x56, 0xa0, 0xef, 0xf1, 0x3e, 0x03, 0x89, 0xa9, 0x27, + 0x79, 0xa8, 0x1a, 0xcd, 0xd1, 0xc9, 0xb3, 0x24, 0xd5, 0xf8, 0xfc, 0x4a, 0x76, 0xd6, 0xbc, 0x5b, 0x8d, 0x29, 0xfe, + 0x23, 0x75, 0xfb, 0xce, 0x65, 0x68, 0xa2, 0xbf, 0x96, 0x07, 0x2d, 0x85, 0x05, 0xc7, 0x6d, 0xe3, 0xaf, 0xdf, 0x66, + 0x0e, 0x31, 0x2c, 0x5d, 0x0e, 0x6f, 0x42, 0x87, 0xee, 0xae, 0xb2, 0x33, 0xd7, 0x47, 0xd4, 0xa9, 0x8b, 0x75, 0x1b, + 0x50, 0xb2, 0xe4, 0xdd, 0x3a, 0x3d, 0xb1, 0xd2, 0xaf, 0x87, 0xe1, 0xce, 0x3c, 0x6a, 0x76, 0x77, 0xbb, 0x9d, 0x90, + 0xb6, 0x7d, 0x30, 0xde, 0x97, 0xb0, 0x10, 0xe7, 0x1d, 0x76, 0xf0, 0x73, 0x58, 0x3d, 0xe4, 0x83, 0x7f, 0xe1, 0x38, + 0xc3, 0xe8, 0x67, 0xca, 0xd0, 0xe7, 0x45, 0x21, 0xaf, 0x54, 0xa7, 0x7c, 0xa1, 0x5b, 0xcb, 0xd4, 0xfb, 0x75, 0xfc, + 0xba, 0x15, 0x20, 0xc6, 0xeb, 0x8a, 0x95, 0xe2, 0x0d, 0xad, 0x30, 0xae, 0x81, 0xdb, 0xe4, 0x50, 0x4b, 0xb5, 0x40, + 0xd4, 0xe5, 0x27, 0x0f, 0x79, 0x64, 0xd4, 0x99, 0xf0, 0xdd, 0x43, 0xee, 0x4b, 0xd7, 0x76, 0x9b, 0xf8, 0xb9, 0xa6, + 0x1d, 0xee, 0x0e, 0x74, 0x47, 0xeb, 0xee, 0x6f, 0x9e, 0xcd, 0xcf, 0x23, 0xf3, 0xc5, 0x00, 0x9b, 0xb5, 0xcb, 0xb8, + 0xec, 0x18, 0xee, 0x7b, 0xd3, 0x83, 0xb1, 0x80, 0x40, 0x62, 0x86, 0x5e, 0x06, 0x2e, 0x70, 0x81, 0xbb, 0xc2, 0x80, + 0x21, 0xae, 0x69, 0xc9, 0x9d, 0xb6, 0xb2, 0xf5, 0x91, 0xb7, 0x51, 0x21, 0x58, 0xd7, 0x1d, 0x37, 0x49, 0x0e, 0xc1, + 0x09, 0x5b, 0xee, 0x7d, 0xed, 0xb5, 0x33, 0xfc, 0x65, 0x20, 0x9c, 0x5b, 0xa2, 0x67, 0xd4, 0xf6, 0x50, 0xab, 0x7b, + 0x0d, 0xaf, 0x72, 0x17, 0x79, 0xd6, 0x6f, 0xe6, 0xa5, 0x61, 0x5f, 0xf0, 0x5a, 0x0a, 0x0e, 0x8d, 0xed, 0x56, 0xb8, + 0xc5, 0xe2, 0x1d, 0xad, 0x56, 0xd6, 0xda, 0x6a, 0xaf, 0x95, 0x8a, 0xee, 0x5f, 0x73, 0x9c, 0x38, 0x4b, 0x61, 0xfb, + 0xe1, 0xfd, 0x05, 0xbb, 0x26, 0x80, 0x41, 0x8b, 0xc9, 0x02, 0x25, 0xa8, 0x64, 0xad, 0x6a, 0xb7, 0x53, 0xe2, 0x97, + 0xfb, 0x45, 0x97, 0xd9, 0xce, 0xe3, 0xd7, 0x4d, 0xda, 0x67, 0x3e, 0x47, 0x3f, 0xcc, 0xef, 0xac, 0x93, 0x92, 0x33, + 0x8c, 0x6b, 0xf9, 0xff, 0x55, 0xf4, 0xb2, 0xc8, 0xd2, 0x68, 0x63, 0x78, 0x30, 0x1b, 0x6a, 0xd3, 0x87, 0xc6, 0xa8, + 0xdc, 0xb2, 0x51, 0x44, 0xb4, 0xba, 0x05, 0xc1, 0x8c, 0xe2, 0xbe, 0x44, 0x9b, 0x57, 0xaa, 0x2c, 0xbc, 0xc3, 0x67, + 0x36, 0x7a, 0xc3, 0xf6, 0x84, 0x50, 0xbe, 0x7b, 0x5a, 0x98, 0x55, 0x4b, 0x45, 0x83, 0xed, 0x12, 0xde, 0xc5, 0xa8, + 0xd2, 0x4f, 0x98, 0x6c, 0x59, 0x30, 0xd5, 0xff, 0xef, 0x8b, 0x2c, 0x6d, 0x53, 0x74, 0x60, 0x3a, 0x9b, 0x3e, 0x9d, + 0x74, 0x83, 0xeb, 0x0c, 0x58, 0x44, 0xb0, 0xa5, 0xc2, 0xf1, 0x28, 0xb5, 0x1b, 0x24, 0x4c, 0x04, 0x37, 0x51, 0x2f, + 0x3b, 0x5a, 0xa6, 0x64, 0x55, 0xc0, 0xf3, 0x2b, 0x57, 0x99, 0x8e, 0xa3, 0xa1, 0xdf, 0x3f, 0x4f, 0x4d, 0xe8, 0x57, + 0xea, 0xa5, 0x2a, 0xce, 0xc3, 0xa8, 0x3a, 0x54, 0x18, 0xa3, 0x25, 0x4d, 0xe1, 0x18, 0xcc, 0x2e, 0xc2, 0x14, 0x2f, + 0x67, 0x9b, 0x84, 0x7d, 0xc1, 0x40, 0x2e, 0xb5, 0x41, 0xbd, 0xa6, 0x44, 0x6b, 0xd6, 0xde, 0xcc, 0x29, 0xa1, 0x17, + 0xac, 0xf4, 0xef, 0x42, 0x6b, 0x10, 0x28, 0xca, 0x66, 0xca, 0xf4, 0x4c, 0xb7, 0xf3, 0x82, 0x26, 0xb4, 0xa0, 0x2b, + 0x52, 0x83, 0xbe, 0xd7, 0xc9, 0xd9, 0xd1, 0xc9, 0xce, 0xcc, 0x7a, 0xcc, 0x8a, 0xe1, 0x64, 0x1a, 0xc3, 0x35, 0x2d, + 0x76, 0xd7, 0xb4, 0x65, 0xf3, 0xc6, 0xd5, 0xd8, 0x38, 0x0d, 0xda, 0x05, 0xd2, 0x36, 0xcd, 0xed, 0xa7, 0x1e, 0xb7, + 0xbf, 0xae, 0xd9, 0x72, 0xda, 0x5b, 0x6f, 0xb7, 0xbd, 0x14, 0x6c, 0x44, 0x3d, 0x3e, 0x7e, 0xad, 0xa4, 0xeb, 0x96, + 0xcb, 0x4f, 0xe1, 0xd9, 0xe3, 0xeb, 0x97, 0x3e, 0xb8, 0x1c, 0xad, 0xda, 0xdc, 0xfd, 0x72, 0x17, 0x59, 0xee, 0x8b, + 0x86, 0x96, 0xeb, 0x19, 0x6a, 0x92, 0x67, 0xa3, 0xbd, 0x43, 0x2d, 0x58, 0xce, 0xba, 0x09, 0x4f, 0x0c, 0x76, 0xec, + 0x55, 0x63, 0x73, 0x54, 0xe6, 0x92, 0xd5, 0x20, 0x81, 0x3e, 0xc9, 0x33, 0x4d, 0xff, 0x20, 0xc3, 0x7c, 0x74, 0x4b, + 0x73, 0xc0, 0x15, 0xab, 0xec, 0x25, 0x83, 0xd4, 0x55, 0x7b, 0x89, 0x2b, 0x5f, 0xe1, 0x90, 0x6c, 0xf0, 0xc9, 0x30, + 0x55, 0x9f, 0x5d, 0xf2, 0xe0, 0xff, 0x6d, 0xd5, 0x2a, 0x3d, 0x37, 0xc9, 0x0d, 0xc7, 0xbf, 0x4e, 0xda, 0x3e, 0x26, + 0x06, 0x09, 0x78, 0x6a, 0x17, 0x43, 0x35, 0xaa, 0x8a, 0x58, 0x94, 0xb9, 0x89, 0x39, 0xb6, 0xb7, 0x6b, 0xe8, 0xa0, + 0x0c, 0x7e, 0xdd, 0xf0, 0x89, 0xb9, 0x03, 0x5b, 0x81, 0x8e, 0x4e, 0x34, 0x97, 0x61, 0x66, 0x2e, 0xc3, 0xb4, 0x6b, + 0xab, 0xc0, 0xf0, 0xaa, 0xad, 0x92, 0x28, 0x57, 0xa3, 0x1e, 0x37, 0xb3, 0xd4, 0xec, 0x45, 0xde, 0xbd, 0x26, 0x3d, + 0x89, 0x3f, 0x5d, 0x7a, 0xf2, 0x7a, 0x18, 0x10, 0xf9, 0x25, 0x4b, 0xc3, 0x35, 0x0a, 0x82, 0x53, 0xab, 0x1d, 0x48, + 0xf3, 0x11, 0x20, 0xf3, 0xe3, 0x34, 0x7c, 0xa7, 0xc5, 0x39, 0x64, 0xa3, 0x34, 0x4e, 0x6c, 0x69, 0xd4, 0x43, 0x70, + 0xe7, 0xbd, 0xe2, 0x31, 0x04, 0x3e, 0xfc, 0x80, 0x9b, 0x41, 0x45, 0xb7, 0x25, 0x26, 0x4a, 0x9b, 0x47, 0xdd, 0xf2, + 0x51, 0x43, 0xa8, 0x64, 0x65, 0x78, 0x09, 0xb4, 0x77, 0x47, 0x60, 0x54, 0x39, 0x81, 0xcc, 0xb0, 0x38, 0x3c, 0x1a, + 0xa6, 0x4a, 0x50, 0x34, 0x94, 0xc3, 0x25, 0xca, 0x01, 0x31, 0x09, 0x04, 0x46, 0xc5, 0x20, 0xd5, 0x95, 0xa9, 0x17, + 0x83, 0x54, 0xdf, 0xaa, 0x48, 0x7d, 0x96, 0x85, 0x15, 0xd5, 0x2d, 0xa2, 0x63, 0x3a, 0x94, 0x74, 0x69, 0x76, 0x6a, + 0xae, 0xa5, 0x17, 0x6a, 0x39, 0x3e, 0xd5, 0x69, 0x30, 0x8a, 0xef, 0x5d, 0x8a, 0x7e, 0xab, 0xf6, 0xb3, 0xff, 0x16, + 0x53, 0x6a, 0xc4, 0xa6, 0xf6, 0x16, 0x31, 0xac, 0xda, 0x0f, 0x59, 0x95, 0x83, 0x76, 0x17, 0x94, 0x8d, 0x95, 0x71, + 0x9e, 0x6f, 0x04, 0x33, 0x07, 0x6d, 0x63, 0xd5, 0xf4, 0xa1, 0x37, 0x62, 0xd4, 0xde, 0x98, 0x6a, 0xdc, 0x13, 0xf8, + 0x69, 0x83, 0xa6, 0x7b, 0x91, 0xe7, 0xa8, 0x47, 0xde, 0xfd, 0xcf, 0x1c, 0xd9, 0x99, 0x7c, 0x16, 0xcb, 0xa4, 0x6e, + 0x1f, 0x93, 0x60, 0xa1, 0xea, 0x18, 0x5d, 0xb8, 0x91, 0x29, 0xed, 0xe7, 0xce, 0xf4, 0x23, 0x9e, 0xc9, 0xfd, 0x76, + 0x68, 0xd4, 0x97, 0x86, 0xb5, 0xa4, 0x88, 0xfa, 0x82, 0xde, 0x9a, 0xea, 0xe8, 0x88, 0x7a, 0x1d, 0x81, 0xd5, 0x15, + 0x6d, 0x50, 0x03, 0x30, 0x19, 0xd7, 0xb6, 0x36, 0x9f, 0x83, 0xa9, 0xad, 0xaa, 0xe0, 0x09, 0xdd, 0x15, 0x4a, 0xf7, + 0x26, 0x75, 0xdd, 0x1a, 0x62, 0x0b, 0x18, 0x10, 0xb8, 0xd1, 0x53, 0xd3, 0x1f, 0x34, 0x51, 0x01, 0x68, 0xd0, 0xb8, + 0x9d, 0xe9, 0x1c, 0x89, 0x7e, 0xa7, 0x36, 0x6d, 0x33, 0xd5, 0xab, 0xca, 0x07, 0x50, 0xf1, 0x67, 0xe9, 0xec, 0xc2, + 0x8c, 0x58, 0x00, 0xe3, 0x1e, 0x38, 0x53, 0xbd, 0xd3, 0x0c, 0xac, 0x27, 0xf2, 0x3c, 0x2b, 0x79, 0x22, 0x05, 0xcc, + 0x88, 0xbc, 0xba, 0x92, 0x02, 0x86, 0x41, 0x0d, 0x00, 0x5a, 0x34, 0x97, 0xd1, 0x84, 0x3f, 0xaa, 0xe9, 0xbe, 0x3c, + 0xfc, 0x91, 0xce, 0xf5, 0xcd, 0xb8, 0x06, 0x43, 0xe5, 0x75, 0xc5, 0x77, 0x32, 0x7d, 0xc3, 0x1f, 0x7b, 0x99, 0x96, + 0x72, 0x5d, 0xec, 0x64, 0x79, 0xf4, 0x0d, 0x7f, 0xa2, 0xf3, 0x1c, 0x3d, 0xae, 0x69, 0x1a, 0xdf, 0xed, 0x64, 0xf9, + 0xfb, 0x37, 0x8f, 0x6d, 0x9e, 0x47, 0xe3, 0x9a, 0xde, 0x70, 0xfe, 0xd1, 0x65, 0x9a, 0xe8, 0xaa, 0xc6, 0x8f, 0xff, + 0x6e, 0x73, 0x3d, 0xae, 0xe9, 0x95, 0x14, 0xd5, 0x72, 0xa7, 0xa8, 0xa3, 0x6f, 0x8e, 0xfe, 0xce, 0xbf, 0x31, 0xdd, + 0x3b, 0xaa, 0xe9, 0x5f, 0xeb, 0xb8, 0xa8, 0x78, 0xb1, 0x53, 0xdc, 0xdf, 0xfe, 0xfe, 0xf7, 0xc7, 0x36, 0xe3, 0xe3, + 0x9a, 0xde, 0xf1, 0xb8, 0xa3, 0xed, 0x93, 0x27, 0x8f, 0xf9, 0xdf, 0xea, 0x9a, 0xfe, 0xc6, 0xfc, 0xe0, 0xa8, 0xa7, + 0x99, 0xa7, 0x87, 0xcf, 0x65, 0x13, 0x35, 0x60, 0xe8, 0xa1, 0x01, 0x2c, 0xa5, 0x55, 0xd3, 0xec, 0xf1, 0xca, 0x05, + 0xb7, 0xef, 0xb3, 0x38, 0x8d, 0x57, 0x70, 0x10, 0x6c, 0xd0, 0x38, 0xab, 0x00, 0x4e, 0x15, 0x78, 0xcf, 0xa8, 0xa4, + 0x59, 0x29, 0x7f, 0xe3, 0xfc, 0x23, 0x0c, 0x1a, 0x42, 0xda, 0xa8, 0xc8, 0x40, 0x6f, 0x56, 0x3a, 0xb2, 0x11, 0xfa, + 0x6f, 0x36, 0xe3, 0xe0, 0xf8, 0x30, 0x7a, 0xfd, 0x7e, 0x58, 0x30, 0x11, 0x16, 0x84, 0xd0, 0x3f, 0xc3, 0x02, 0x1c, + 0x4a, 0x0a, 0xe6, 0xe5, 0x33, 0xbe, 0xe7, 0xda, 0x28, 0x2c, 0x04, 0xd1, 0x5d, 0x64, 0x1f, 0x50, 0xf5, 0xe8, 0x3b, + 0x74, 0x43, 0xbc, 0xac, 0xb0, 0x60, 0x68, 0x55, 0x03, 0x33, 0x04, 0xc5, 0xbf, 0xe2, 0xa1, 0x04, 0x9f, 0x78, 0x80, + 0x8f, 0x1e, 0x93, 0x19, 0x57, 0xd7, 0xda, 0x37, 0x17, 0x61, 0x41, 0x03, 0xdd, 0x76, 0x08, 0x3a, 0x10, 0xf9, 0x2f, + 0xc0, 0x53, 0x60, 0xe0, 0xc3, 0xc2, 0xae, 0x3b, 0xf0, 0x7c, 0x7e, 0x33, 0xac, 0xa3, 0x0b, 0x3f, 0xfa, 0x9b, 0x75, + 0x61, 0xcf, 0xc8, 0x54, 0x1e, 0x97, 0xc3, 0xc9, 0x74, 0x30, 0x90, 0x2e, 0x8e, 0xdb, 0x69, 0x36, 0xff, 0x6d, 0x2e, + 0x17, 0x0b, 0xd4, 0x7d, 0xe3, 0xbc, 0xce, 0xf4, 0xdf, 0x48, 0x3b, 0x1f, 0xbc, 0x3a, 0xfd, 0xfd, 0xec, 0xfd, 0xe9, + 0x0b, 0x70, 0x3e, 0xf8, 0xf0, 0xfc, 0xfb, 0xe7, 0xef, 0x54, 0x70, 0x77, 0x35, 0xe7, 0xfd, 0xbe, 0x93, 0xfa, 0x84, + 0x7c, 0x58, 0x91, 0xc3, 0x30, 0x7e, 0x58, 0x28, 0xa3, 0x07, 0x72, 0xcc, 0x2c, 0x14, 0x32, 0x54, 0x51, 0xdb, 0xdf, + 0xe5, 0x70, 0xe2, 0x81, 0x59, 0xdc, 0x36, 0x44, 0xb8, 0x7e, 0xcb, 0x6d, 0x90, 0x35, 0x79, 0xe2, 0xf5, 0x83, 0x93, + 0xa9, 0x74, 0x6c, 0x61, 0xc1, 0xa0, 0x6c, 0x68, 0xd3, 0x69, 0x36, 0x2f, 0x16, 0xb6, 0x5d, 0x6e, 0x81, 0x8c, 0xd2, + 0xec, 0xe2, 0x22, 0x54, 0xd0, 0xd5, 0x27, 0xa0, 0x01, 0x30, 0x8d, 0x2a, 0x5c, 0x8b, 0xf8, 0xcc, 0x2f, 0x3f, 0x1a, + 0x7b, 0xcd, 0xbb, 0x46, 0xdd, 0x93, 0x69, 0x56, 0xd5, 0x18, 0xd0, 0xc1, 0x84, 0x72, 0x37, 0xe8, 0x26, 0x98, 0x8c, + 0x6a, 0xcb, 0x6f, 0xf3, 0x6a, 0x61, 0x9a, 0xe3, 0x86, 0xa1, 0xf2, 0x4a, 0xbe, 0x90, 0x0d, 0x44, 0x06, 0x92, 0x61, + 0xd8, 0xa3, 0x31, 0x8a, 0xd4, 0x0f, 0x76, 0xbd, 0xe3, 0x37, 0xb9, 0x84, 0x68, 0x8a, 0x19, 0x48, 0xe7, 0x4f, 0x85, + 0x72, 0x2e, 0x97, 0x8c, 0xcf, 0xc5, 0xe2, 0x04, 0xdc, 0xce, 0xe7, 0x62, 0x11, 0x61, 0x50, 0xbe, 0x0c, 0x62, 0x95, + 0x80, 0xdd, 0x8b, 0x83, 0xf0, 0xed, 0x84, 0x36, 0xb0, 0x1b, 0x48, 0xb2, 0x41, 0x69, 0x57, 0x1a, 0xa2, 0xdc, 0x29, + 0x8f, 0x36, 0x88, 0x3c, 0xc4, 0xaa, 0x79, 0xd5, 0xf6, 0x64, 0x33, 0x17, 0x13, 0x5c, 0x65, 0x31, 0x93, 0xd3, 0xf8, + 0x98, 0x15, 0xd3, 0x18, 0x4a, 0x89, 0xd3, 0x34, 0x8c, 0xe9, 0x84, 0x0a, 0x42, 0x12, 0xc6, 0xe7, 0xf1, 0x82, 0x26, + 0x28, 0x25, 0x08, 0x21, 0xe4, 0xc7, 0x08, 0x6d, 0x73, 0x60, 0xc9, 0xdb, 0xed, 0xe7, 0xe9, 0xe7, 0x76, 0x0c, 0x97, + 0x51, 0x11, 0xba, 0x41, 0x67, 0x0d, 0xff, 0x46, 0x54, 0xd0, 0x18, 0x2b, 0x86, 0x20, 0xe0, 0x05, 0x46, 0x25, 0x2c, + 0x48, 0xcc, 0x2a, 0x88, 0x22, 0x50, 0xce, 0xe3, 0x05, 0x2b, 0x68, 0xd3, 0xe6, 0x34, 0xd6, 0x26, 0x41, 0x3d, 0x87, + 0xa5, 0x76, 0x20, 0x95, 0x0a, 0xb1, 0xc7, 0x67, 0x22, 0xba, 0xd1, 0x86, 0x06, 0x80, 0x02, 0xa5, 0xe4, 0xe2, 0x37, + 0x5f, 0xee, 0xe1, 0xa6, 0xa0, 0xff, 0xd9, 0xc6, 0x44, 0x3b, 0xcb, 0xd5, 0xa1, 0x37, 0x5f, 0xd0, 0x38, 0xcf, 0x21, + 0x14, 0x9b, 0x41, 0x20, 0x17, 0x59, 0x05, 0x11, 0x2d, 0xee, 0x02, 0x13, 0x12, 0x0e, 0xda, 0xf4, 0x0b, 0xa4, 0x36, + 0xc4, 0xe4, 0xca, 0x13, 0x03, 0xbb, 0xad, 0x12, 0x04, 0x1c, 0xe9, 0x79, 0xf6, 0xa9, 0x89, 0xb1, 0xa6, 0xa9, 0x99, + 0x89, 0xb7, 0xa1, 0x10, 0x0d, 0x5a, 0x10, 0xcd, 0xe0, 0xfd, 0x73, 0xc5, 0xf1, 0xaa, 0x03, 0x3f, 0xe0, 0x9d, 0x8b, + 0x33, 0xaf, 0x66, 0x1e, 0x91, 0x53, 0x4f, 0x73, 0x44, 0xbf, 0xe4, 0x61, 0x35, 0xd2, 0xc9, 0x18, 0x2b, 0x89, 0x83, + 0xde, 0x06, 0x0b, 0xe6, 0x84, 0xae, 0x78, 0x68, 0xf9, 0xf8, 0x17, 0xc8, 0x64, 0x94, 0xd4, 0x58, 0xd1, 0x95, 0x16, + 0x23, 0xce, 0x6b, 0x98, 0xa5, 0xc9, 0x8a, 0x2e, 0x16, 0x9a, 0x34, 0x0b, 0x65, 0x1a, 0xe0, 0x13, 0x68, 0x31, 0x72, + 0x0f, 0x35, 0x6d, 0x20, 0x34, 0xec, 0x0e, 0x01, 0x1f, 0xb9, 0x87, 0x0e, 0xff, 0x3f, 0xcf, 0x2e, 0x10, 0x69, 0xef, + 0xd2, 0x44, 0xc6, 0x23, 0x75, 0x03, 0x07, 0xc5, 0xf8, 0xd8, 0x37, 0x13, 0xbf, 0x70, 0x46, 0xef, 0x93, 0xca, 0x77, + 0xf8, 0x60, 0xf9, 0xe3, 0x4d, 0xcd, 0xac, 0x8c, 0x60, 0x3d, 0x6c, 0xb7, 0xb8, 0x20, 0xda, 0x2e, 0x80, 0xd4, 0x33, + 0x5e, 0x2d, 0x7c, 0xe3, 0xd5, 0x78, 0x8f, 0xf1, 0xaa, 0xb3, 0xc2, 0x0a, 0x73, 0xb2, 0x41, 0x7d, 0x96, 0x92, 0xe7, + 0xe7, 0x28, 0x13, 0x6c, 0xba, 0x9c, 0x95, 0x54, 0xa5, 0x12, 0xda, 0x8b, 0xfd, 0x8c, 0xf1, 0x2d, 0xc1, 0x38, 0x2b, + 0x0e, 0x23, 0x81, 0xaa, 0x54, 0x52, 0x87, 0xbd, 0x02, 0xd4, 0x63, 0xf0, 0xde, 0x60, 0x88, 0x1a, 0x19, 0xbb, 0x69, + 0x03, 0xa1, 0xa1, 0xb1, 0x1e, 0xed, 0x59, 0xeb, 0xd1, 0xed, 0xb6, 0x32, 0xfe, 0x76, 0x72, 0x5d, 0x24, 0x88, 0x2a, + 0xac, 0x46, 0x13, 0xe0, 0x4d, 0x13, 0x7b, 0x5b, 0x72, 0x4a, 0x0b, 0x0c, 0x9f, 0xfd, 0x67, 0x58, 0x3a, 0x95, 0x44, + 0x49, 0x66, 0x65, 0x34, 0x70, 0xe7, 0xe0, 0xb3, 0xb8, 0x82, 0x35, 0x00, 0x91, 0x1c, 0xd1, 0xc3, 0xf5, 0xcf, 0x50, + 0xba, 0xcc, 0x92, 0xcc, 0x24, 0x64, 0xe6, 0x22, 0x6d, 0x67, 0x1d, 0x4c, 0x9c, 0x49, 0xad, 0x37, 0x16, 0x72, 0x68, + 0x90, 0x1f, 0x40, 0x19, 0xe2, 0xf0, 0xc9, 0x07, 0x13, 0x2a, 0x55, 0x28, 0xd5, 0x46, 0x37, 0xbb, 0x81, 0x57, 0x3e, + 0x64, 0x57, 0xbc, 0xac, 0xe2, 0xab, 0x95, 0xb1, 0x24, 0xe6, 0x6c, 0x9f, 0xdb, 0x1e, 0x15, 0xe6, 0xd5, 0xeb, 0xe7, + 0xdf, 0x9f, 0x36, 0x5e, 0xed, 0x22, 0x8e, 0x86, 0x60, 0x5b, 0x31, 0xc6, 0xe8, 0x2d, 0x3e, 0x0d, 0x26, 0xca, 0x35, + 0x02, 0xbd, 0x4b, 0x41, 0xbf, 0xfd, 0xa5, 0x9e, 0x80, 0x57, 0x5c, 0x2f, 0xbf, 0xe4, 0x23, 0x60, 0x89, 0x0a, 0x3d, + 0x2b, 0xcc, 0xcd, 0xca, 0x6c, 0x6f, 0xb7, 0x22, 0x33, 0xed, 0x4a, 0x23, 0x03, 0xf1, 0x6a, 0x3b, 0x8c, 0x85, 0x4b, + 0xd7, 0x74, 0x3b, 0xd8, 0xd5, 0xd2, 0xb3, 0x44, 0xde, 0x6e, 0x4b, 0xe8, 0x90, 0x1d, 0x70, 0xef, 0x65, 0x7c, 0x0b, + 0x2f, 0x4b, 0xaf, 0x9b, 0xcd, 0xe0, 0x09, 0x60, 0x26, 0x5c, 0x38, 0xcb, 0xe2, 0x98, 0x65, 0x49, 0xa8, 0x62, 0x73, + 0x35, 0x44, 0xde, 0x8a, 0xd0, 0x9a, 0xfd, 0x15, 0x8a, 0x11, 0xd8, 0x9d, 0xbc, 0xff, 0x98, 0xad, 0x66, 0x6b, 0x40, + 0xcd, 0xbf, 0xca, 0x04, 0xd0, 0x5c, 0xbb, 0x16, 0x6c, 0x53, 0x68, 0x73, 0x5d, 0x3f, 0x8d, 0x57, 0x71, 0x02, 0xaa, + 0x1b, 0xf0, 0x16, 0xb9, 0xd5, 0xa2, 0x2b, 0x83, 0x2e, 0x4a, 0xef, 0x29, 0xc7, 0x92, 0x42, 0x47, 0xdf, 0x7b, 0x42, + 0x9d, 0x7b, 0x06, 0x70, 0x49, 0xa3, 0xe6, 0xa9, 0x96, 0x32, 0x16, 0x00, 0x0b, 0x1d, 0xcc, 0x14, 0xd9, 0x8a, 0xae, + 0x0d, 0x26, 0x05, 0xbc, 0x35, 0xc0, 0x1f, 0x22, 0xab, 0xd4, 0x5d, 0xb1, 0x0c, 0x4b, 0xcf, 0xfe, 0xba, 0xdf, 0x8f, + 0x3d, 0xfb, 0xeb, 0x95, 0xa6, 0x75, 0x71, 0xbb, 0x01, 0xa4, 0xc6, 0x00, 0x22, 0xa7, 0x7a, 0x20, 0x4c, 0x44, 0xb1, + 0xa6, 0xef, 0xdf, 0xa9, 0xc9, 0xa2, 0x40, 0xe8, 0x77, 0xea, 0xf5, 0xa4, 0x24, 0xa0, 0x53, 0xab, 0xd8, 0xc9, 0x40, + 0x9b, 0x7d, 0x40, 0x40, 0x54, 0x3f, 0x23, 0x9b, 0x2f, 0x94, 0x73, 0xb1, 0x0a, 0x1f, 0x3e, 0xa6, 0x10, 0x50, 0xb8, + 0xa3, 0x46, 0xe7, 0x6d, 0x88, 0x04, 0xca, 0x0a, 0x45, 0xac, 0x79, 0xb1, 0x96, 0x84, 0xcc, 0xc7, 0x0b, 0x14, 0x5c, + 0x39, 0x60, 0x57, 0xce, 0x26, 0xc3, 0x32, 0xe2, 0x2c, 0xdc, 0xff, 0xcd, 0x64, 0x41, 0x50, 0x73, 0xe5, 0x07, 0x72, + 0xdc, 0xc9, 0xd4, 0xd8, 0x53, 0x8d, 0x1a, 0x04, 0x93, 0x11, 0x04, 0x86, 0x1b, 0x7e, 0xc1, 0xc7, 0x47, 0x0b, 0x02, + 0x2a, 0x32, 0x6b, 0x16, 0x62, 0x5e, 0x1c, 0x3f, 0x02, 0xd4, 0x98, 0xd1, 0xd1, 0x93, 0x29, 0x67, 0x70, 0x88, 0xd2, + 0x31, 0xc8, 0x68, 0x05, 0xfc, 0x16, 0xea, 0x77, 0xeb, 0xc4, 0xf7, 0xa1, 0x5f, 0x05, 0xbd, 0x88, 0x81, 0xe1, 0x88, + 0x26, 0x87, 0x21, 0x1f, 0x4c, 0x06, 0xa0, 0x2d, 0xf1, 0x76, 0x5f, 0x4b, 0x2b, 0x6e, 0x4e, 0x97, 0x4e, 0xf7, 0x4f, + 0xda, 0x04, 0x49, 0xa4, 0x92, 0x95, 0x8a, 0x18, 0x40, 0x28, 0x4b, 0xb5, 0x4d, 0xd6, 0x60, 0x59, 0x61, 0x96, 0x34, + 0x37, 0x28, 0x89, 0xbb, 0x9b, 0x81, 0x63, 0xd4, 0xac, 0xd3, 0xb0, 0x6c, 0xb9, 0x51, 0x03, 0x7c, 0x4e, 0xc2, 0x0a, + 0x7b, 0xc3, 0x99, 0x49, 0xef, 0x4c, 0x87, 0xab, 0x63, 0xce, 0x5e, 0x71, 0x04, 0xe3, 0x48, 0xf0, 0xc6, 0x43, 0x97, + 0x4c, 0x43, 0x45, 0xa6, 0x8c, 0x83, 0x69, 0x0f, 0x70, 0xef, 0x39, 0x18, 0x87, 0xb1, 0x41, 0x65, 0x49, 0x7d, 0xea, + 0xdd, 0x85, 0x40, 0x90, 0xd6, 0x7a, 0x99, 0xcf, 0xf0, 0xf4, 0x8c, 0x50, 0xf6, 0x87, 0x1c, 0xbe, 0x00, 0x3b, 0x0a, + 0x72, 0x32, 0xe1, 0x4f, 0x1e, 0xee, 0x06, 0xaa, 0xe2, 0x83, 0xe0, 0x20, 0x16, 0xe9, 0x41, 0x30, 0x10, 0xf0, 0xab, + 0xe0, 0x07, 0x95, 0x94, 0x07, 0x17, 0x71, 0x71, 0x10, 0xaf, 0xe2, 0xa2, 0x3a, 0xb8, 0xc9, 0xaa, 0xe5, 0x81, 0xe9, + 0x10, 0x40, 0xf3, 0x06, 0x83, 0x78, 0x10, 0x1c, 0x04, 0x83, 0xc2, 0x4c, 0xed, 0x8a, 0x95, 0x8d, 0xe3, 0xcc, 0x84, + 0x28, 0x0b, 0x9a, 0x01, 0xc2, 0x1a, 0xa7, 0x01, 0xf0, 0xa9, 0x6b, 0x96, 0xd2, 0x0b, 0x0c, 0x37, 0x20, 0xa6, 0x6b, + 0xe8, 0x03, 0xf0, 0xc8, 0x6b, 0x1a, 0xc3, 0x12, 0xb8, 0x18, 0x0c, 0xc8, 0x05, 0x44, 0x2e, 0x58, 0x53, 0x1b, 0xc4, + 0x21, 0x5c, 0x2b, 0x3b, 0xed, 0x5d, 0x60, 0xa6, 0xed, 0x16, 0x10, 0x95, 0x27, 0xa4, 0xdf, 0xb7, 0xdf, 0x50, 0xff, + 0x82, 0xbd, 0x04, 0xfb, 0xab, 0xa2, 0x0a, 0x73, 0xa9, 0x34, 0xdf, 0x97, 0xec, 0x64, 0xa0, 0x22, 0x0e, 0xef, 0x38, + 0x52, 0xb4, 0x51, 0xb9, 0x2c, 0x7b, 0xb2, 0x6c, 0xf8, 0x4a, 0x5c, 0x71, 0xe7, 0xc7, 0x55, 0x49, 0x99, 0x57, 0xd9, + 0x4a, 0xb1, 0x7f, 0x33, 0xae, 0xb9, 0x3f, 0xb0, 0xfe, 0x6c, 0xbe, 0x82, 0x6b, 0xab, 0xf7, 0xae, 0xc9, 0x35, 0x22, + 0x67, 0x09, 0xe5, 0x92, 0xda, 0xe6, 0xe1, 0x2d, 0x7d, 0x9f, 0x5f, 0x7d, 0x9b, 0xe9, 0x34, 0x3e, 0xab, 0xb0, 0x70, + 0x21, 0x5a, 0x11, 0x1c, 0x1a, 0x72, 0xd1, 0x3c, 0x02, 0xcc, 0xb5, 0xcf, 0x56, 0x50, 0x90, 0xfa, 0xac, 0x42, 0xef, + 0x56, 0x48, 0x78, 0xa1, 0xd9, 0xa5, 0xfb, 0x81, 0x94, 0x71, 0x7b, 0x68, 0x09, 0x93, 0x96, 0x17, 0xe1, 0xbd, 0xd7, + 0xdc, 0xe4, 0x9e, 0x85, 0x18, 0xbd, 0xc8, 0xb3, 0x13, 0x30, 0xd6, 0x5d, 0xb2, 0xb3, 0xe1, 0x89, 0xdf, 0xf0, 0x9c, + 0xb5, 0x68, 0x34, 0x5d, 0xb2, 0xa4, 0xdf, 0x8f, 0xc1, 0xc4, 0x3b, 0x65, 0x39, 0xfc, 0xca, 0x17, 0x74, 0xcd, 0x00, + 0x53, 0x8c, 0x5e, 0x40, 0x42, 0x8a, 0x48, 0x24, 0x6b, 0x75, 0x92, 0x7c, 0xa6, 0xbb, 0x00, 0x8c, 0x7e, 0x31, 0x4b, + 0xa3, 0xe5, 0x5e, 0x33, 0x0b, 0x24, 0xcf, 0xd0, 0x77, 0x1d, 0x6c, 0x6f, 0xec, 0x83, 0x94, 0xf3, 0x63, 0x31, 0x1d, + 0x0c, 0x38, 0xd1, 0x70, 0xe3, 0xa5, 0x12, 0xd7, 0xea, 0x16, 0x77, 0x0c, 0x63, 0xa9, 0x6f, 0x8b, 0x18, 0x1c, 0xb0, + 0x8b, 0x56, 0x76, 0xfb, 0x00, 0xfb, 0xca, 0xf1, 0x2e, 0x55, 0x76, 0xa7, 0xc7, 0x4c, 0x73, 0xd9, 0x6a, 0xd2, 0x49, + 0xc5, 0x7e, 0x22, 0xdf, 0xe4, 0x0e, 0xba, 0x5c, 0x8e, 0x35, 0x6f, 0x39, 0x00, 0x15, 0xfd, 0x48, 0x51, 0xdd, 0x2f, + 0x70, 0x84, 0xb9, 0xb7, 0x6e, 0xf3, 0xc9, 0xa1, 0x29, 0x70, 0x88, 0x3c, 0x69, 0xa3, 0x29, 0xa0, 0x7b, 0x17, 0x0f, + 0xbb, 0xfa, 0x6d, 0xe9, 0x2e, 0x50, 0xa2, 0x9d, 0x8a, 0x1b, 0x7e, 0x4c, 0xd4, 0xe9, 0x4c, 0x1b, 0x42, 0xff, 0xca, + 0x88, 0xfb, 0x4b, 0xe3, 0x2a, 0xde, 0xf4, 0x2e, 0x9f, 0x71, 0xa8, 0xb3, 0x1b, 0x42, 0x01, 0xb8, 0x6a, 0x4f, 0xa7, + 0x6e, 0x0c, 0xe9, 0x95, 0x12, 0xdd, 0x06, 0x07, 0xdb, 0xeb, 0x33, 0x8e, 0xa2, 0x1f, 0xa3, 0x46, 0xbe, 0x89, 0xc4, + 0x43, 0x39, 0x88, 0x1f, 0x16, 0x74, 0x19, 0x89, 0x87, 0xc5, 0x20, 0x7e, 0x28, 0xeb, 0x7a, 0xf7, 0x5c, 0xd9, 0xdf, + 0x47, 0xe4, 0x59, 0x77, 0xf6, 0x52, 0x09, 0x1b, 0x03, 0xcf, 0xae, 0x05, 0x84, 0x53, 0xf0, 0x44, 0xb6, 0x96, 0x3e, + 0x74, 0x6e, 0xf7, 0xb1, 0x65, 0x92, 0x20, 0xe8, 0x79, 0x9b, 0x4d, 0xa2, 0xd8, 0xd9, 0xe6, 0xd1, 0x87, 0x53, 0x20, + 0xa1, 0xdb, 0x6d, 0xb3, 0xae, 0xd6, 0x80, 0x62, 0x1a, 0x8e, 0xf9, 0x61, 0x31, 0xba, 0xf1, 0xdd, 0xf5, 0x0f, 0x8b, + 0xd1, 0x92, 0x0c, 0x27, 0x66, 0xf2, 0xe3, 0x93, 0xf1, 0x2c, 0x8e, 0x26, 0x75, 0xc7, 0x69, 0xa1, 0xf1, 0x4f, 0xbd, + 0x5b, 0x28, 0x02, 0xa7, 0x62, 0x04, 0x47, 0x4e, 0x85, 0x72, 0x52, 0x6a, 0x60, 0xf8, 0x1f, 0x54, 0x3b, 0xda, 0xb4, + 0x57, 0x71, 0x95, 0x2c, 0x33, 0x71, 0xa9, 0xc3, 0x87, 0xeb, 0xe8, 0xe2, 0x36, 0xa0, 0x9d, 0x77, 0x99, 0x76, 0xfc, + 0x3a, 0x69, 0xd0, 0x13, 0x57, 0x33, 0x03, 0x6e, 0xdd, 0x8f, 0xd0, 0x0c, 0x81, 0xd1, 0xf2, 0xfc, 0x2d, 0x62, 0x6e, + 0xff, 0xaa, 0x6c, 0x7e, 0x15, 0xed, 0x73, 0x64, 0xa4, 0x6c, 0x93, 0x91, 0x0a, 0x8c, 0x30, 0xa5, 0x48, 0xe2, 0x2a, + 0x84, 0x40, 0xf6, 0x5f, 0x52, 0x5c, 0x8b, 0xa5, 0xf7, 0x1a, 0x84, 0x09, 0xb6, 0x0b, 0xda, 0xaf, 0x6e, 0xe7, 0xb6, + 0xd2, 0x62, 0x8f, 0xd4, 0xf7, 0xb9, 0xb3, 0x5d, 0xd1, 0xe4, 0xef, 0xcb, 0x06, 0xb4, 0x01, 0x44, 0xb9, 0xaf, 0x8f, + 0x4a, 0xe0, 0x64, 0xc4, 0x0d, 0x25, 0x46, 0x2f, 0xe8, 0xea, 0x44, 0xee, 0xd9, 0xa9, 0x79, 0x53, 0x31, 0x53, 0x71, + 0xe5, 0x9b, 0x3d, 0xf3, 0x1f, 0x0c, 0x05, 0x2d, 0xc1, 0xc0, 0xdb, 0x9c, 0xf1, 0xe8, 0x40, 0x77, 0x63, 0x74, 0x5a, + 0xb0, 0x59, 0x50, 0x97, 0x75, 0xd3, 0xc6, 0x83, 0x46, 0x1c, 0x14, 0xc5, 0xaa, 0x50, 0x23, 0xe1, 0x89, 0x40, 0xc0, + 0x94, 0x5d, 0xf1, 0xc8, 0x08, 0x6a, 0x7a, 0x13, 0x0a, 0x1b, 0x0a, 0xfe, 0x2a, 0x51, 0x4d, 0x6f, 0x42, 0x9b, 0x4c, + 0x9c, 0x66, 0x10, 0xc1, 0x8c, 0xd8, 0xee, 0xb7, 0x80, 0x36, 0xb7, 0x66, 0xb4, 0xa9, 0x6b, 0xab, 0xad, 0x42, 0x2e, + 0x29, 0x52, 0x96, 0xff, 0x4e, 0x4d, 0x05, 0x25, 0xb5, 0x5c, 0xf4, 0x26, 0x4d, 0x17, 0x3d, 0x9e, 0x19, 0x49, 0xa0, + 0x72, 0xcb, 0x1d, 0xa3, 0x3f, 0x84, 0x05, 0x1e, 0x31, 0x71, 0x62, 0xc1, 0xdc, 0xea, 0x84, 0x65, 0x73, 0xb1, 0x18, + 0xad, 0x24, 0x84, 0x0d, 0x3e, 0x66, 0xd9, 0xbc, 0xd4, 0x0f, 0xa1, 0x2f, 0x2c, 0x7d, 0x03, 0x76, 0xb1, 0xc1, 0x4a, + 0x96, 0x01, 0xf8, 0x5e, 0xd0, 0xcd, 0x4a, 0x96, 0x91, 0x54, 0xdd, 0x8f, 0x6b, 0x2c, 0x41, 0xa5, 0x15, 0x2a, 0x2d, + 0xa9, 0xb1, 0x20, 0xf0, 0x55, 0xd5, 0xe5, 0x43, 0xb2, 0xab, 0x40, 0x3d, 0x75, 0xd4, 0x80, 0x53, 0xa0, 0xaa, 0xc0, + 0x82, 0x24, 0xa8, 0x0c, 0x5d, 0x15, 0x98, 0x56, 0x60, 0x9a, 0xa9, 0xc2, 0x45, 0x99, 0x1d, 0x4a, 0xb3, 0x5e, 0xf2, + 0x59, 0x3c, 0x08, 0x93, 0x61, 0x4c, 0x1e, 0x22, 0xd4, 0xfe, 0x61, 0x1e, 0xc5, 0x5a, 0x2e, 0x79, 0xe9, 0xfc, 0xe2, + 0x6f, 0x3e, 0x63, 0xaf, 0x7b, 0x86, 0xc1, 0x02, 0x9c, 0xa5, 0xed, 0x55, 0x26, 0xde, 0xca, 0x56, 0x70, 0x1c, 0xcc, + 0xa2, 0x1c, 0x56, 0x3d, 0x39, 0xa2, 0xb9, 0xc8, 0xb5, 0x77, 0x11, 0x22, 0x07, 0x99, 0x3d, 0x06, 0xd8, 0x8d, 0xf0, + 0x75, 0x68, 0x6d, 0x6e, 0x75, 0x85, 0xf8, 0x1b, 0x25, 0x12, 0x3f, 0x49, 0xf9, 0x71, 0xbd, 0x52, 0xb9, 0x2a, 0x83, + 0xc7, 0xaa, 0x9b, 0xc1, 0x33, 0xed, 0x7b, 0xac, 0xfd, 0x5b, 0xdb, 0xcd, 0xf1, 0xde, 0x83, 0x07, 0xad, 0xff, 0xad, + 0x27, 0x21, 0xb4, 0x57, 0x4e, 0x52, 0x77, 0xd4, 0xe8, 0x99, 0xc9, 0x1a, 0x51, 0x09, 0x53, 0xbb, 0x53, 0x39, 0x06, + 0x6a, 0x3a, 0x80, 0x6b, 0x89, 0x9a, 0xa0, 0x27, 0x05, 0x1b, 0xc3, 0x11, 0x67, 0x71, 0xd0, 0x8e, 0x63, 0x14, 0x2f, + 0xe7, 0x4a, 0xbc, 0x9c, 0x9f, 0x30, 0x0e, 0xd0, 0x5a, 0x80, 0x54, 0xaf, 0x61, 0x3f, 0x73, 0x05, 0x0b, 0x6c, 0xee, + 0x7c, 0x47, 0x16, 0xc8, 0x10, 0x27, 0x9b, 0xe3, 0x64, 0x8f, 0x6b, 0x3d, 0xf7, 0x02, 0x1f, 0x27, 0xf5, 0xc2, 0xab, + 0xab, 0x6c, 0xd7, 0xb5, 0x64, 0xe5, 0xbc, 0x18, 0x4c, 0x20, 0x28, 0x4b, 0x39, 0x2f, 0x86, 0x93, 0x05, 0xcd, 0xe1, + 0xc7, 0xa2, 0x81, 0x0e, 0xb1, 0x1c, 0x24, 0x70, 0xe9, 0xec, 0x31, 0xe0, 0x0d, 0xa5, 0x16, 0x77, 0x63, 0x1d, 0x39, + 0xd6, 0x51, 0x1c, 0x86, 0x31, 0xe0, 0xca, 0x3a, 0x81, 0xf7, 0xfe, 0xeb, 0x63, 0x13, 0x90, 0x55, 0xbb, 0xc2, 0xab, + 0x51, 0xee, 0xba, 0xd2, 0xe8, 0x4b, 0x4a, 0x4f, 0x78, 0xc1, 0x53, 0xc9, 0x76, 0xdb, 0x33, 0x70, 0xb6, 0xc4, 0x43, + 0xe2, 0x1d, 0x23, 0x7a, 0x31, 0x6d, 0x64, 0xe6, 0x04, 0xce, 0x6c, 0x77, 0xd9, 0xc6, 0xfc, 0xd8, 0x01, 0x0e, 0x16, + 0x41, 0x48, 0xdc, 0x10, 0x86, 0x89, 0x9d, 0x94, 0x43, 0x2d, 0x84, 0xeb, 0x5a, 0x78, 0x1d, 0xa7, 0x65, 0x0c, 0x2e, + 0xd2, 0xda, 0x36, 0x71, 0x0f, 0x5d, 0xf7, 0xfc, 0x98, 0x5b, 0x1d, 0xa3, 0x2d, 0xa4, 0xdf, 0x8e, 0x4e, 0x1f, 0x38, + 0x0c, 0x40, 0xd3, 0x83, 0x59, 0xd5, 0x3e, 0x93, 0xb8, 0x39, 0xed, 0x04, 0x21, 0x11, 0x88, 0xa2, 0x74, 0x46, 0x98, + 0xfe, 0x9d, 0xe6, 0xb2, 0x8a, 0x56, 0xf7, 0xf2, 0xcc, 0x21, 0xcf, 0x42, 0x6f, 0x7b, 0xd0, 0xaa, 0xb9, 0x1b, 0x8c, + 0x13, 0xb7, 0xdb, 0x3b, 0xff, 0x6f, 0x59, 0xd7, 0x56, 0x6b, 0xc4, 0xc3, 0x76, 0xf5, 0x83, 0xc6, 0x5e, 0xed, 0xa9, + 0x18, 0x30, 0x97, 0xd2, 0x3b, 0xa3, 0x4a, 0x5e, 0x64, 0xbc, 0xc4, 0x93, 0xea, 0xb2, 0xe1, 0xe3, 0x7d, 0x93, 0x8d, + 0xcc, 0x03, 0x99, 0x02, 0xe2, 0xf9, 0xc7, 0xd4, 0xa8, 0x8f, 0x53, 0x94, 0x80, 0xbf, 0xd5, 0xf1, 0x8d, 0xe8, 0x7b, + 0xfb, 0xe2, 0x92, 0x57, 0x6f, 0x6e, 0x84, 0x79, 0xf1, 0xcc, 0xea, 0xfc, 0xe9, 0xd3, 0xc2, 0x87, 0x0e, 0x47, 0xed, + 0x1d, 0x14, 0x59, 0x32, 0x71, 0x32, 0x31, 0xb2, 0x36, 0x31, 0x7b, 0xaf, 0xe0, 0x62, 0xa2, 0x0a, 0x3d, 0xeb, 0xec, + 0x09, 0x53, 0x80, 0xbe, 0x71, 0x8c, 0x4a, 0xc6, 0xb0, 0x60, 0xa0, 0x4e, 0x53, 0x42, 0xf4, 0x50, 0xcc, 0x30, 0x5e, + 0x31, 0x80, 0xc2, 0x14, 0x0a, 0x44, 0xd1, 0xd9, 0x87, 0x03, 0x4d, 0xe8, 0xf7, 0x3f, 0xa6, 0x3a, 0x03, 0x2d, 0xeb, + 0x69, 0x01, 0xa2, 0x3a, 0x88, 0xb6, 0x0a, 0x84, 0x39, 0xa5, 0x65, 0x46, 0x97, 0x82, 0xa6, 0x82, 0x26, 0x19, 0xbd, + 0xe0, 0x4a, 0x54, 0x7c, 0x21, 0x98, 0xa2, 0xed, 0x86, 0xb0, 0xff, 0xd8, 0xa0, 0xeb, 0xad, 0x58, 0x6b, 0x68, 0x77, + 0x82, 0x8c, 0xd0, 0x7c, 0xa1, 0x83, 0x90, 0xa1, 0x72, 0x12, 0xf1, 0xe1, 0x35, 0x5e, 0x81, 0x4b, 0xa6, 0xd9, 0x68, + 0x19, 0x97, 0x61, 0x60, 0xbf, 0x0a, 0x2c, 0x26, 0x07, 0x26, 0xbd, 0x5f, 0x9f, 0x3f, 0x95, 0x57, 0x2b, 0x29, 0xb8, + 0xa8, 0x14, 0x44, 0xbf, 0xc1, 0x7d, 0x37, 0x71, 0xd5, 0x59, 0xb3, 0x56, 0x7a, 0xdf, 0xb7, 0x3e, 0x6b, 0xe3, 0xbe, + 0x30, 0x38, 0x06, 0x3b, 0x1f, 0x11, 0x03, 0x69, 0x50, 0xe9, 0x16, 0x87, 0x26, 0x40, 0x97, 0x0e, 0x29, 0x64, 0xc9, + 0x54, 0xa6, 0x4a, 0x50, 0xf1, 0x8d, 0xdf, 0x49, 0x59, 0x8d, 0xfe, 0x5a, 0xf3, 0xe2, 0xee, 0x3d, 0xcf, 0x39, 0x8e, + 0x51, 0x90, 0xc4, 0xe2, 0x3a, 0x2e, 0x03, 0xe2, 0x5b, 0x5e, 0x05, 0x47, 0xa9, 0x09, 0x1b, 0xb3, 0x53, 0x35, 0x6a, + 0xbd, 0x0a, 0xf4, 0x95, 0x51, 0xbe, 0x31, 0x18, 0x9a, 0x88, 0x2a, 0xe8, 0x7b, 0xad, 0xee, 0x69, 0x75, 0xc3, 0x02, + 0xe2, 0xcf, 0x95, 0x5e, 0xa8, 0xf5, 0xba, 0x19, 0x73, 0xc3, 0x44, 0x08, 0x1a, 0x3d, 0xaa, 0x17, 0x0e, 0x3f, 0x7f, + 0xa3, 0x2c, 0x89, 0xe0, 0xc5, 0x26, 0x5d, 0x17, 0x26, 0x96, 0x06, 0xd5, 0x01, 0x73, 0xa3, 0x4d, 0xce, 0x2f, 0x41, + 0xf4, 0xe7, 0xac, 0x88, 0x26, 0x75, 0x4d, 0x15, 0x82, 0x61, 0xb4, 0xb9, 0x6d, 0xa4, 0xd3, 0x3b, 0xf0, 0x72, 0x33, + 0xd6, 0x48, 0xda, 0xd3, 0xb1, 0xa6, 0x05, 0x2f, 0x57, 0x52, 0x94, 0x10, 0xdd, 0xb9, 0x37, 0xa6, 0x57, 0x71, 0x26, + 0xaa, 0x38, 0x13, 0xa7, 0xe5, 0x8a, 0x27, 0xd5, 0x3b, 0xa8, 0x50, 0x1b, 0xe3, 0x60, 0xeb, 0xd5, 0xa8, 0xab, 0x70, + 0xc8, 0x2f, 0x2f, 0x9e, 0xdf, 0xae, 0x62, 0x91, 0xc2, 0xa8, 0xd7, 0xfb, 0x5e, 0x34, 0xa7, 0x63, 0x15, 0x17, 0x5c, + 0x98, 0xa8, 0xc5, 0xb4, 0x62, 0x01, 0xd7, 0x19, 0x03, 0xca, 0x55, 0xec, 0xce, 0x4c, 0xc5, 0x32, 0x8c, 0xcb, 0xf2, + 0xa7, 0xac, 0xc4, 0x3b, 0x00, 0xb4, 0x06, 0x4e, 0x8b, 0x99, 0x01, 0x01, 0xb9, 0xcb, 0x0d, 0x2e, 0x02, 0x0b, 0x8e, + 0x1e, 0x8f, 0x57, 0xb7, 0x01, 0xf5, 0xde, 0x48, 0x75, 0x3d, 0x64, 0xc1, 0x78, 0xf4, 0x24, 0x70, 0xc8, 0x21, 0xfe, + 0x47, 0x8f, 0x8f, 0xf6, 0x7f, 0x33, 0x09, 0x48, 0x3d, 0x05, 0x55, 0x85, 0x51, 0x88, 0xc2, 0xb4, 0xbf, 0x5a, 0xab, + 0x5b, 0xee, 0x9b, 0xf3, 0x92, 0x17, 0xd7, 0x10, 0xad, 0x9d, 0x4c, 0x33, 0x20, 0xe7, 0x52, 0x25, 0xc0, 0xa2, 0x88, + 0xab, 0xaa, 0xc8, 0xce, 0xc1, 0x44, 0x09, 0x0d, 0xc0, 0xcc, 0xd3, 0x0b, 0x74, 0xf8, 0x88, 0xe6, 0x01, 0xf6, 0x29, + 0x58, 0xd4, 0xa4, 0x2e, 0xa1, 0xb0, 0xe4, 0x00, 0x83, 0xd5, 0xa9, 0xb8, 0xd2, 0x0e, 0xe0, 0xbb, 0xfa, 0x23, 0x5a, + 0x4a, 0x8c, 0x35, 0xab, 0xe7, 0x29, 0x3e, 0x2f, 0x65, 0xbe, 0xae, 0x40, 0x7b, 0x7e, 0x51, 0x45, 0x47, 0x8f, 0x57, + 0xb7, 0x53, 0xd5, 0x8d, 0x08, 0x7a, 0x31, 0x55, 0x38, 0x6f, 0x49, 0x9c, 0x27, 0xe1, 0x64, 0x3c, 0xfe, 0xea, 0x60, + 0x78, 0x00, 0xc9, 0x64, 0xfa, 0x69, 0xa8, 0x1c, 0xb9, 0x86, 0x93, 0xf1, 0xb8, 0xfe, 0xa3, 0x36, 0x61, 0xbe, 0x4d, + 0x3d, 0xef, 0xff, 0x38, 0x56, 0xeb, 0xff, 0xe4, 0xf8, 0x50, 0xff, 0xf8, 0xa3, 0xae, 0xa7, 0x4f, 0x8b, 0x70, 0xfe, + 0xcf, 0x50, 0xad, 0xef, 0xd3, 0xa2, 0x88, 0xef, 0x6a, 0x88, 0x6c, 0x2a, 0x9c, 0x77, 0x0d, 0xf5, 0xc8, 0x02, 0x3d, + 0x22, 0xd3, 0x0b, 0xc1, 0xe0, 0x9b, 0x77, 0x55, 0x18, 0xf0, 0x72, 0x35, 0xe4, 0xa2, 0xca, 0xaa, 0xbb, 0x21, 0xe6, + 0x09, 0xf0, 0x53, 0x8b, 0x67, 0x56, 0x18, 0xe2, 0x7b, 0x51, 0x70, 0xfe, 0x89, 0x87, 0xca, 0x58, 0x7c, 0x8c, 0xc6, + 0xe2, 0x63, 0xaa, 0xba, 0x31, 0xf9, 0x86, 0xea, 0xbe, 0x4d, 0xbe, 0x01, 0x93, 0xac, 0xac, 0xfd, 0x8d, 0x32, 0xd6, + 0x8c, 0xc6, 0xf4, 0xfa, 0x45, 0x9e, 0xad, 0xe0, 0x52, 0xb0, 0xd4, 0x3f, 0x6a, 0x42, 0xdf, 0xf1, 0x76, 0xf6, 0xd1, + 0x68, 0xf4, 0xa6, 0xa0, 0xa3, 0xd1, 0xe8, 0x63, 0x56, 0x13, 0xba, 0x12, 0x1d, 0xef, 0xdf, 0x71, 0x7a, 0x2e, 0xd3, + 0xbb, 0x28, 0x08, 0xe8, 0x32, 0x4b, 0x53, 0x2e, 0x54, 0x59, 0xaf, 0xd2, 0x76, 0x5e, 0xd5, 0x42, 0x04, 0x42, 0xd2, + 0x6d, 0x44, 0x48, 0x26, 0x42, 0x1f, 0xec, 0xf4, 0x6c, 0x34, 0x1a, 0xbd, 0x4a, 0x4d, 0xb5, 0xee, 0x82, 0xf2, 0x14, + 0xcd, 0x29, 0x9c, 0x9f, 0x02, 0x58, 0x23, 0x99, 0xe8, 0x2f, 0x87, 0xff, 0x3d, 0x9c, 0xcd, 0xc7, 0xc3, 0x6f, 0x47, + 0x8b, 0x87, 0x87, 0x34, 0x08, 0xfc, 0xd0, 0x0d, 0xa1, 0xb6, 0x6e, 0x99, 0x96, 0xc7, 0xe3, 0x29, 0x29, 0x07, 0xec, + 0xb1, 0xf5, 0x2d, 0xfa, 0xea, 0x31, 0x20, 0xb3, 0xa2, 0x48, 0x39, 0x70, 0xd2, 0x50, 0xbc, 0x9a, 0xbd, 0x14, 0x80, + 0x17, 0x67, 0x23, 0x3b, 0x18, 0xad, 0xe8, 0x38, 0x82, 0xf2, 0x6a, 0x6b, 0x2a, 0xd2, 0x63, 0x2c, 0x33, 0x51, 0x52, + 0xc7, 0xd3, 0xf2, 0x26, 0xab, 0x92, 0x25, 0x06, 0x7a, 0x8a, 0x4b, 0x1e, 0x7c, 0x15, 0x44, 0x25, 0x3b, 0x7a, 0x32, + 0x55, 0x70, 0xc7, 0x98, 0x94, 0xf2, 0x4b, 0x48, 0xfc, 0x76, 0x8c, 0x90, 0xb0, 0x44, 0x7b, 0x70, 0x62, 0x8d, 0x2f, + 0x72, 0x19, 0x83, 0x47, 0x6b, 0xa9, 0x79, 0x38, 0x7b, 0x32, 0x5a, 0x7b, 0x94, 0x56, 0x73, 0x24, 0x34, 0x27, 0x94, + 0x4c, 0x1e, 0x96, 0x54, 0x7e, 0x35, 0x41, 0x2f, 0x29, 0x70, 0x33, 0x8f, 0xe0, 0xf8, 0xb7, 0x96, 0x1e, 0x7a, 0xf9, + 0xa4, 0xec, 0x70, 0xfe, 0xbf, 0x4b, 0xba, 0x18, 0x1c, 0xba, 0xa1, 0x79, 0xa0, 0xdd, 0x79, 0x2b, 0x64, 0x1c, 0xab, + 0xf0, 0x4d, 0x4a, 0xac, 0x31, 0x2e, 0x67, 0x27, 0x1b, 0xd3, 0x9d, 0x51, 0x55, 0x64, 0x57, 0x21, 0xd1, 0xbd, 0x72, + 0x20, 0xa1, 0x41, 0x94, 0x8d, 0x70, 0xfd, 0x80, 0xf5, 0x8c, 0xd7, 0xc9, 0x6b, 0x5e, 0x54, 0x59, 0xa2, 0xde, 0x5f, + 0x37, 0xde, 0xd7, 0xb5, 0x09, 0xa8, 0xfa, 0xb6, 0x60, 0x30, 0xcf, 0x0f, 0x0a, 0x00, 0x31, 0x45, 0x1a, 0xe0, 0x13, + 0xcc, 0x20, 0xa8, 0x5d, 0x33, 0xaf, 0x1a, 0xc1, 0x37, 0xe0, 0xab, 0xb7, 0x05, 0x60, 0x90, 0x84, 0x20, 0x45, 0x86, + 0xd0, 0x40, 0x20, 0xd0, 0x30, 0xe4, 0x02, 0x83, 0x9f, 0x78, 0x71, 0x24, 0x95, 0x53, 0x22, 0x0f, 0x03, 0xfc, 0x11, + 0x50, 0x15, 0x80, 0xc4, 0x78, 0x1c, 0xc2, 0x0b, 0xf5, 0xcb, 0xbd, 0x51, 0x7b, 0x84, 0x3d, 0x4d, 0x43, 0x08, 0x36, + 0x84, 0x0f, 0x01, 0x2c, 0x29, 0x42, 0x1f, 0x20, 0x97, 0x11, 0x06, 0x17, 0x79, 0xb6, 0xd2, 0x49, 0xd5, 0xa8, 0xa3, + 0xf9, 0x50, 0x6a, 0x47, 0x72, 0x40, 0xbd, 0xf4, 0x18, 0xd3, 0x0b, 0x95, 0xae, 0x8a, 0x72, 0x46, 0x39, 0x6f, 0xf5, + 0xc4, 0xb8, 0xb0, 0x85, 0x1c, 0x22, 0xe1, 0xbc, 0x2d, 0x54, 0x28, 0x1c, 0xbe, 0x00, 0x30, 0x30, 0x90, 0x76, 0xec, + 0xc6, 0xbb, 0x51, 0xd9, 0xcf, 0x38, 0x3b, 0xfc, 0xef, 0x79, 0x3c, 0xfc, 0x34, 0x1e, 0x7e, 0xbb, 0x18, 0x84, 0x43, + 0xfb, 0x93, 0x3c, 0x7c, 0x70, 0x48, 0x5f, 0x70, 0xcb, 0xa5, 0xc1, 0xc2, 0x6f, 0x04, 0xfb, 0x51, 0x2b, 0x21, 0x88, + 0x02, 0xbc, 0x61, 0xb9, 0xd5, 0x38, 0x01, 0xc0, 0xc3, 0xe0, 0xbf, 0x02, 0x34, 0x9a, 0x72, 0x17, 0x2f, 0xd0, 0x97, + 0xa8, 0xdf, 0x27, 0x8f, 0x1a, 0x06, 0x83, 0x20, 0xae, 0x51, 0x31, 0x61, 0x88, 0x2e, 0x63, 0xa2, 0x60, 0x90, 0x6d, + 0xf6, 0xed, 0xb6, 0xd7, 0x96, 0x84, 0xe1, 0x97, 0x7e, 0xa6, 0x89, 0x99, 0x77, 0xb8, 0xb1, 0xad, 0xe4, 0x2a, 0x44, + 0xac, 0x40, 0xfd, 0x2b, 0x67, 0x10, 0x7b, 0xf3, 0x3a, 0x03, 0x9f, 0x0e, 0xfb, 0xc5, 0x78, 0x06, 0x6c, 0x14, 0xdc, + 0xf9, 0x0a, 0x7e, 0x91, 0x81, 0x9b, 0xb7, 0x88, 0x51, 0xe0, 0x60, 0x97, 0x44, 0xbf, 0xdf, 0xcb, 0xb3, 0x30, 0xd7, + 0xb8, 0xd3, 0x79, 0x6d, 0xd4, 0x10, 0xa8, 0x23, 0x07, 0xf5, 0x83, 0x1e, 0x82, 0xa1, 0x1a, 0x82, 0xa2, 0xa3, 0x2d, + 0xae, 0x5e, 0x5b, 0x4f, 0x61, 0x7a, 0xab, 0xea, 0x2b, 0x46, 0x7f, 0xca, 0x4c, 0x60, 0x21, 0xed, 0x9a, 0x63, 0x5d, + 0x73, 0x8c, 0xb4, 0xa7, 0xdf, 0x17, 0x0d, 0xf2, 0xd3, 0x59, 0x78, 0x10, 0xa8, 0x52, 0xe5, 0x4e, 0x59, 0x94, 0xdb, + 0xd2, 0xbc, 0x31, 0xac, 0x69, 0x9e, 0xd9, 0x38, 0x37, 0xb3, 0x5e, 0x2f, 0x0c, 0xd1, 0xc1, 0x13, 0x4b, 0xc5, 0xda, + 0x20, 0xdc, 0x91, 0x49, 0x18, 0x5d, 0x81, 0xec, 0x32, 0x3c, 0xe3, 0x04, 0xf9, 0x54, 0x60, 0x1f, 0x54, 0xb5, 0x5e, + 0x4e, 0x78, 0x6c, 0xe4, 0xcb, 0x46, 0xd0, 0x20, 0x2f, 0x29, 0xea, 0x4d, 0xdc, 0x8e, 0x3d, 0x6d, 0x21, 0x57, 0x6e, + 0xea, 0x69, 0x4f, 0x93, 0x8a, 0x1e, 0xeb, 0x55, 0xea, 0x17, 0x58, 0x5a, 0x58, 0xf2, 0x41, 0x68, 0x4f, 0xd3, 0x0a, + 0xcc, 0x70, 0x6d, 0x33, 0x18, 0xfa, 0xe1, 0xf8, 0x09, 0xe8, 0x8c, 0xda, 0x96, 0x10, 0xc6, 0x6e, 0x10, 0x56, 0xde, + 0x13, 0xf9, 0xea, 0xb1, 0x77, 0x31, 0x08, 0xb9, 0xd9, 0xcc, 0xa2, 0x81, 0xe9, 0x7e, 0x2e, 0x9b, 0xcd, 0xd3, 0xcd, + 0xf5, 0xa2, 0x84, 0x0a, 0xd8, 0x6e, 0x2b, 0x41, 0xf0, 0xef, 0xc7, 0x6c, 0x86, 0x7f, 0xb3, 0x7e, 0xbf, 0x17, 0xe2, + 0x2f, 0x8e, 0xc1, 0x8c, 0xe6, 0x62, 0xc1, 0x3e, 0x82, 0x8c, 0x89, 0x44, 0x98, 0xaa, 0x8c, 0x01, 0x59, 0x05, 0x16, + 0x81, 0xe6, 0x03, 0x95, 0x0b, 0x33, 0xd9, 0xcb, 0x9c, 0x6b, 0xc8, 0xf3, 0xd6, 0x38, 0x65, 0xa3, 0x2c, 0x51, 0xae, + 0x1c, 0xd9, 0x28, 0xce, 0xb3, 0xb8, 0xe4, 0xe5, 0x76, 0xab, 0x0f, 0xc7, 0xa4, 0xe0, 0xc0, 0xae, 0x2b, 0x2a, 0x55, + 0xb2, 0x8e, 0x54, 0x0f, 0xbc, 0x34, 0x2c, 0x70, 0x9f, 0xf2, 0x79, 0x61, 0x68, 0xc4, 0x01, 0x08, 0x33, 0x98, 0xba, + 0xa5, 0xf7, 0xc2, 0x02, 0x9a, 0x57, 0x12, 0xb2, 0xc1, 0x54, 0xcf, 0xc2, 0x37, 0x66, 0x62, 0x5e, 0x2c, 0x20, 0xac, + 0x4e, 0xb1, 0xd0, 0xcc, 0x26, 0x4d, 0x58, 0x0c, 0xb0, 0x79, 0x31, 0x99, 0x42, 0x7c, 0x77, 0x55, 0x4e, 0xbc, 0x30, + 0xf7, 0xed, 0xc4, 0x21, 0x87, 0xc0, 0xab, 0xda, 0xa0, 0xab, 0xd9, 0x86, 0xa3, 0x8e, 0x94, 0x13, 0x93, 0xdf, 0x4f, + 0x15, 0x84, 0xb8, 0x13, 0x47, 0xc2, 0xe5, 0xcd, 0x76, 0xe1, 0x65, 0x07, 0x82, 0x8e, 0x1a, 0x9c, 0xf2, 0x33, 0x83, + 0xa3, 0x31, 0x49, 0x37, 0xde, 0x09, 0x52, 0x84, 0x31, 0xd9, 0x48, 0x76, 0x2e, 0x43, 0x31, 0x8f, 0x17, 0xa0, 0xbc, + 0x8c, 0x17, 0x60, 0x69, 0x64, 0x0c, 0x52, 0x41, 0x7e, 0xc7, 0xbd, 0x50, 0x58, 0x14, 0x57, 0x88, 0xf4, 0xac, 0x7e, + 0x4f, 0x8b, 0x76, 0x28, 0x10, 0x14, 0x77, 0x28, 0xf3, 0xe4, 0xac, 0xc7, 0x02, 0x89, 0x0d, 0x01, 0xe3, 0x2b, 0x9d, + 0xa6, 0x5a, 0xeb, 0xde, 0xd8, 0xe8, 0x55, 0xd3, 0x6c, 0x24, 0x64, 0x75, 0x76, 0x01, 0x22, 0x25, 0x1f, 0x1d, 0x1f, + 0xf9, 0x45, 0xdc, 0x59, 0xe6, 0xad, 0x6d, 0x51, 0xc9, 0x4e, 0x36, 0x00, 0x5a, 0xa8, 0xa3, 0x67, 0x29, 0xb9, 0x4d, + 0x49, 0x6a, 0xb7, 0x29, 0x60, 0x25, 0xf9, 0x0b, 0x18, 0x82, 0xaf, 0x1d, 0x08, 0xa7, 0x63, 0x85, 0x78, 0x4d, 0x53, + 0x44, 0x9a, 0x0c, 0x4b, 0x8a, 0x63, 0x5b, 0x22, 0x0a, 0xaa, 0x2d, 0xcb, 0x0e, 0x86, 0x89, 0x12, 0xfc, 0x2c, 0xf5, + 0x28, 0x51, 0x10, 0x50, 0x3d, 0xe4, 0x20, 0xc1, 0xb6, 0x0d, 0x84, 0x07, 0xe4, 0x11, 0xbd, 0xb1, 0xfe, 0x3e, 0xeb, + 0x3c, 0xbb, 0xd0, 0x3c, 0x97, 0xeb, 0x5d, 0x61, 0xc6, 0x08, 0x4f, 0x32, 0x13, 0x36, 0xc0, 0x3b, 0xcf, 0x8c, 0xda, + 0xa6, 0xe7, 0xe1, 0xb5, 0x3d, 0xc7, 0x08, 0x7d, 0x7b, 0x06, 0xdd, 0x04, 0xf3, 0xea, 0xb0, 0x59, 0xaf, 0x14, 0xa4, + 0x86, 0xa9, 0x45, 0x13, 0xb3, 0x9e, 0x35, 0x28, 0xdf, 0x6e, 0x7b, 0x7a, 0xae, 0xf6, 0xcf, 0xdd, 0x76, 0xdb, 0xc3, + 0x6e, 0x3d, 0x4b, 0xbb, 0xad, 0xe2, 0x2b, 0xf5, 0x41, 0x7b, 0xfc, 0xb9, 0x1b, 0x7f, 0x6e, 0x90, 0x4d, 0x4a, 0x47, + 0x33, 0x6d, 0x7d, 0x10, 0x1e, 0x38, 0xbd, 0x6b, 0x34, 0xe9, 0xfb, 0x2c, 0x94, 0x74, 0x25, 0x1a, 0xd5, 0x99, 0x10, + 0x66, 0xac, 0xba, 0x7f, 0xfd, 0xdf, 0xbf, 0x0a, 0xf0, 0x88, 0x53, 0x3b, 0x7b, 0x6f, 0x83, 0x8a, 0x46, 0x5b, 0x38, + 0x52, 0x84, 0x1e, 0x90, 0x84, 0x7d, 0x2d, 0x6b, 0x71, 0x9b, 0xef, 0xb3, 0xfb, 0xe9, 0xd3, 0x87, 0xd4, 0xf7, 0x42, + 0x70, 0xcb, 0x2c, 0x33, 0x07, 0x5e, 0x45, 0x71, 0x40, 0xa3, 0x2e, 0xda, 0x77, 0x95, 0x95, 0x25, 0x78, 0xbd, 0xc0, + 0xbd, 0xf2, 0x3d, 0xf7, 0xe1, 0xf7, 0x2e, 0xab, 0xe6, 0x26, 0x7d, 0x9f, 0xcd, 0xb3, 0xc5, 0x76, 0x1b, 0xe2, 0xdf, + 0xae, 0x16, 0x39, 0x9a, 0x3c, 0x07, 0x9d, 0x26, 0x46, 0x32, 0x62, 0xba, 0x71, 0xde, 0xe6, 0x7f, 0x2d, 0x1a, 0x4e, + 0x13, 0xcf, 0x81, 0x5e, 0xcc, 0x4e, 0x41, 0x26, 0x65, 0x40, 0x0e, 0xc4, 0x4c, 0xaf, 0x19, 0x88, 0x46, 0x26, 0x22, + 0xc0, 0x15, 0xc6, 0x46, 0xa2, 0xd1, 0x09, 0x27, 0x35, 0x01, 0x0b, 0x56, 0x5b, 0xde, 0x4f, 0x96, 0xb6, 0x55, 0xc5, + 0x9d, 0xb7, 0xa4, 0x39, 0xae, 0x03, 0xe7, 0xeb, 0x60, 0x86, 0xd8, 0x94, 0x5d, 0x2d, 0x90, 0xfb, 0xe5, 0x35, 0xed, + 0x8d, 0xeb, 0x04, 0x66, 0x6d, 0x53, 0x5b, 0xc6, 0xcf, 0x96, 0xfe, 0x4e, 0x0f, 0xae, 0x32, 0x06, 0x9b, 0x1b, 0x2b, + 0x0d, 0xbb, 0x6f, 0x3c, 0x5f, 0x0a, 0x08, 0x4f, 0xe7, 0xd3, 0xe3, 0xf7, 0x99, 0x47, 0x8f, 0x81, 0xe8, 0x98, 0x8f, + 0x4a, 0xf7, 0x91, 0xdd, 0xbd, 0x7e, 0x40, 0xc0, 0x79, 0xd5, 0x2e, 0x68, 0x5e, 0x2e, 0x20, 0xb0, 0xaa, 0x57, 0x5e, + 0x61, 0xf9, 0xcc, 0x98, 0x5d, 0x02, 0x19, 0x2a, 0x08, 0x04, 0xee, 0xee, 0x3a, 0x17, 0x62, 0xd5, 0x61, 0x65, 0x4e, + 0x93, 0xb0, 0x93, 0x10, 0xcd, 0x5b, 0x83, 0x59, 0xf0, 0x5f, 0xc1, 0xa0, 0x1c, 0x04, 0x51, 0x10, 0x05, 0x01, 0x19, + 0x14, 0xf0, 0x0b, 0x71, 0xd7, 0x08, 0xc6, 0x6c, 0x81, 0x0e, 0x3f, 0xe0, 0xcc, 0x67, 0x44, 0x5e, 0xfa, 0x61, 0x3d, + 0xbd, 0x01, 0x38, 0x97, 0x32, 0xe7, 0x31, 0xfa, 0x9c, 0x3c, 0xe0, 0x2c, 0x23, 0xf4, 0x81, 0x77, 0x2a, 0xbf, 0xe5, + 0x8d, 0x60, 0x7f, 0xbb, 0xc3, 0xf6, 0x02, 0xe4, 0x15, 0xbd, 0x31, 0x7d, 0xc0, 0x49, 0x94, 0x35, 0x9c, 0xa9, 0x39, + 0xf4, 0xac, 0xb2, 0xac, 0x15, 0x35, 0xe4, 0x06, 0xc5, 0xdc, 0xc8, 0x32, 0x39, 0x99, 0xb6, 0x9a, 0x53, 0x81, 0xeb, + 0xce, 0xae, 0x17, 0x90, 0x1c, 0x0a, 0xcd, 0xd2, 0xd9, 0x70, 0xde, 0xb6, 0x65, 0xcf, 0x5a, 0xa7, 0x90, 0xd7, 0x10, + 0x15, 0x0d, 0xd2, 0x11, 0x50, 0x43, 0x2b, 0x2e, 0x2b, 0x70, 0x61, 0x36, 0xed, 0xe1, 0xa6, 0x3d, 0xa6, 0x19, 0x3f, + 0x41, 0xcc, 0x3c, 0x8e, 0x2d, 0x03, 0x3b, 0x12, 0x87, 0xef, 0xe3, 0x7c, 0x81, 0x76, 0xe9, 0xad, 0xab, 0xc5, 0x23, + 0xac, 0x3d, 0x6f, 0x85, 0x84, 0x00, 0xf1, 0x69, 0x2a, 0xdd, 0x6e, 0x83, 0x00, 0x06, 0xb8, 0xdf, 0xef, 0x01, 0xd7, + 0x6a, 0xd8, 0x49, 0x73, 0x6b, 0xb6, 0xc4, 0x5e, 0x51, 0x78, 0x0c, 0xcc, 0xa9, 0xf9, 0xcf, 0x20, 0xa0, 0x78, 0xee, + 0x86, 0x60, 0x6f, 0xca, 0x4e, 0x36, 0x45, 0xbf, 0xff, 0xac, 0xc0, 0x07, 0x94, 0x0b, 0x83, 0x98, 0x5b, 0xc7, 0xf1, + 0x30, 0xec, 0x93, 0xfa, 0x10, 0xc7, 0x22, 0xcf, 0x42, 0x47, 0x58, 0x2a, 0x43, 0x58, 0xb8, 0x62, 0xa4, 0x83, 0x38, + 0xa8, 0x49, 0xe7, 0x60, 0x55, 0x2e, 0xf8, 0x72, 0xaf, 0xf7, 0x1a, 0x60, 0xd2, 0x33, 0x6f, 0x58, 0x5e, 0x78, 0x80, + 0x68, 0xbd, 0x1e, 0x2e, 0x14, 0x8f, 0x4c, 0x34, 0xd0, 0x38, 0xf1, 0xa5, 0x65, 0xd7, 0x67, 0x5a, 0x56, 0x32, 0x1a, + 0x8d, 0xaa, 0x5a, 0x49, 0x3e, 0xec, 0x77, 0x9f, 0x5a, 0x28, 0x9e, 0x32, 0x4e, 0x79, 0x0a, 0x96, 0xef, 0x86, 0xd2, + 0xcd, 0x17, 0x74, 0xc5, 0x45, 0xaa, 0x7e, 0x7a, 0xe8, 0x9b, 0x0d, 0xe2, 0x9a, 0x35, 0x75, 0x38, 0x76, 0xf8, 0x21, + 0x00, 0xa6, 0x7d, 0x98, 0xb9, 0x74, 0x0d, 0xd3, 0x0b, 0xe2, 0xd9, 0xb8, 0xe0, 0xa1, 0xcb, 0x03, 0xd8, 0x87, 0xe6, + 0x90, 0xc4, 0x4f, 0xe1, 0xe7, 0xcc, 0xa4, 0x75, 0x7c, 0x86, 0xb3, 0x19, 0x95, 0xea, 0x46, 0xd0, 0x7e, 0x0d, 0x89, + 0xc4, 0x20, 0x3d, 0x37, 0x18, 0x8a, 0xd6, 0xdd, 0x06, 0xae, 0xfc, 0x96, 0xde, 0xf9, 0x34, 0x08, 0xb0, 0xbe, 0xb1, + 0x18, 0x00, 0x50, 0xc5, 0x1f, 0xa8, 0xba, 0x32, 0x57, 0x14, 0xd3, 0x30, 0x95, 0x68, 0xef, 0x38, 0xae, 0xa3, 0xc6, + 0x75, 0x58, 0xb0, 0xd2, 0xda, 0x36, 0xbb, 0xb7, 0x10, 0x7f, 0x44, 0x97, 0x80, 0x6a, 0x41, 0xdc, 0x09, 0xe0, 0x43, + 0x23, 0xd5, 0x81, 0x20, 0xbb, 0x0f, 0x0e, 0x00, 0x78, 0xc3, 0xf3, 0x30, 0x84, 0x3f, 0xb0, 0x70, 0x60, 0x59, 0xaa, + 0x7e, 0x2e, 0xa7, 0x31, 0x9c, 0xbb, 0xb9, 0xda, 0xe1, 0xb3, 0x25, 0x28, 0x36, 0xd5, 0x9c, 0x9a, 0xcb, 0x57, 0xde, + 0xd8, 0xef, 0x31, 0xc1, 0x3c, 0x66, 0xb6, 0xe1, 0xb7, 0x9e, 0x6e, 0xeb, 0x1b, 0xec, 0x06, 0x4e, 0xda, 0x0b, 0xa7, + 0xbd, 0xd8, 0x2e, 0x0d, 0xe4, 0x5f, 0xdd, 0x10, 0x22, 0x7c, 0xd0, 0xc4, 0x22, 0x6b, 0xc8, 0x74, 0x2c, 0x56, 0x88, + 0x6a, 0x53, 0xf1, 0x54, 0x1b, 0x08, 0x94, 0x53, 0x75, 0x61, 0x6a, 0xa5, 0x32, 0x61, 0x10, 0x77, 0x4a, 0x58, 0x54, + 0x19, 0x60, 0x18, 0x54, 0x48, 0x71, 0x6d, 0x3d, 0x7f, 0xe2, 0xf2, 0xcd, 0x4c, 0x9b, 0xed, 0xa7, 0x2f, 0xf2, 0xf8, + 0x72, 0xbb, 0x0d, 0xbb, 0x5f, 0x80, 0x39, 0x6a, 0xa9, 0x34, 0x8c, 0xe0, 0x04, 0xa2, 0x24, 0xd7, 0x7b, 0x72, 0x4e, + 0x1c, 0x27, 0xd7, 0x6e, 0xde, 0x6c, 0x27, 0xc5, 0x08, 0x2c, 0xe0, 0xc4, 0x45, 0x3a, 0xd0, 0x52, 0x49, 0x6a, 0x4f, + 0x01, 0x6f, 0xd3, 0x3b, 0x4a, 0x85, 0x57, 0x0b, 0x4d, 0x42, 0x2a, 0x77, 0x2f, 0xb1, 0xa3, 0x06, 0x9c, 0x93, 0xba, + 0x83, 0x80, 0xd3, 0x9e, 0x6e, 0xac, 0x55, 0x24, 0x9b, 0x04, 0xef, 0x95, 0x1e, 0xba, 0x44, 0x3b, 0xb5, 0xbb, 0x6d, + 0x55, 0xb6, 0x50, 0x30, 0x0f, 0x72, 0x96, 0xa8, 0xe3, 0x01, 0x85, 0x2e, 0xea, 0x68, 0xc8, 0x17, 0xa4, 0xd0, 0x2b, + 0x47, 0xab, 0x9a, 0x77, 0x25, 0x03, 0xa5, 0x5a, 0x05, 0x79, 0x4d, 0xac, 0xfb, 0x5a, 0xd6, 0x58, 0x5c, 0x39, 0x21, + 0x85, 0x4d, 0xf8, 0xd2, 0x52, 0x2c, 0xcc, 0x62, 0x6f, 0x4c, 0x7d, 0xe1, 0x12, 0xa1, 0xed, 0x6e, 0x43, 0x8c, 0x36, + 0x58, 0x37, 0xdb, 0xed, 0x87, 0x22, 0x9c, 0x67, 0x0b, 0x2a, 0x47, 0x59, 0x8a, 0x90, 0x6a, 0xc6, 0x63, 0xd9, 0x76, + 0xc1, 0x4c, 0x0c, 0x75, 0xed, 0xf1, 0x92, 0x4c, 0xb1, 0x36, 0x49, 0x8e, 0xe2, 0x73, 0x59, 0xa8, 0xb5, 0x46, 0x08, + 0x1e, 0xee, 0xdf, 0xa5, 0x10, 0xd3, 0xce, 0xac, 0xbb, 0x5f, 0x77, 0x6e, 0x88, 0xdf, 0x41, 0x60, 0x85, 0x92, 0x7d, + 0x28, 0x46, 0xe7, 0x99, 0x48, 0x71, 0xa7, 0xaa, 0x28, 0xc1, 0x6a, 0x1d, 0x34, 0x5b, 0x6e, 0xef, 0xc5, 0x96, 0x28, + 0x40, 0x9c, 0x67, 0xa1, 0x19, 0xcf, 0xca, 0x59, 0xce, 0x64, 0x14, 0x1b, 0x12, 0x95, 0x5e, 0x94, 0x78, 0x9f, 0xa7, + 0x31, 0x3d, 0x74, 0x6b, 0x10, 0x5c, 0x57, 0x77, 0x36, 0xd2, 0x7c, 0x41, 0x88, 0x9a, 0x00, 0x09, 0x1b, 0xd5, 0x9c, + 0x5a, 0x97, 0xe2, 0x7e, 0x56, 0xf9, 0x4e, 0x1f, 0xc4, 0x97, 0x02, 0x78, 0x58, 0x6f, 0x7b, 0x5f, 0x09, 0x8f, 0xb5, + 0xc1, 0xb7, 0xdb, 0xed, 0xa5, 0x98, 0x07, 0x81, 0xc7, 0x68, 0xfe, 0xa0, 0x24, 0xe6, 0xbd, 0x31, 0x85, 0x15, 0xef, + 0xbb, 0xf8, 0x75, 0x93, 0x5a, 0x6b, 0x91, 0xbb, 0xc3, 0xf5, 0x01, 0xcf, 0x53, 0xe2, 0x68, 0x47, 0xe5, 0x54, 0x5a, + 0xdb, 0x01, 0xec, 0x8a, 0xc0, 0x40, 0xd9, 0xbf, 0xa4, 0x6c, 0x03, 0xe6, 0x89, 0x60, 0x7d, 0x84, 0x7e, 0x5b, 0x4a, + 0x7f, 0x32, 0x46, 0xe3, 0x1e, 0xb9, 0xae, 0xa2, 0x23, 0xae, 0xa3, 0xd9, 0xf3, 0xe8, 0x6f, 0x4f, 0xc6, 0xb4, 0x88, + 0x45, 0x2a, 0xaf, 0x40, 0x05, 0x01, 0xca, 0x10, 0x74, 0x84, 0xd0, 0xd4, 0x00, 0x34, 0x08, 0x6e, 0x00, 0x7e, 0xeb, + 0x74, 0xa2, 0xb4, 0x35, 0xf9, 0x18, 0xad, 0xaa, 0xc8, 0x59, 0x1b, 0xda, 0x4d, 0x25, 0x87, 0xe4, 0x61, 0x09, 0xf8, + 0x96, 0xd8, 0x2c, 0x65, 0x83, 0xa2, 0x36, 0x9b, 0x7a, 0xad, 0xd8, 0x91, 0xdb, 0x46, 0xd1, 0x66, 0x2d, 0x6a, 0xbb, + 0x91, 0xf9, 0x62, 0x7a, 0x6b, 0x85, 0x81, 0x53, 0xd3, 0x9a, 0x9b, 0x1d, 0x28, 0x39, 0x5b, 0x9f, 0xc9, 0x4d, 0x80, + 0x38, 0xc0, 0x70, 0xdd, 0xce, 0x6f, 0x16, 0x84, 0xde, 0xb2, 0x5b, 0x2b, 0x56, 0xbd, 0xb1, 0x72, 0x11, 0x93, 0x76, + 0x33, 0x98, 0xc0, 0x65, 0x9c, 0x15, 0xf6, 0x85, 0x56, 0x37, 0x14, 0x1d, 0x6d, 0x93, 0xf6, 0xf3, 0x8e, 0x76, 0xc3, + 0x05, 0xdf, 0x8a, 0x75, 0x9c, 0x5b, 0xd6, 0x54, 0xa1, 0x69, 0x07, 0x7a, 0x3b, 0x04, 0x34, 0x67, 0x63, 0xba, 0xa4, + 0x29, 0x5e, 0xa0, 0xe9, 0x1a, 0xcc, 0x74, 0x2e, 0xa0, 0xaf, 0xdd, 0x3e, 0xda, 0x17, 0xaa, 0x27, 0xc2, 0x5b, 0xa2, + 0xe0, 0xdb, 0x92, 0x82, 0x97, 0x5a, 0xce, 0x63, 0x33, 0x87, 0x80, 0x4f, 0xa3, 0x4a, 0xf4, 0x4e, 0x8a, 0x4b, 0xd0, + 0x66, 0xc2, 0x11, 0x68, 0xaa, 0x46, 0x6c, 0xe5, 0x00, 0xb7, 0x17, 0x4f, 0x03, 0x42, 0x41, 0xaa, 0xbb, 0xb6, 0x2b, + 0xf2, 0x96, 0x9d, 0x6c, 0x6e, 0xc1, 0x4c, 0xb8, 0x5a, 0x97, 0xad, 0xaf, 0x6c, 0xb2, 0xfb, 0xb8, 0x26, 0xd8, 0x76, + 0x6f, 0x83, 0x84, 0xb7, 0xf4, 0x86, 0x6c, 0x6e, 0xfa, 0xfd, 0x10, 0xfa, 0x43, 0xa8, 0xee, 0xd0, 0x6d, 0x67, 0x87, + 0x6e, 0xbd, 0x76, 0x9e, 0x5b, 0x3d, 0x9f, 0xf2, 0x0e, 0xf9, 0x80, 0x26, 0x6b, 0x74, 0x15, 0xdf, 0xc1, 0xa6, 0x8e, + 0x2a, 0xaa, 0x2a, 0x8f, 0x12, 0x0a, 0x2a, 0xf1, 0x8c, 0x97, 0xef, 0x39, 0xc6, 0x7a, 0xd5, 0x4f, 0x6f, 0x35, 0xaf, + 0xb6, 0x36, 0x6b, 0xb3, 0x5c, 0x9f, 0x83, 0x85, 0xc4, 0x39, 0x8f, 0xae, 0x34, 0x2d, 0xb9, 0xf4, 0xa1, 0x34, 0x71, + 0x54, 0x82, 0x8b, 0x38, 0xcb, 0x41, 0x8d, 0x7b, 0xd1, 0xec, 0x7f, 0xa8, 0x6d, 0xc7, 0x96, 0x8d, 0x33, 0xf7, 0x3a, + 0x24, 0x9b, 0xff, 0xb1, 0x81, 0x7a, 0x1a, 0x62, 0x84, 0x58, 0xb3, 0xa0, 0xdf, 0x30, 0x88, 0x15, 0x1a, 0x94, 0xeb, + 0x24, 0xe1, 0x65, 0x19, 0x18, 0xa5, 0xd6, 0x9a, 0xad, 0xcd, 0x79, 0xf6, 0x96, 0x9d, 0xbc, 0xed, 0x31, 0x76, 0x4b, + 0x68, 0xa2, 0x75, 0x42, 0xa6, 0xc6, 0xc8, 0xd3, 0x02, 0xe9, 0x0e, 0x45, 0xd9, 0x45, 0xf8, 0x06, 0x85, 0x2c, 0xed, + 0x7d, 0x6e, 0x4e, 0x64, 0xf5, 0x8d, 0x36, 0x42, 0x89, 0x54, 0x22, 0xc8, 0xc6, 0x6f, 0x10, 0xc0, 0x18, 0x9a, 0x1d, + 0x90, 0xcd, 0x92, 0xbd, 0xa2, 0x67, 0xd6, 0x24, 0x08, 0x5e, 0xbf, 0x51, 0x89, 0x66, 0x94, 0x15, 0xd1, 0x55, 0x46, + 0x3f, 0x77, 0x21, 0x89, 0xce, 0x42, 0xe2, 0xe7, 0x86, 0xa5, 0x75, 0x1d, 0xa2, 0x98, 0xd9, 0x6c, 0x78, 0xad, 0x88, + 0x6a, 0x6c, 0x2b, 0xe3, 0x63, 0x7e, 0x6b, 0xd3, 0xc8, 0x14, 0xfa, 0x3a, 0x9c, 0xf4, 0xfb, 0xf0, 0x57, 0xd3, 0x0f, + 0xbc, 0xa5, 0xe0, 0x2f, 0xf6, 0x96, 0xd4, 0x09, 0x0b, 0x00, 0x9e, 0x31, 0xe7, 0x55, 0x73, 0x02, 0xdf, 0xb2, 0x93, + 0xcd, 0xdb, 0xf0, 0x55, 0x63, 0xe6, 0xee, 0x42, 0xbc, 0x54, 0x25, 0x3d, 0x6f, 0x9e, 0xcc, 0x40, 0xac, 0xac, 0xd6, + 0xfc, 0x96, 0x59, 0x7d, 0x02, 0x10, 0xa9, 0x5b, 0xeb, 0x60, 0x8b, 0x1f, 0x9b, 0x2e, 0x93, 0x4d, 0xca, 0xda, 0x4c, + 0x94, 0x52, 0x91, 0x34, 0x17, 0x01, 0xf4, 0x1b, 0x86, 0xa3, 0x06, 0xb8, 0x73, 0x3d, 0xf6, 0x66, 0x68, 0xbc, 0x31, + 0x35, 0xf4, 0x6c, 0xa3, 0x97, 0xb7, 0xa3, 0x10, 0x66, 0x2c, 0xa2, 0x5b, 0x77, 0x2c, 0x86, 0xaf, 0xe8, 0x1b, 0xa8, + 0xf0, 0x69, 0x88, 0xd1, 0x85, 0x49, 0x5d, 0x4f, 0xd7, 0x6a, 0x2b, 0xdd, 0x10, 0x9a, 0x63, 0x54, 0x23, 0xaf, 0x6d, + 0x77, 0xd4, 0x08, 0xed, 0x09, 0xe5, 0xe1, 0x2d, 0xad, 0xe8, 0x8d, 0x65, 0x11, 0x9c, 0xfc, 0xd8, 0xcb, 0x4f, 0xe8, + 0xb9, 0x27, 0x30, 0x29, 0xda, 0x1a, 0xc0, 0x5f, 0x50, 0x3f, 0x9c, 0xd5, 0x53, 0x2b, 0xe5, 0xf0, 0x14, 0xbe, 0x64, + 0x03, 0x72, 0x05, 0xbd, 0x58, 0x63, 0x76, 0x12, 0x83, 0x0e, 0x6a, 0x67, 0x77, 0x78, 0x93, 0x52, 0x86, 0x68, 0x8d, + 0xe8, 0x20, 0xaf, 0x7e, 0x03, 0x4d, 0x1f, 0xa4, 0x85, 0x29, 0x5d, 0xa3, 0x80, 0x07, 0xf4, 0x4d, 0xfd, 0x7e, 0x8e, + 0xcf, 0xb5, 0x67, 0x99, 0xa6, 0x2c, 0x90, 0x09, 0x5d, 0x7a, 0x71, 0xbb, 0x40, 0xda, 0xec, 0x58, 0x05, 0x60, 0x45, + 0x12, 0x68, 0x44, 0x02, 0x96, 0x4b, 0x9e, 0xb8, 0x6c, 0x83, 0x06, 0x35, 0x51, 0x49, 0x21, 0x4b, 0x24, 0x81, 0x1f, + 0x46, 0x50, 0xa6, 0x28, 0x06, 0x71, 0xaf, 0x5e, 0x5e, 0x71, 0x4d, 0x0d, 0x58, 0x53, 0x04, 0x13, 0xac, 0xd3, 0x29, + 0x10, 0x5b, 0xb1, 0x5e, 0x81, 0x27, 0xaa, 0xbb, 0x48, 0x22, 0x4b, 0x80, 0x06, 0x7a, 0xbe, 0x74, 0xda, 0x2d, 0x6f, + 0x4f, 0xb4, 0x54, 0xb1, 0xb9, 0xf7, 0x62, 0x61, 0xb9, 0xc7, 0xca, 0xdf, 0x0e, 0xb4, 0x17, 0x56, 0x3b, 0x22, 0x6a, + 0xb0, 0x3a, 0x6c, 0xdb, 0xf9, 0xa1, 0x34, 0x54, 0xf7, 0xca, 0x31, 0x01, 0x15, 0x5d, 0xc5, 0xd5, 0x32, 0xca, 0x46, + 0xf0, 0x67, 0xbb, 0x0d, 0x0e, 0x03, 0xb0, 0x08, 0xfd, 0xe5, 0xdd, 0x4f, 0x11, 0x86, 0xab, 0xfa, 0xe5, 0xdd, 0x4f, + 0xdb, 0xed, 0x93, 0xf1, 0xd8, 0x70, 0x05, 0x4e, 0xad, 0x03, 0xfc, 0x81, 0x61, 0x1b, 0xec, 0x92, 0xdd, 0x6e, 0x9f, + 0x00, 0x07, 0xa1, 0xd8, 0x06, 0xb3, 0x8b, 0x95, 0x63, 0x9b, 0x62, 0x35, 0xf4, 0x8e, 0x04, 0xec, 0xbe, 0x1d, 0x96, + 0x62, 0x97, 0xfa, 0xa8, 0x90, 0x94, 0x7a, 0xd1, 0x3f, 0xef, 0x14, 0x58, 0x52, 0x30, 0xe5, 0x0d, 0x96, 0x55, 0xb5, + 0x2a, 0xa3, 0xc3, 0xc3, 0x78, 0x95, 0x8d, 0xca, 0x0c, 0xb6, 0x79, 0x79, 0x7d, 0x09, 0x00, 0x13, 0x01, 0x6d, 0xbc, + 0x5b, 0x8b, 0xcc, 0xbc, 0x58, 0xd0, 0x65, 0x86, 0x6b, 0x12, 0xcc, 0x0e, 0x72, 0x6e, 0x75, 0x93, 0x53, 0x62, 0x1f, + 0xc0, 0x06, 0x73, 0xbb, 0x6d, 0xf0, 0x0b, 0x27, 0xa3, 0x27, 0xb3, 0x65, 0xa6, 0x0d, 0x5c, 0xb9, 0xd9, 0xff, 0x24, + 0xf2, 0xd2, 0x50, 0xf1, 0x49, 0xa6, 0xcf, 0x33, 0xe0, 0xf3, 0xd8, 0x27, 0x11, 0xfa, 0x2c, 0x57, 0xa3, 0x35, 0xc0, + 0xc6, 0x66, 0x17, 0x77, 0xa3, 0x94, 0x43, 0x84, 0x8e, 0xc0, 0xaa, 0x6b, 0x96, 0x19, 0xf1, 0x6d, 0x2a, 0x6e, 0x5b, + 0xaa, 0xb0, 0x4f, 0xc2, 0x73, 0xde, 0xe1, 0xc6, 0x71, 0xa8, 0x37, 0x89, 0xc2, 0xe7, 0x28, 0x44, 0xe5, 0x68, 0x5c, + 0xe8, 0xe4, 0x6b, 0x99, 0xc7, 0x84, 0x62, 0x0e, 0xf7, 0xee, 0x9f, 0xa9, 0x33, 0x97, 0xf1, 0x85, 0x7b, 0xcf, 0x7d, + 0x99, 0xc9, 0xb5, 0x04, 0x90, 0x28, 0x55, 0xfb, 0xcf, 0x9f, 0x91, 0x1a, 0xff, 0x95, 0x6a, 0x0d, 0x40, 0xef, 0x67, + 0xa8, 0xc9, 0x11, 0x04, 0x6c, 0xc5, 0xd4, 0x8f, 0x2e, 0x60, 0x25, 0xf3, 0x3f, 0xa1, 0x6e, 0x47, 0xb0, 0x8d, 0x8a, + 0x27, 0x14, 0x55, 0xb4, 0xe0, 0xe9, 0x5a, 0xa4, 0xb1, 0x48, 0xee, 0x22, 0x5e, 0x4f, 0xb1, 0x24, 0x66, 0x23, 0x86, + 0xfd, 0xdc, 0xec, 0xc2, 0xbb, 0xa2, 0x61, 0x12, 0x4f, 0x4b, 0x7f, 0x5b, 0x79, 0x9b, 0xc9, 0x32, 0xce, 0xc8, 0x94, + 0x2b, 0x04, 0x73, 0xab, 0xef, 0x31, 0x27, 0xf8, 0xe3, 0xa3, 0xc7, 0x84, 0x5e, 0xcb, 0x69, 0x89, 0x20, 0x7d, 0x22, + 0xb5, 0xae, 0xab, 0xd8, 0xaf, 0x29, 0x44, 0xb5, 0x10, 0x0c, 0x42, 0x99, 0x9a, 0xf6, 0x29, 0xbe, 0xcf, 0x96, 0xfd, + 0xc9, 0x94, 0x2d, 0xc9, 0x46, 0x40, 0xc7, 0xa4, 0xf3, 0x7e, 0xf5, 0xf6, 0xec, 0xcc, 0xfb, 0x0d, 0x9a, 0x70, 0x50, + 0xdd, 0x40, 0xbb, 0x0a, 0x32, 0x8d, 0x51, 0x6c, 0x16, 0x63, 0xed, 0xd6, 0x44, 0x04, 0x41, 0xb8, 0xcb, 0x59, 0xd8, + 0x6e, 0x27, 0xc4, 0xdb, 0x40, 0x02, 0x05, 0xae, 0x6d, 0x94, 0x93, 0x90, 0xa8, 0x0b, 0x99, 0x39, 0x26, 0x24, 0x0b, + 0xf4, 0x1a, 0x3b, 0x0a, 0xe8, 0x29, 0xb7, 0x4f, 0x01, 0x7d, 0x51, 0xb0, 0x53, 0x3e, 0x08, 0x86, 0x18, 0x6f, 0x36, + 0xa0, 0x1f, 0xa5, 0x7a, 0x04, 0x8f, 0x69, 0x60, 0xb9, 0xe8, 0xeb, 0x82, 0x21, 0xcc, 0xd2, 0xef, 0x29, 0x9b, 0x7c, + 0xf3, 0x77, 0x37, 0xbf, 0xe7, 0x5a, 0xcc, 0x0e, 0x42, 0x71, 0x7b, 0x3d, 0x01, 0xe2, 0x57, 0xf1, 0x2b, 0xb0, 0x36, + 0xd7, 0x12, 0x6f, 0x4f, 0xf2, 0x20, 0x7c, 0x39, 0xba, 0xfd, 0xa4, 0x34, 0x9f, 0x40, 0xd0, 0x1e, 0x27, 0x29, 0x77, + 0xdf, 0xbd, 0x97, 0xae, 0x22, 0x18, 0x2d, 0x40, 0xf0, 0xdb, 0x5b, 0xc9, 0x59, 0x53, 0xf8, 0x8f, 0x75, 0xbe, 0xc0, + 0x58, 0x2a, 0xf2, 0x3d, 0x4e, 0x7f, 0x13, 0x1c, 0xdc, 0xbf, 0x95, 0x59, 0x43, 0xa2, 0x73, 0xf5, 0x11, 0xd0, 0xff, + 0xb1, 0x1e, 0xbf, 0x53, 0x94, 0xf4, 0x25, 0x71, 0x8e, 0xf0, 0x4d, 0xbc, 0x44, 0xd3, 0xc5, 0xde, 0xb8, 0xa6, 0x9f, + 0x0a, 0xf3, 0x42, 0x2b, 0x38, 0xec, 0x5b, 0xa3, 0xf0, 0xc0, 0x33, 0xef, 0x3b, 0xd1, 0x10, 0x74, 0xff, 0x03, 0xf7, + 0xc6, 0x77, 0x82, 0x65, 0x78, 0x53, 0xce, 0x32, 0x73, 0x87, 0xbb, 0xc9, 0x44, 0x2a, 0x6f, 0x18, 0x0b, 0xd6, 0x42, + 0x99, 0xf3, 0xa6, 0xc1, 0x6c, 0x53, 0x47, 0x2a, 0xd9, 0x7d, 0xff, 0x67, 0xe3, 0x84, 0xcd, 0x06, 0xc1, 0xfb, 0x4a, + 0x16, 0xf1, 0x25, 0x0f, 0xa6, 0x5a, 0x45, 0x91, 0x81, 0x5d, 0x21, 0x20, 0xe5, 0x38, 0xed, 0x1d, 0x3c, 0x59, 0x6a, + 0x66, 0x42, 0x7e, 0x5b, 0x9d, 0x05, 0xbc, 0x35, 0xa3, 0x79, 0x5a, 0xc1, 0x2e, 0xf3, 0x95, 0x14, 0xdf, 0xb5, 0x24, + 0xd9, 0x58, 0x7f, 0x43, 0x86, 0x6d, 0xe5, 0x33, 0xe7, 0x80, 0xb9, 0xf3, 0x51, 0xaa, 0xa0, 0x7f, 0x3d, 0xc6, 0x6e, + 0x24, 0x12, 0x01, 0xe1, 0x2c, 0x26, 0xee, 0x84, 0x09, 0x87, 0xe9, 0x02, 0x05, 0xc5, 0x18, 0x28, 0xe8, 0xbd, 0x0c, + 0x39, 0x3d, 0xe5, 0x83, 0xa4, 0x31, 0x5b, 0x7f, 0xaa, 0x12, 0xe9, 0x8d, 0x24, 0xf4, 0x0c, 0x7e, 0x8f, 0x5b, 0x3c, + 0x50, 0x23, 0x58, 0xa7, 0xbb, 0x39, 0x1d, 0xbe, 0x2e, 0xc8, 0xf0, 0x77, 0xf0, 0x76, 0x8b, 0xed, 0x65, 0x39, 0x81, + 0xc5, 0x1d, 0x7b, 0xc5, 0xd3, 0x5c, 0xb5, 0x38, 0x21, 0x1e, 0xb1, 0xc8, 0x7d, 0x62, 0x01, 0x23, 0x6a, 0x18, 0x8d, + 0x1f, 0xdf, 0xbf, 0x79, 0xad, 0x31, 0xac, 0x72, 0xff, 0x03, 0x18, 0x51, 0x2d, 0x6d, 0xb7, 0x03, 0xbe, 0x1c, 0xa1, + 0x01, 0x7b, 0xea, 0x06, 0xbb, 0xdf, 0x37, 0x69, 0x27, 0xa5, 0x97, 0xcd, 0x89, 0x41, 0x77, 0x94, 0x36, 0x4b, 0x65, + 0x60, 0xdc, 0x55, 0x38, 0x9a, 0x13, 0x1b, 0xb1, 0xaa, 0xf7, 0x61, 0xb8, 0xa4, 0xb1, 0x95, 0x95, 0xdb, 0xdd, 0x84, + 0x23, 0x9b, 0x00, 0xd7, 0xa7, 0xa0, 0xbd, 0x9a, 0x73, 0xd0, 0x82, 0x12, 0x05, 0x8e, 0x68, 0xbb, 0x0d, 0x21, 0x22, + 0x49, 0x31, 0x9c, 0xcc, 0xc2, 0x62, 0x38, 0x54, 0x03, 0x5f, 0x10, 0x12, 0x7d, 0x2a, 0xe6, 0xd9, 0x42, 0x21, 0x18, + 0xf9, 0x3b, 0xe9, 0xd7, 0x42, 0x71, 0xca, 0xbd, 0xef, 0x04, 0xd9, 0xfc, 0x23, 0xc5, 0x18, 0x8c, 0x4e, 0xb3, 0x99, + 0x81, 0x84, 0xf5, 0xb4, 0x22, 0x6a, 0x1d, 0xd9, 0xd9, 0x00, 0x55, 0x2c, 0x9a, 0x06, 0x83, 0xba, 0xc5, 0x13, 0xeb, + 0x19, 0xbd, 0x07, 0x95, 0x20, 0xaa, 0x05, 0xbb, 0x31, 0x5c, 0x6b, 0xaf, 0x45, 0x28, 0x29, 0x27, 0x4d, 0x66, 0xc6, + 0x8a, 0x06, 0x0b, 0x10, 0x92, 0xc6, 0x65, 0xf5, 0x4a, 0xa6, 0xd9, 0x45, 0x06, 0x08, 0x12, 0xce, 0x9f, 0x50, 0x36, + 0xde, 0x3c, 0x55, 0xf3, 0xd2, 0x95, 0x38, 0xb3, 0xb0, 0x27, 0x5d, 0x6f, 0x69, 0x41, 0xa2, 0x02, 0x68, 0x94, 0xaf, + 0xe5, 0xf9, 0x7e, 0xc7, 0x2a, 0x64, 0xf7, 0xc3, 0xa9, 0xb2, 0x1d, 0xe2, 0x27, 0xac, 0x22, 0xde, 0x69, 0x5d, 0x29, + 0x91, 0x46, 0x47, 0xdb, 0x80, 0x18, 0xb6, 0xec, 0x5b, 0xd4, 0xf0, 0x41, 0xd8, 0x45, 0x27, 0xf9, 0x41, 0x4f, 0xf1, + 0xd8, 0x1a, 0x48, 0xfa, 0x5a, 0x04, 0x5f, 0xa3, 0x23, 0x9d, 0x28, 0xd3, 0x48, 0x4c, 0x21, 0xd1, 0xaf, 0x17, 0x5a, + 0x63, 0x19, 0x65, 0x5f, 0x91, 0xff, 0xb3, 0xee, 0xde, 0x77, 0x62, 0xbb, 0x85, 0x49, 0xf6, 0x3c, 0xd0, 0x60, 0x53, + 0xa3, 0x56, 0x08, 0x67, 0xe7, 0xb4, 0x42, 0xed, 0x58, 0x2f, 0x2c, 0x81, 0x3c, 0x80, 0xad, 0x48, 0x83, 0x32, 0x48, + 0xf6, 0xa9, 0x98, 0x8b, 0x85, 0x13, 0xe5, 0x48, 0x85, 0x7f, 0x26, 0x47, 0x29, 0x87, 0xab, 0x58, 0x58, 0x30, 0xe4, + 0x57, 0x47, 0x17, 0x85, 0xbc, 0x02, 0x49, 0x89, 0x61, 0xa8, 0x2c, 0xaf, 0x8b, 0xab, 0xb6, 0x24, 0xb4, 0x77, 0x06, + 0xa0, 0x34, 0x05, 0x08, 0x5e, 0x1a, 0x35, 0xc4, 0x6c, 0xa3, 0x76, 0x57, 0xb4, 0x97, 0x1c, 0x50, 0xa7, 0xbb, 0x76, + 0xeb, 0x4d, 0xd9, 0xaa, 0x5b, 0x71, 0xe1, 0x9f, 0x50, 0xfa, 0x29, 0x1f, 0x14, 0x3e, 0x95, 0xc0, 0x8d, 0xaf, 0x36, + 0x59, 0x76, 0x71, 0x87, 0x4b, 0xbf, 0x6a, 0x8c, 0x5f, 0xbf, 0xdf, 0x53, 0x0b, 0xa1, 0x91, 0x0a, 0xcc, 0xb7, 0xcf, + 0x4c, 0x55, 0x46, 0x53, 0x6a, 0x2f, 0xc1, 0x95, 0xb3, 0x1f, 0x41, 0x45, 0x5c, 0x57, 0xa4, 0x36, 0x35, 0x40, 0x07, + 0x5e, 0x56, 0xb8, 0x95, 0x05, 0x78, 0xec, 0x04, 0x64, 0xbb, 0xe5, 0x61, 0xa0, 0x0f, 0x9d, 0xc0, 0xdf, 0x92, 0xaf, + 0x90, 0x59, 0xb3, 0x8f, 0xff, 0xd2, 0x82, 0x7f, 0x6c, 0xc1, 0x4f, 0x28, 0xee, 0xb4, 0x32, 0xff, 0x56, 0x5a, 0xb7, + 0xb8, 0x7f, 0x27, 0xd3, 0x84, 0xa2, 0x32, 0xa1, 0xf6, 0x2b, 0xfd, 0xd1, 0x04, 0x8f, 0x52, 0xd9, 0x3f, 0x48, 0xf8, + 0x60, 0xd6, 0x78, 0x62, 0x8d, 0x27, 0xc3, 0xe9, 0x56, 0x1a, 0x96, 0x01, 0x85, 0x7e, 0x5e, 0xe6, 0x8a, 0xea, 0xe7, + 0x9f, 0xd7, 0x7c, 0xcd, 0x9b, 0x2d, 0xb6, 0x49, 0xf7, 0x34, 0xd8, 0xcb, 0xa3, 0x29, 0x85, 0x93, 0xa8, 0x73, 0x23, + 0x51, 0x17, 0x35, 0xcb, 0x50, 0x9d, 0xe0, 0xd5, 0x3c, 0xd5, 0xc3, 0xde, 0x4c, 0x44, 0x6b, 0x25, 0x65, 0x89, 0x01, + 0x6b, 0x1d, 0x79, 0x48, 0xee, 0xd6, 0x3a, 0xee, 0x34, 0xd4, 0xa5, 0x29, 0xd4, 0x04, 0x2b, 0x5c, 0x80, 0x23, 0xe8, + 0x5d, 0x11, 0x72, 0xb8, 0xa6, 0x2a, 0xfd, 0x82, 0xa6, 0xe4, 0x89, 0xa7, 0xa8, 0xd5, 0x8a, 0x74, 0xfb, 0x51, 0x8e, + 0xdd, 0xf0, 0x8d, 0x13, 0x72, 0x62, 0x84, 0xfe, 0xee, 0x58, 0xca, 0x19, 0x5a, 0x3c, 0xa8, 0x13, 0xac, 0x97, 0xb7, + 0x14, 0x28, 0xe6, 0xe8, 0xb2, 0xea, 0x9a, 0x97, 0x68, 0xfb, 0xb2, 0xec, 0xf7, 0x73, 0x5b, 0x4f, 0xca, 0x4e, 0x36, + 0x4b, 0xb3, 0x0f, 0x51, 0x31, 0x85, 0xbb, 0x3e, 0xd1, 0xfc, 0x55, 0xa8, 0xaf, 0xda, 0x32, 0xe7, 0x23, 0x8e, 0x38, + 0x21, 0x39, 0xa9, 0xff, 0xa5, 0xa6, 0x5e, 0x89, 0xfb, 0x55, 0x25, 0xbf, 0x0a, 0x63, 0xc5, 0x68, 0x89, 0x21, 0x8a, + 0xb4, 0x7b, 0x63, 0xfa, 0xb2, 0x00, 0xf8, 0x2b, 0xc1, 0x3e, 0xa5, 0xa1, 0x56, 0x7e, 0x8b, 0xb6, 0x80, 0x7f, 0xa3, + 0xb8, 0x01, 0xab, 0xc0, 0x00, 0xa3, 0xc9, 0xf6, 0x9c, 0x26, 0x70, 0xc0, 0x09, 0xad, 0xa2, 0xa0, 0xc2, 0x0c, 0x0d, + 0xb5, 0x85, 0xd1, 0x57, 0x28, 0xe3, 0x56, 0x99, 0xbd, 0x1b, 0x63, 0xa7, 0x05, 0x5e, 0xc3, 0xbf, 0xd1, 0x0b, 0xc5, + 0x6c, 0xd4, 0x41, 0x7a, 0x74, 0x12, 0xd3, 0x1f, 0xb7, 0x70, 0x72, 0xb3, 0x70, 0x96, 0x35, 0x4b, 0xa0, 0x3b, 0x70, + 0x41, 0x8c, 0xfb, 0xfd, 0x1c, 0x8e, 0x4c, 0x33, 0xf2, 0x05, 0xcb, 0x69, 0xcc, 0x96, 0x54, 0x7b, 0x1e, 0x5e, 0x56, + 0x61, 0x4e, 0x97, 0x56, 0xc6, 0x9b, 0x32, 0x50, 0x19, 0x6d, 0xb7, 0x21, 0xfc, 0xe9, 0xb6, 0x76, 0x49, 0xe7, 0x4b, + 0xc8, 0x00, 0x7f, 0x40, 0x22, 0x8a, 0x58, 0xe0, 0xff, 0x56, 0xe3, 0x94, 0x9e, 0x28, 0xad, 0x59, 0x42, 0xd7, 0x4c, + 0xd7, 0x4f, 0x2f, 0xd8, 0xba, 0xb1, 0x14, 0xb6, 0xdb, 0xb0, 0x99, 0xc0, 0x34, 0xe7, 0x4a, 0xa6, 0x17, 0xa8, 0x93, + 0x02, 0x2a, 0x16, 0x5e, 0xe0, 0xf2, 0x4b, 0x09, 0x85, 0xe6, 0xce, 0x97, 0x0b, 0xa3, 0xc4, 0x84, 0x56, 0xc9, 0x2f, + 0x1f, 0x2a, 0xf3, 0xb5, 0xf1, 0x10, 0xfc, 0x31, 0x0d, 0x13, 0x53, 0x24, 0x2a, 0x44, 0x67, 0xbf, 0x82, 0x2c, 0x47, + 0x00, 0xae, 0xe7, 0x2b, 0x88, 0x02, 0xb7, 0x86, 0xb8, 0xf0, 0xd0, 0xa0, 0xb7, 0x85, 0xbc, 0xca, 0x4a, 0x1e, 0xe2, + 0x3d, 0xc1, 0xd3, 0x8c, 0xee, 0x37, 0xf8, 0xd0, 0xd6, 0x1e, 0x3d, 0x41, 0x36, 0x9e, 0x72, 0xbf, 0xfe, 0x55, 0x84, + 0x73, 0x88, 0xde, 0xb9, 0xa0, 0x5a, 0x5d, 0xed, 0x00, 0xb9, 0x3c, 0xdb, 0xab, 0xb7, 0x70, 0xba, 0xe9, 0xeb, 0x5b, + 0x15, 0x3a, 0x73, 0x00, 0x69, 0x0f, 0xc9, 0xba, 0xe6, 0x7a, 0x07, 0x78, 0x47, 0xe2, 0x1a, 0x68, 0xac, 0xdb, 0x9a, + 0x9d, 0xf6, 0x28, 0x1e, 0x13, 0x99, 0x19, 0x8b, 0x14, 0x63, 0xee, 0xd6, 0x69, 0x51, 0xb4, 0x41, 0x33, 0x84, 0xdd, + 0xbb, 0x4e, 0xb6, 0x6e, 0x45, 0x9c, 0xdf, 0x6f, 0xfb, 0x02, 0xa3, 0x61, 0xcc, 0xb5, 0x7b, 0xbe, 0xa1, 0xdb, 0xda, + 0x8d, 0x8c, 0x46, 0x82, 0xcc, 0xd4, 0x81, 0x28, 0x6b, 0x6b, 0xc0, 0xf6, 0x96, 0xeb, 0x4d, 0x0b, 0xfc, 0xbc, 0x89, + 0xc1, 0xdb, 0xb3, 0xc6, 0x29, 0xad, 0xaf, 0x71, 0xcd, 0x71, 0x55, 0x88, 0xa8, 0x2d, 0x52, 0x00, 0x0c, 0x3b, 0x5f, + 0xe0, 0xce, 0xac, 0x30, 0x98, 0x13, 0x96, 0x4a, 0x76, 0x2a, 0xd7, 0x9f, 0xc3, 0x16, 0x07, 0xa9, 0x7c, 0xe9, 0xf5, + 0xf7, 0x5f, 0xbe, 0xf8, 0x02, 0xdd, 0xf6, 0x9c, 0x1f, 0x41, 0x90, 0x09, 0x74, 0x50, 0x53, 0xaa, 0xc7, 0x1f, 0x0a, + 0xa0, 0xf6, 0x30, 0x0f, 0x3f, 0x14, 0x4c, 0xc4, 0xd7, 0xd9, 0x65, 0x5c, 0xc9, 0x62, 0x74, 0xcd, 0x45, 0x2a, 0x0b, + 0x2b, 0x35, 0x0e, 0x4e, 0x57, 0xab, 0x9c, 0x07, 0x60, 0x2a, 0x6f, 0x19, 0x65, 0x27, 0x97, 0xd4, 0x83, 0xab, 0xe5, + 0xe9, 0x95, 0x16, 0x9d, 0x97, 0xd7, 0x97, 0x41, 0x84, 0xbf, 0xce, 0xcd, 0x8f, 0xab, 0xb8, 0xfc, 0x18, 0x44, 0xd6, + 0xa6, 0xce, 0xfc, 0x40, 0xa9, 0x3c, 0xf8, 0x3b, 0x81, 0x4c, 0xf7, 0x87, 0x02, 0x2c, 0xb3, 0x6d, 0xc5, 0xc7, 0x31, + 0xd6, 0x3a, 0x9c, 0x90, 0x99, 0x2a, 0xd1, 0x7b, 0x97, 0xac, 0x0b, 0xb0, 0xf6, 0x53, 0xd8, 0xce, 0x2a, 0xd7, 0x0c, + 0x2b, 0x53, 0x15, 0x19, 0x82, 0xb6, 0x66, 0x87, 0xa1, 0x75, 0xa2, 0x99, 0xa3, 0xb7, 0x80, 0x7e, 0x20, 0x87, 0x97, + 0x54, 0xae, 0x99, 0xe7, 0x63, 0xd3, 0x78, 0xfd, 0xe0, 0xf0, 0xd2, 0x13, 0x28, 0xd9, 0x3b, 0x39, 0x0a, 0x13, 0xc1, + 0xd3, 0xd8, 0x8c, 0x2f, 0xf2, 0xac, 0x80, 0x1d, 0x34, 0x19, 0x8f, 0xa9, 0xb7, 0xb4, 0x5a, 0x37, 0x47, 0x87, 0x6c, + 0x9b, 0x3d, 0xac, 0x1e, 0x72, 0x72, 0xc8, 0x5b, 0xa6, 0xb6, 0x6d, 0xeb, 0x38, 0x4f, 0x93, 0xaf, 0x4c, 0xf7, 0xcb, + 0xb5, 0x8d, 0x10, 0xaf, 0x9c, 0x1d, 0x9d, 0x97, 0x74, 0xeb, 0x9b, 0xd2, 0xd0, 0x6b, 0x09, 0xc0, 0x7c, 0xda, 0x80, + 0xbf, 0x60, 0x72, 0x3d, 0xaa, 0x78, 0x59, 0x81, 0x84, 0x05, 0x45, 0x78, 0x53, 0xec, 0x4d, 0xe1, 0x6e, 0x9c, 0x9e, + 0xc3, 0x0e, 0x5c, 0x4c, 0xd1, 0x1d, 0x27, 0x26, 0xb3, 0xd2, 0x68, 0x45, 0x23, 0xfd, 0xcb, 0xf5, 0x25, 0xd6, 0x7d, + 0xd1, 0xca, 0x3c, 0x9b, 0x53, 0x61, 0xd3, 0xbb, 0xca, 0xa5, 0x13, 0xf5, 0x5b, 0x26, 0x5c, 0xb9, 0x12, 0x04, 0x64, + 0x5a, 0xb0, 0x5e, 0x61, 0x76, 0x51, 0x81, 0x84, 0x0c, 0x0c, 0x5f, 0x83, 0xb5, 0x28, 0xb9, 0xb1, 0x82, 0xf5, 0xee, + 0xf9, 0x3a, 0x41, 0x48, 0xc1, 0x03, 0x37, 0x41, 0xbf, 0xb4, 0x6e, 0xde, 0x8e, 0x12, 0x65, 0x10, 0x9f, 0x5c, 0x3b, + 0xe5, 0x20, 0x81, 0x00, 0x1c, 0x58, 0x15, 0x92, 0x44, 0x81, 0xce, 0x83, 0xab, 0x19, 0x47, 0xb0, 0x79, 0xe5, 0xcc, + 0xc5, 0x0d, 0xe0, 0xbc, 0xf2, 0xe7, 0xb2, 0xc1, 0x96, 0xf5, 0x88, 0x2a, 0x73, 0xc6, 0x29, 0x06, 0x75, 0xb2, 0x04, + 0x7d, 0x65, 0x29, 0xed, 0x25, 0x68, 0x1a, 0xaf, 0xd8, 0x4a, 0xf9, 0x00, 0xd0, 0x73, 0xb6, 0x52, 0xc6, 0xfe, 0xf8, + 0xf5, 0x19, 0x5b, 0x69, 0x69, 0xf0, 0xf4, 0x6a, 0x76, 0x3e, 0x3b, 0x1b, 0xb0, 0xa3, 0x28, 0xd4, 0x06, 0x0c, 0x81, + 0x8b, 0x4c, 0x10, 0x0c, 0x42, 0x8d, 0xff, 0x32, 0x50, 0x01, 0xc2, 0x88, 0xc7, 0x63, 0x23, 0x8e, 0x58, 0x38, 0x1e, + 0x62, 0x30, 0xb0, 0xe6, 0x0b, 0x12, 0x10, 0x6a, 0x4a, 0x43, 0x5f, 0xcf, 0x70, 0x38, 0x39, 0x98, 0x40, 0x2a, 0x66, + 0x66, 0xaa, 0x30, 0x36, 0x26, 0x11, 0xc4, 0x7f, 0xed, 0xac, 0x17, 0xca, 0xed, 0xae, 0xd1, 0x40, 0xd0, 0x0c, 0xbe, + 0xa8, 0xe2, 0xc9, 0xc1, 0xb0, 0xab, 0x62, 0x1c, 0x85, 0x6b, 0xa3, 0x7c, 0x3b, 0x3b, 0x06, 0x30, 0xdf, 0xb3, 0xa1, + 0x2f, 0x97, 0x38, 0x3b, 0x7c, 0x4c, 0x1e, 0x3e, 0x26, 0xf4, 0x8c, 0x9d, 0x7d, 0xf5, 0x98, 0x9e, 0x29, 0x72, 0x72, + 0x30, 0x89, 0xae, 0x99, 0xc5, 0xc0, 0x39, 0x52, 0x4d, 0xa0, 0x97, 0xa3, 0xb5, 0x50, 0x0b, 0x4c, 0x3b, 0x34, 0x85, + 0xdf, 0x8e, 0x0f, 0x82, 0xc1, 0x75, 0xbb, 0xe9, 0xd7, 0xed, 0xb6, 0x7a, 0x5e, 0x5d, 0x07, 0x47, 0xd1, 0x6e, 0x31, + 0x93, 0xbf, 0x8f, 0x0f, 0xdc, 0x1c, 0x60, 0x7d, 0xf7, 0x8f, 0x89, 0x69, 0xd2, 0xce, 0xa8, 0xf8, 0x35, 0x3d, 0xc2, + 0x3e, 0x34, 0x8b, 0xec, 0xe8, 0xc3, 0xf0, 0xdf, 0xea, 0x44, 0x7d, 0xf6, 0xd5, 0x11, 0x90, 0x23, 0x90, 0x81, 0x62, + 0x89, 0x60, 0x86, 0x03, 0x4d, 0x01, 0x05, 0x99, 0x1e, 0x77, 0xaa, 0x87, 0x5f, 0x8d, 0x9a, 0x9a, 0x91, 0x6b, 0x98, + 0x1a, 0x6c, 0x0b, 0x7e, 0xa0, 0xba, 0xa1, 0xbf, 0xd1, 0x68, 0x4f, 0xda, 0xc9, 0xcc, 0xbc, 0xa4, 0x36, 0xce, 0xdd, + 0x35, 0x04, 0x74, 0x76, 0x70, 0x8b, 0x92, 0x7d, 0x7d, 0x7c, 0x79, 0x80, 0xab, 0x08, 0x50, 0xc3, 0x58, 0xf0, 0xf5, + 0xe0, 0x52, 0x6f, 0xee, 0x83, 0x80, 0x0c, 0xbe, 0x0e, 0x4e, 0xbe, 0x1e, 0xc8, 0x41, 0x70, 0x7c, 0x78, 0x79, 0x12, + 0x38, 0xe3, 0x7e, 0x08, 0x79, 0xa9, 0x2a, 0x8a, 0x99, 0x30, 0x55, 0x24, 0xb6, 0xf6, 0xdc, 0xd6, 0xab, 0x8c, 0xcf, + 0x68, 0x3a, 0xb5, 0x48, 0xe8, 0x61, 0xca, 0x62, 0xf3, 0x3b, 0x98, 0xf0, 0xab, 0x20, 0x72, 0x41, 0x61, 0x67, 0x79, + 0x14, 0xd3, 0x25, 0xbb, 0x15, 0x61, 0x4a, 0x93, 0xc3, 0x9c, 0x90, 0x28, 0x5c, 0x2a, 0x30, 0x41, 0xf5, 0x3a, 0x81, + 0xb8, 0xb6, 0xee, 0xf3, 0x5b, 0x11, 0x2e, 0x69, 0x7e, 0x98, 0x90, 0x56, 0x11, 0x2e, 0x42, 0xcd, 0xa6, 0xa6, 0x17, + 0x2c, 0x5c, 0xd1, 0x4b, 0x34, 0xd5, 0x5c, 0x87, 0x97, 0xc0, 0xe5, 0xad, 0xe7, 0xab, 0x05, 0xbb, 0x6c, 0x48, 0xdf, + 0x0c, 0x5f, 0x7c, 0x61, 0x7d, 0xf2, 0x80, 0x87, 0x74, 0x7e, 0x78, 0x29, 0xd8, 0x00, 0x5c, 0x67, 0xfc, 0xe6, 0x3b, + 0x79, 0xab, 0xe7, 0xa5, 0x3d, 0xc5, 0x38, 0x33, 0xed, 0xc4, 0xa4, 0x9d, 0x90, 0xfb, 0xf7, 0x6d, 0xdf, 0xbd, 0x78, + 0xad, 0x5c, 0x56, 0x2d, 0x43, 0x12, 0xaf, 0x95, 0xeb, 0x34, 0x4a, 0x4e, 0xad, 0xc0, 0x93, 0x5d, 0xf0, 0x2a, 0x59, + 0xfa, 0x07, 0x95, 0xb5, 0x1a, 0xb0, 0xc7, 0x88, 0x65, 0xa1, 0x70, 0xec, 0x5f, 0x65, 0x2c, 0x5e, 0x37, 0x90, 0x81, + 0x91, 0x7b, 0x7b, 0x95, 0x31, 0x2f, 0x06, 0x6d, 0xbe, 0xf6, 0x42, 0xf7, 0x79, 0xe9, 0xcb, 0x16, 0xef, 0xe5, 0x94, + 0x1a, 0x46, 0x22, 0x7a, 0x30, 0x56, 0x66, 0x94, 0x2a, 0x51, 0x6b, 0xd0, 0x88, 0x60, 0x63, 0x17, 0x0c, 0x14, 0x9c, + 0x50, 0xb9, 0xa7, 0xce, 0xf6, 0xed, 0x94, 0x4a, 0x0f, 0x68, 0x97, 0x1a, 0x55, 0xb9, 0x5b, 0x66, 0x92, 0x55, 0x83, + 0x60, 0xf4, 0x67, 0x29, 0xc5, 0x0c, 0xef, 0x8c, 0x2c, 0x98, 0x82, 0x95, 0xa0, 0xaa, 0x65, 0x58, 0x0e, 0x39, 0x6a, + 0xf1, 0x8c, 0x4f, 0xaa, 0xd4, 0x3f, 0x3a, 0x82, 0x06, 0xa7, 0xeb, 0x56, 0xd0, 0xe0, 0xc7, 0xe3, 0xc7, 0x7a, 0xa0, + 0xd7, 0x6b, 0xed, 0x78, 0xe8, 0xf3, 0xdb, 0x88, 0x37, 0xae, 0x7b, 0x4f, 0xb5, 0x56, 0xa1, 0x0c, 0xb4, 0x58, 0x51, + 0xb9, 0x52, 0x4b, 0xba, 0xdf, 0x45, 0x00, 0x2c, 0x62, 0x63, 0x36, 0xde, 0xb5, 0xcd, 0x0a, 0x41, 0xa3, 0xcb, 0x4e, + 0x36, 0xf1, 0x80, 0x25, 0xba, 0xb5, 0x83, 0x09, 0x8d, 0x4f, 0x58, 0xd9, 0xef, 0xe7, 0x27, 0x40, 0x4f, 0xb5, 0x11, + 0x53, 0x01, 0x47, 0xfe, 0x97, 0x56, 0x64, 0x8a, 0x02, 0x9b, 0x35, 0x75, 0xb7, 0xc6, 0x32, 0x12, 0x7d, 0x99, 0xd2, + 0xe5, 0x09, 0xcf, 0x80, 0xe9, 0x62, 0xdd, 0x72, 0x5c, 0xd9, 0x55, 0x1c, 0x79, 0x2a, 0x2c, 0x2b, 0xce, 0xab, 0x70, + 0xbc, 0xf5, 0xf8, 0x06, 0x87, 0x86, 0x4d, 0x5b, 0xf9, 0x43, 0x08, 0x0b, 0xe1, 0x55, 0x06, 0xb7, 0x11, 0x6d, 0x27, + 0x81, 0xca, 0x1b, 0x73, 0x9d, 0x50, 0x36, 0xb7, 0x17, 0x6b, 0xcf, 0x20, 0x9d, 0x98, 0x03, 0xa5, 0x1a, 0x41, 0x6b, + 0x34, 0x0b, 0xaa, 0x46, 0x3c, 0x72, 0xe6, 0x5f, 0xce, 0x20, 0x56, 0xcb, 0x97, 0x34, 0x95, 0xa2, 0x01, 0x18, 0x17, + 0xc0, 0xe5, 0xe9, 0x97, 0x77, 0x3f, 0xbd, 0xe7, 0x71, 0x91, 0x2c, 0xdf, 0xc6, 0x45, 0x7c, 0x55, 0x86, 0x1b, 0x35, + 0x46, 0x71, 0x4d, 0xa6, 0x62, 0xc0, 0xa4, 0x59, 0x49, 0xcd, 0x5d, 0xa9, 0x09, 0x31, 0xd6, 0x99, 0xac, 0xcb, 0x4a, + 0x5e, 0x35, 0x2a, 0x5d, 0x17, 0x19, 0x7e, 0xdc, 0xf2, 0x39, 0x3d, 0x04, 0x60, 0x53, 0xe3, 0x42, 0x1a, 0x49, 0x5d, + 0x88, 0x31, 0x17, 0xf1, 0xba, 0x3e, 0x1e, 0x37, 0xba, 0x5e, 0xb2, 0x27, 0xe3, 0x47, 0xd3, 0x57, 0x59, 0x98, 0x0d, + 0x04, 0x19, 0x55, 0x4b, 0x2e, 0x5a, 0xa6, 0x9c, 0xca, 0x24, 0x00, 0x7d, 0x3c, 0x7b, 0x8c, 0x1d, 0x8d, 0xc7, 0x64, + 0xd3, 0x16, 0x0f, 0xf0, 0x30, 0x5d, 0x87, 0x05, 0x99, 0xe9, 0x3a, 0xa2, 0x40, 0xf0, 0xdb, 0x2a, 0x00, 0x64, 0x4b, + 0x5b, 0x95, 0xe1, 0xd2, 0xd8, 0x93, 0xf1, 0x84, 0x4a, 0xec, 0x76, 0x48, 0x6a, 0xaf, 0x42, 0x37, 0xf3, 0xd2, 0xf7, + 0x28, 0x92, 0xc6, 0x65, 0x69, 0xa7, 0x52, 0xa9, 0xf6, 0xcc, 0xcc, 0x75, 0x0d, 0x62, 0x52, 0x84, 0xba, 0xee, 0xd2, + 0xab, 0x7b, 0xb7, 0xb9, 0xd6, 0x6c, 0x07, 0xbc, 0xd7, 0xa0, 0x19, 0x4a, 0xde, 0x62, 0xde, 0xba, 0x22, 0x6a, 0x7a, + 0xb9, 0x06, 0xb3, 0x62, 0x94, 0x2d, 0x45, 0xeb, 0x35, 0x05, 0xa5, 0x60, 0xb4, 0x5a, 0x7b, 0x0b, 0xf7, 0xa9, 0x6c, + 0x5c, 0x58, 0x32, 0xbd, 0x5a, 0x94, 0x94, 0x50, 0xdd, 0x54, 0x8c, 0x94, 0x30, 0x52, 0x1a, 0x9e, 0xca, 0xf7, 0x02, + 0x8f, 0xf3, 0x3c, 0x88, 0x5a, 0x5e, 0x60, 0xa7, 0x15, 0x39, 0x05, 0x47, 0x2f, 0x93, 0xd3, 0x50, 0xe0, 0x1f, 0x33, + 0x05, 0xea, 0x3a, 0x54, 0xf7, 0x1b, 0xdc, 0xfc, 0xbf, 0x15, 0x2c, 0xf0, 0xf8, 0xd6, 0x2b, 0xdc, 0x46, 0xbf, 0x15, + 0x3e, 0x2d, 0x7d, 0x23, 0x7d, 0x57, 0x17, 0x4f, 0xda, 0x9b, 0x8d, 0x92, 0x65, 0x96, 0xa7, 0xaf, 0x65, 0xca, 0x41, + 0x64, 0x86, 0xd6, 0xa0, 0xec, 0x44, 0x34, 0x6e, 0x78, 0x60, 0xc4, 0xd8, 0xb8, 0xf1, 0xfd, 0x98, 0x81, 0x6c, 0x18, + 0xac, 0xbe, 0x59, 0x2a, 0x93, 0x35, 0x20, 0x6c, 0x68, 0xf9, 0x89, 0xc6, 0xdb, 0x08, 0xf5, 0xf5, 0x0b, 0xdc, 0xe6, + 0x4a, 0xdf, 0xe7, 0xfc, 0xc7, 0x8c, 0xfe, 0x88, 0xc0, 0x2f, 0xf1, 0x0a, 0xe4, 0x1e, 0x4f, 0xa1, 0x6e, 0x84, 0xed, + 0xe5, 0x18, 0x2c, 0x09, 0xd1, 0x51, 0x44, 0xc5, 0x02, 0x05, 0x4d, 0x61, 0x10, 0x45, 0xd4, 0x05, 0x73, 0x78, 0x9e, + 0xcb, 0xe4, 0xe3, 0xd4, 0xf8, 0xcc, 0x0f, 0x63, 0x8c, 0x21, 0x1d, 0x0c, 0xc2, 0x6a, 0x16, 0x0c, 0xc7, 0xa3, 0xc9, + 0xd1, 0x13, 0x38, 0xb7, 0x83, 0x71, 0x40, 0x06, 0x41, 0x5d, 0xae, 0x62, 0x41, 0xcb, 0xeb, 0x4b, 0x5b, 0x06, 0x7e, + 0x5c, 0x07, 0x83, 0xdf, 0x0a, 0x4f, 0xf1, 0x0e, 0x9a, 0x93, 0x3b, 0x19, 0x06, 0x01, 0xbd, 0x5c, 0x13, 0x90, 0x94, + 0xf5, 0x34, 0x3f, 0xa9, 0x0f, 0x37, 0xa6, 0xb4, 0x7f, 0xe6, 0xf0, 0x82, 0xc3, 0x0e, 0x09, 0x14, 0x48, 0xe3, 0x69, + 0x36, 0x7a, 0xa9, 0x14, 0xb9, 0x6f, 0x0b, 0x0e, 0x77, 0xe6, 0x9e, 0x33, 0x3d, 0x72, 0x0a, 0x89, 0x66, 0x16, 0x70, + 0x23, 0x7f, 0x29, 0xae, 0xe3, 0x3c, 0x4b, 0x0f, 0x9a, 0x6f, 0x0e, 0xca, 0x3b, 0x51, 0xc5, 0xb7, 0xa3, 0xc0, 0x58, + 0x13, 0x72, 0x5f, 0xf5, 0x04, 0xe8, 0x09, 0xb0, 0x05, 0xc0, 0x80, 0x78, 0xc7, 0xcc, 0x64, 0xc6, 0x23, 0xf0, 0x08, + 0x6c, 0xfa, 0x40, 0x16, 0x77, 0xce, 0x25, 0xc9, 0xdf, 0x4c, 0xa5, 0xbd, 0xea, 0x95, 0x3b, 0x05, 0x59, 0xaf, 0xb6, + 0x72, 0xd7, 0xad, 0xcf, 0xbe, 0xe9, 0xf0, 0x0a, 0x3c, 0x93, 0xe0, 0x16, 0xd9, 0xef, 0x37, 0x05, 0x95, 0xc2, 0xa8, + 0x88, 0x77, 0x92, 0x6b, 0xf4, 0x6f, 0xf7, 0xc6, 0x46, 0x91, 0xdc, 0xf2, 0xfe, 0x01, 0xd4, 0x99, 0xbc, 0x2b, 0x6e, + 0xe7, 0x10, 0xb5, 0x75, 0x37, 0x1e, 0x78, 0x6f, 0xd0, 0x2e, 0x6b, 0x8e, 0x60, 0xcb, 0x8b, 0x83, 0x0c, 0xc6, 0x02, + 0x67, 0x65, 0xa4, 0xd4, 0xb8, 0x56, 0x46, 0x03, 0x6a, 0x93, 0x3d, 0x64, 0xa9, 0x27, 0x41, 0x91, 0xe3, 0x59, 0x0c, + 0x99, 0xc6, 0xdb, 0x40, 0xec, 0xb7, 0x32, 0x04, 0x69, 0xda, 0x76, 0xdb, 0x1c, 0x81, 0xb2, 0x7b, 0x60, 0x4a, 0x52, + 0xd7, 0xc6, 0xd4, 0x40, 0x43, 0x0f, 0xa2, 0x46, 0x2a, 0xe2, 0xec, 0xe4, 0x29, 0xe8, 0x10, 0xc1, 0xf7, 0x3b, 0xcd, + 0xca, 0x8e, 0x17, 0x13, 0x82, 0x27, 0xef, 0xf3, 0xdb, 0xac, 0xac, 0xca, 0xe8, 0x45, 0x8a, 0x86, 0x50, 0x89, 0x14, + 0xd1, 0x6b, 0x88, 0x2f, 0x58, 0xe2, 0xef, 0x32, 0x7a, 0x97, 0xd2, 0x38, 0x4d, 0x31, 0xfd, 0x59, 0x01, 0x3f, 0x9f, + 0x02, 0xca, 0x25, 0xee, 0x84, 0xe8, 0x4c, 0x82, 0xbd, 0x1a, 0x44, 0xf7, 0xaa, 0x38, 0x60, 0x8a, 0x46, 0xb7, 0x82, + 0x22, 0x66, 0x1d, 0x66, 0xff, 0xa5, 0x40, 0xa1, 0x90, 0x2a, 0xe6, 0x57, 0x61, 0x1f, 0xa2, 0x6a, 0x0d, 0xe5, 0x9c, + 0xbe, 0x7d, 0x69, 0x86, 0x34, 0xba, 0x95, 0x54, 0x6f, 0x6d, 0x3c, 0xb6, 0x10, 0xa5, 0x27, 0xba, 0x5a, 0xd3, 0xb3, + 0x78, 0x95, 0x45, 0x1b, 0xc0, 0x9f, 0x78, 0xfb, 0xf2, 0xa9, 0xb2, 0x30, 0x79, 0x99, 0x81, 0xe2, 0xe0, 0xf4, 0xed, + 0xcb, 0x57, 0x32, 0x5d, 0xe7, 0x3c, 0xba, 0x93, 0x48, 0x5a, 0x4f, 0xdf, 0xbe, 0xfc, 0x19, 0xcd, 0xbd, 0xde, 0x15, + 0xf0, 0xfe, 0x05, 0xf0, 0x96, 0x51, 0xb2, 0x86, 0x3e, 0xa9, 0xdf, 0xf9, 0x1a, 0x3b, 0xe5, 0xd5, 0x5a, 0x46, 0xff, + 0x4c, 0x6b, 0x4f, 0x5a, 0xf5, 0x57, 0xe1, 0x53, 0x3b, 0x4f, 0xc0, 0x73, 0x9b, 0x67, 0xe2, 0x63, 0x64, 0x45, 0x3b, + 0x41, 0xf4, 0xf5, 0xc1, 0xed, 0x55, 0x2e, 0xca, 0x08, 0x5f, 0x30, 0xb4, 0x0b, 0x8a, 0x0e, 0x0f, 0x6f, 0x6e, 0x6e, + 0x46, 0x37, 0x8f, 0x46, 0xb2, 0xb8, 0x3c, 0x9c, 0x7c, 0xfb, 0xed, 0xb7, 0x87, 0xf8, 0x36, 0xf8, 0xba, 0xed, 0xf6, + 0x5e, 0x11, 0x3e, 0x60, 0x01, 0x22, 0x76, 0x7f, 0x0d, 0x57, 0x14, 0xd0, 0xc2, 0x0d, 0xbe, 0x0e, 0xbe, 0xd6, 0x87, + 0xce, 0xd7, 0xc7, 0xe5, 0xf5, 0xa5, 0x2a, 0xbf, 0xab, 0xe4, 0xa3, 0xf1, 0x78, 0x7c, 0x08, 0x12, 0xa8, 0xaf, 0x07, + 0x7c, 0x10, 0x9c, 0x04, 0x83, 0x0c, 0x2e, 0x34, 0xe5, 0xf5, 0xe5, 0x49, 0xe0, 0x19, 0xd8, 0x36, 0x58, 0x44, 0x07, + 0xe2, 0x12, 0x1c, 0x5e, 0xd2, 0xe0, 0xeb, 0x80, 0xb8, 0x94, 0xaf, 0x20, 0xe5, 0xab, 0xa3, 0x27, 0x7e, 0xda, 0xff, + 0x52, 0x69, 0x8f, 0xfc, 0xb4, 0x63, 0x4c, 0x7b, 0xf4, 0xd4, 0x4f, 0x3b, 0x51, 0x69, 0xcf, 0xfd, 0xb4, 0xff, 0x5d, + 0x0e, 0x20, 0xf5, 0xc0, 0xb7, 0xfe, 0x3b, 0xf3, 0x5a, 0x83, 0xa7, 0x50, 0x94, 0x5d, 0xc5, 0x97, 0x1c, 0x1a, 0x3d, + 0xb8, 0xbd, 0xca, 0x69, 0x30, 0xc0, 0xf6, 0x7a, 0x46, 0x1e, 0xde, 0x07, 0x5f, 0xaf, 0x8b, 0x3c, 0x0c, 0xbe, 0x1e, + 0x60, 0x21, 0x83, 0xaf, 0x03, 0xf2, 0xb5, 0x3e, 0xd2, 0xae, 0x05, 0xdb, 0x04, 0x2e, 0x34, 0xeb, 0xd0, 0x06, 0x4c, + 0xf3, 0xa5, 0x71, 0x35, 0xfd, 0xbd, 0xe8, 0xce, 0x86, 0xb7, 0x44, 0xe5, 0xa6, 0x1b, 0xd4, 0xf4, 0x01, 0x78, 0x27, + 0x40, 0xa3, 0xa2, 0xe0, 0x3a, 0x2e, 0xc2, 0xe1, 0xb0, 0xbc, 0xbe, 0x24, 0x60, 0x97, 0xb9, 0xe2, 0x71, 0x15, 0x05, + 0x42, 0x0e, 0xd5, 0xcf, 0x40, 0x45, 0x02, 0x0b, 0x10, 0xca, 0x08, 0xfe, 0x0b, 0x6a, 0xfa, 0x56, 0xb2, 0x4d, 0x30, + 0xbc, 0xe1, 0xe7, 0x1f, 0xb3, 0x6a, 0xa8, 0x44, 0x8b, 0xd7, 0x82, 0xc2, 0x0f, 0xf8, 0xeb, 0xaa, 0x8e, 0x7e, 0x07, + 0x37, 0xee, 0xa6, 0x86, 0xfd, 0xad, 0xf4, 0x1c, 0xda, 0xe4, 0x3c, 0x5b, 0x4c, 0x5b, 0x07, 0xfa, 0x03, 0x49, 0xaa, + 0x79, 0x36, 0x08, 0x86, 0xc1, 0x80, 0x2f, 0xd8, 0x03, 0x39, 0xe7, 0x9e, 0xf9, 0xd4, 0xa9, 0xf4, 0xa7, 0x79, 0x96, + 0x0d, 0xc0, 0x37, 0x05, 0xf9, 0x91, 0xc3, 0xff, 0x9e, 0x0f, 0x51, 0x78, 0x38, 0x78, 0x70, 0x48, 0x66, 0xc1, 0xea, + 0x16, 0x3d, 0x3a, 0xa3, 0x20, 0x13, 0x4b, 0x5e, 0x64, 0x95, 0xb7, 0x54, 0x6e, 0xd7, 0x6d, 0x2f, 0x8f, 0xbd, 0x67, + 0xf3, 0x2a, 0x16, 0x81, 0x3a, 0xe7, 0x40, 0xf1, 0x86, 0xb2, 0xa7, 0xb2, 0x29, 0x21, 0xd5, 0x86, 0xbc, 0x61, 0x39, + 0x60, 0xc1, 0x71, 0x6f, 0x38, 0x3c, 0x08, 0x06, 0x4e, 0x9d, 0x3b, 0x08, 0x0e, 0x86, 0xc3, 0x93, 0xc0, 0xdd, 0x87, + 0xb2, 0x91, 0xbb, 0x33, 0xd2, 0x82, 0xfd, 0x55, 0x84, 0x25, 0x05, 0xf1, 0x98, 0xd4, 0xe2, 0x2f, 0x0d, 0x2e, 0x33, + 0x00, 0xe8, 0x23, 0x25, 0x01, 0x33, 0xb0, 0x32, 0x03, 0x08, 0x55, 0x4e, 0x63, 0x76, 0x07, 0xcc, 0x23, 0x70, 0xcc, + 0x0a, 0x26, 0x0b, 0x10, 0x4b, 0x02, 0x9c, 0xbb, 0x20, 0x8a, 0x75, 0x21, 0xa7, 0x10, 0x04, 0x00, 0x7f, 0x12, 0x53, + 0x0a, 0x26, 0xe9, 0xd8, 0x8d, 0x20, 0x88, 0xe3, 0xb3, 0x6b, 0xd1, 0x9a, 0x9c, 0x25, 0x3a, 0x98, 0x91, 0x04, 0xd8, + 0x10, 0x03, 0xc3, 0x07, 0xf7, 0x73, 0x50, 0x7a, 0x58, 0xbd, 0x13, 0x72, 0xc1, 0x77, 0xdc, 0xb1, 0x50, 0xd7, 0x70, + 0xf5, 0x84, 0x83, 0xe0, 0x8e, 0x6b, 0x16, 0x60, 0x54, 0x15, 0xeb, 0xb2, 0xe2, 0xe9, 0x87, 0xbb, 0x15, 0xc4, 0x02, + 0xc4, 0x01, 0x7d, 0x2b, 0xf3, 0x2c, 0xb9, 0x0b, 0x9d, 0x3d, 0xd7, 0x46, 0xa5, 0xff, 0xf0, 0xe1, 0xd5, 0x4f, 0x11, + 0x88, 0x1c, 0x6b, 0x43, 0xe9, 0xef, 0x38, 0x9e, 0x4d, 0x7e, 0xc4, 0x2b, 0x7f, 0x63, 0xdf, 0x71, 0x7b, 0x7a, 0xf4, + 0xfb, 0x50, 0x37, 0xbd, 0xe3, 0xb3, 0x3b, 0x3e, 0x72, 0xc5, 0xa1, 0xba, 0xc2, 0x7d, 0xfd, 0x71, 0xed, 0x1b, 0x21, + 0xdd, 0x3f, 0xcf, 0x94, 0x37, 0xe6, 0x47, 0x3b, 0x18, 0x06, 0xc1, 0x54, 0x0b, 0x25, 0x21, 0x0a, 0x09, 0x53, 0x02, + 0x86, 0xe8, 0x40, 0x2f, 0xab, 0x29, 0x72, 0x6e, 0x6a, 0x64, 0xe1, 0xfd, 0x80, 0x69, 0xa1, 0x43, 0x23, 0x87, 0xf2, + 0x83, 0xc3, 0x09, 0x63, 0x16, 0x7e, 0xab, 0x84, 0xe9, 0x57, 0x8b, 0xca, 0x39, 0x88, 0x1e, 0x80, 0x31, 0xae, 0xe0, + 0x05, 0x74, 0x85, 0xdd, 0xac, 0x55, 0x94, 0x10, 0x04, 0xd3, 0x43, 0x0e, 0xd0, 0xc3, 0x2e, 0x68, 0x59, 0x59, 0xaa, + 0x5b, 0x95, 0xb3, 0x54, 0x51, 0x97, 0xa1, 0xac, 0x8c, 0x15, 0x06, 0x7e, 0xc9, 0x7e, 0x29, 0xd0, 0xb3, 0x7c, 0x2a, + 0xba, 0xe0, 0x85, 0x50, 0x82, 0xe5, 0xba, 0xde, 0x89, 0x40, 0xd4, 0xf9, 0xa1, 0x77, 0xd5, 0xd7, 0xb8, 0x7e, 0x3c, + 0x7d, 0x25, 0x53, 0xae, 0x4d, 0x28, 0x34, 0x9f, 0x2f, 0x7d, 0xc5, 0x44, 0xc1, 0x3e, 0x42, 0xbf, 0xda, 0x36, 0xfa, + 0xec, 0x76, 0xad, 0x37, 0x83, 0x12, 0x1d, 0xf3, 0x1a, 0x05, 0xd7, 0x4a, 0xa1, 0x60, 0xb4, 0xb7, 0xf1, 0x67, 0x38, + 0x72, 0xab, 0xdb, 0x43, 0xef, 0xb7, 0x2a, 0xbe, 0x7c, 0x8d, 0xbe, 0x9d, 0xf6, 0xe7, 0xa8, 0x92, 0xbf, 0xac, 0x56, + 0xe0, 0x43, 0x05, 0x91, 0x56, 0x2c, 0x4e, 0x2f, 0xd4, 0xf3, 0xfe, 0xed, 0xe9, 0x6b, 0xf0, 0xa3, 0xc4, 0xdf, 0xbf, + 0x7e, 0x1f, 0xd4, 0x64, 0x1a, 0xcf, 0x0a, 0xf3, 0xa1, 0xcd, 0x01, 0xa1, 0x5a, 0x5c, 0x9a, 0x7d, 0x3f, 0x8b, 0x9b, + 0xec, 0xbb, 0x66, 0xeb, 0x69, 0xd1, 0x44, 0x92, 0x32, 0xdc, 0x3e, 0x18, 0x10, 0xe8, 0x03, 0x44, 0x71, 0xf6, 0x05, + 0x8d, 0x21, 0xcd, 0x67, 0xf6, 0xfd, 0x08, 0x81, 0x2f, 0x77, 0x42, 0xaa, 0x71, 0x85, 0x45, 0xa3, 0x87, 0x7c, 0xc6, + 0x23, 0x65, 0x58, 0xf4, 0x0e, 0x13, 0x88, 0x33, 0x9c, 0x56, 0xef, 0x11, 0x03, 0x1a, 0xef, 0x06, 0x5a, 0xf6, 0x10, + 0x65, 0xd4, 0x65, 0x6f, 0x58, 0x7c, 0xbf, 0x5f, 0x87, 0x99, 0xb5, 0xbc, 0x1c, 0xc2, 0xdf, 0x40, 0x1b, 0x80, 0x53, + 0x8e, 0x2c, 0x5f, 0x65, 0x36, 0xba, 0x5a, 0x62, 0x7a, 0x13, 0x41, 0x6c, 0x22, 0x9d, 0x0e, 0x6b, 0x57, 0xa7, 0xea, + 0x5d, 0xed, 0x7c, 0x26, 0x7a, 0x15, 0x68, 0xe5, 0xda, 0xf6, 0x78, 0x08, 0xff, 0xa9, 0xa5, 0x15, 0x36, 0xc2, 0x9e, + 0x8b, 0x2f, 0x3c, 0xc7, 0xe6, 0x04, 0x34, 0xb8, 0x92, 0x29, 0x00, 0x67, 0x69, 0x35, 0x1a, 0x35, 0xc2, 0x3e, 0x2b, + 0xe7, 0x73, 0xd8, 0x5a, 0x88, 0xa7, 0x05, 0xe0, 0xc0, 0x4d, 0x4c, 0x4e, 0xde, 0x8d, 0xc9, 0x39, 0xfd, 0xa8, 0xe0, + 0xbe, 0x83, 0xb3, 0x72, 0x19, 0xa7, 0xf2, 0x06, 0xb0, 0x29, 0x03, 0x3f, 0x15, 0x4b, 0xf5, 0x12, 0x92, 0x25, 0x4f, + 0x3e, 0xa2, 0xd5, 0x46, 0x1a, 0x00, 0x57, 0x39, 0x35, 0x96, 0x7b, 0x0a, 0x34, 0xd5, 0x95, 0xa2, 0x12, 0xe2, 0xaa, + 0x8a, 0x93, 0xe5, 0x7b, 0x4c, 0x0d, 0x37, 0xd0, 0x8b, 0x28, 0x90, 0x2b, 0x2e, 0x80, 0xa4, 0xe7, 0xec, 0x5f, 0x99, + 0xc6, 0x5e, 0x7f, 0x23, 0x51, 0xc0, 0xa4, 0x51, 0x94, 0xb1, 0x52, 0xf6, 0x52, 0x9a, 0xe8, 0x77, 0x41, 0x50, 0xbb, + 0x97, 0x7f, 0x41, 0xdd, 0x4f, 0xa1, 0x15, 0x61, 0x03, 0xbc, 0x50, 0x83, 0x1f, 0xa6, 0x76, 0xc9, 0x79, 0x40, 0x86, + 0xce, 0xfb, 0xac, 0xb6, 0x5b, 0xfd, 0xe9, 0x12, 0xb0, 0x5e, 0x53, 0xe3, 0x53, 0x18, 0x26, 0xc4, 0xc4, 0x4a, 0xb6, + 0xca, 0x4a, 0xbb, 0xa1, 0x4c, 0x3b, 0xe9, 0x92, 0x79, 0x2d, 0x9c, 0xe6, 0x3d, 0xc6, 0x96, 0x23, 0x95, 0xbb, 0xdf, + 0x0f, 0xcd, 0x4f, 0x96, 0xd3, 0x37, 0x3a, 0x84, 0xb5, 0x37, 0x1e, 0x34, 0x27, 0x5a, 0x5d, 0xd5, 0xd1, 0x0f, 0xe8, + 0x00, 0xcc, 0xb4, 0x45, 0xa8, 0x74, 0xc1, 0xb7, 0x7d, 0x25, 0x2a, 0x2e, 0x49, 0x58, 0x2a, 0x09, 0xec, 0xec, 0xa6, + 0x64, 0x67, 0x13, 0x10, 0xcf, 0x70, 0xd7, 0xd3, 0x62, 0x27, 0xa4, 0x09, 0x6f, 0x71, 0x90, 0x80, 0xa8, 0x43, 0x55, + 0x97, 0x90, 0x8d, 0x31, 0x74, 0xf1, 0x2f, 0x4a, 0x61, 0xc2, 0x5a, 0x26, 0x55, 0x89, 0x09, 0x0a, 0x55, 0xee, 0xb6, + 0x08, 0x2c, 0x51, 0xb0, 0x03, 0xd8, 0x7b, 0x37, 0xea, 0x66, 0xd4, 0x54, 0x75, 0xea, 0x25, 0xf8, 0x38, 0xcd, 0xba, + 0x0a, 0x32, 0x0b, 0xbb, 0x2a, 0xd6, 0x3c, 0xd0, 0xb1, 0xba, 0x94, 0x31, 0x71, 0x97, 0x16, 0x19, 0xe2, 0x23, 0x63, + 0x6c, 0x61, 0x0d, 0x47, 0xda, 0x1e, 0x37, 0x3d, 0x41, 0xe8, 0x27, 0x6c, 0x28, 0x81, 0x9b, 0xce, 0xf6, 0xd4, 0x34, + 0xf3, 0x01, 0x11, 0x87, 0x01, 0x05, 0x92, 0x8d, 0x43, 0x9a, 0x23, 0x7d, 0x41, 0xd2, 0x84, 0x81, 0xb2, 0x15, 0xcf, + 0x09, 0xb2, 0xa2, 0xd0, 0xb3, 0x75, 0x55, 0x43, 0xfc, 0x5c, 0x86, 0x39, 0x5a, 0x72, 0x2a, 0x3c, 0x4d, 0x90, 0x89, + 0xed, 0x69, 0x9b, 0x99, 0x0c, 0x47, 0xc9, 0x02, 0xf3, 0x2b, 0x88, 0x12, 0x77, 0xa6, 0x59, 0x95, 0x83, 0x71, 0x01, + 0x0b, 0xb4, 0xf2, 0x3d, 0xa8, 0x1b, 0x6b, 0x68, 0xa3, 0x61, 0x99, 0xdd, 0xfe, 0x04, 0xfb, 0xb5, 0x76, 0x5a, 0x97, + 0x29, 0x96, 0x97, 0x29, 0x44, 0x7b, 0x21, 0xf3, 0x1b, 0x45, 0xa2, 0x3b, 0x45, 0x18, 0x12, 0xd6, 0x51, 0xf6, 0xa4, + 0x4d, 0x0d, 0xa0, 0xa7, 0x5e, 0x00, 0xf8, 0xce, 0xb5, 0x0c, 0xbb, 0x48, 0xf7, 0x57, 0x05, 0xe3, 0xd2, 0x0d, 0x82, + 0x14, 0xbd, 0x49, 0xc1, 0x9c, 0xd7, 0xa3, 0xa4, 0xde, 0x9c, 0xb6, 0xcc, 0xa8, 0x3a, 0x2a, 0x42, 0xca, 0x09, 0xfe, + 0x93, 0x57, 0x52, 0x13, 0x9b, 0x30, 0xc1, 0x03, 0x1f, 0xe6, 0x19, 0x36, 0xf0, 0x76, 0xfb, 0x36, 0x0d, 0x93, 0x36, + 0xdb, 0x90, 0x82, 0xb4, 0xc2, 0xc4, 0x09, 0x81, 0xca, 0x5e, 0xe2, 0x7e, 0xc1, 0x76, 0xd2, 0x14, 0x3c, 0x08, 0x1b, + 0x0d, 0x4c, 0xdc, 0xea, 0xca, 0xd6, 0x61, 0x42, 0xc3, 0x25, 0xd5, 0xce, 0x4e, 0x2a, 0xf9, 0xa2, 0xbd, 0x2e, 0x2f, + 0x6c, 0x1f, 0x74, 0x2c, 0xb5, 0xae, 0xe1, 0x81, 0xe6, 0x35, 0xbb, 0xb8, 0x62, 0x9a, 0x26, 0x1a, 0xeb, 0x21, 0x65, + 0xc9, 0xb1, 0xae, 0xa7, 0x2b, 0x5c, 0x2d, 0x33, 0x0d, 0x74, 0x2f, 0xf1, 0x42, 0x0f, 0xf8, 0xe0, 0xe1, 0x8a, 0x44, + 0x17, 0xd8, 0x6c, 0xb6, 0xaa, 0xc9, 0x34, 0xdf, 0x97, 0x2d, 0x37, 0x01, 0xf2, 0x2c, 0xf5, 0xcd, 0x7d, 0x72, 0xac, + 0x69, 0x9b, 0x9f, 0x04, 0xb8, 0xe6, 0x5e, 0x01, 0x49, 0xc7, 0x12, 0x74, 0xf1, 0x3e, 0xfd, 0x41, 0xa4, 0x66, 0x2a, + 0xe8, 0xde, 0xf9, 0x22, 0x75, 0xf3, 0x0b, 0xb0, 0x8d, 0xda, 0x18, 0xd3, 0x2c, 0xb1, 0x0e, 0x13, 0x65, 0x61, 0x8d, + 0x2c, 0xe4, 0x12, 0x7c, 0x30, 0x77, 0x9b, 0x3a, 0x7d, 0xde, 0x41, 0x84, 0xfd, 0x2e, 0x7a, 0x3c, 0xc2, 0x58, 0xb1, + 0x06, 0x89, 0x61, 0x15, 0xd6, 0xb4, 0xb9, 0x1c, 0xa2, 0x9c, 0x9a, 0x25, 0x13, 0x2d, 0xa9, 0x4f, 0x29, 0xa2, 0x14, + 0xcc, 0x8d, 0xa7, 0x65, 0xc3, 0x94, 0x10, 0x21, 0x2b, 0xa4, 0x03, 0xaa, 0xb5, 0xd0, 0x52, 0x4d, 0x10, 0xf0, 0xd0, + 0xcb, 0x42, 0x63, 0x0a, 0xa2, 0x8f, 0xc8, 0x70, 0x23, 0x8e, 0x8c, 0xee, 0x8e, 0x51, 0x4c, 0x20, 0x74, 0xb7, 0x97, + 0x17, 0x56, 0x9f, 0x96, 0x6d, 0x75, 0x10, 0xd7, 0x98, 0x26, 0x7b, 0x08, 0x6a, 0x8c, 0x82, 0x36, 0xa7, 0x1b, 0xfd, + 0x67, 0x11, 0xfa, 0x76, 0xe1, 0xd8, 0x8d, 0x82, 0x48, 0x88, 0x48, 0xeb, 0x35, 0x15, 0x03, 0xd4, 0xce, 0x63, 0x17, + 0xb1, 0x4a, 0x77, 0x0b, 0x51, 0xde, 0xa8, 0xac, 0xdf, 0xaf, 0x43, 0xb2, 0xdd, 0x62, 0x59, 0xe0, 0xcb, 0xfe, 0x6a, + 0xbd, 0x07, 0x02, 0xfd, 0xe9, 0xfa, 0xb3, 0x10, 0xe8, 0xcf, 0xb2, 0x2f, 0x81, 0x40, 0x7f, 0xba, 0xfe, 0x9f, 0x86, + 0x40, 0x7f, 0xb5, 0xf6, 0x20, 0xd0, 0xd5, 0x60, 0xfc, 0xa3, 0x60, 0xc1, 0x9b, 0xd7, 0x01, 0x7d, 0x26, 0x59, 0xf0, + 0xe6, 0xc5, 0x0b, 0x4f, 0x98, 0xfe, 0x83, 0xd0, 0x48, 0xfe, 0x46, 0x16, 0x8c, 0xb8, 0x2d, 0xf0, 0x0a, 0xb5, 0x4e, + 0x3e, 0x50, 0x51, 0x06, 0x40, 0xf4, 0xe5, 0x6f, 0x59, 0xb5, 0x0c, 0x83, 0xc3, 0x80, 0xcc, 0x1c, 0x24, 0xe8, 0x70, + 0xd2, 0xb8, 0xbd, 0xfd, 0x22, 0x1a, 0x42, 0x1d, 0x1b, 0x79, 0x00, 0xbe, 0xf2, 0x44, 0xf6, 0xfe, 0x0d, 0x11, 0x3f, + 0x99, 0x59, 0xd0, 0xd1, 0xc3, 0x80, 0x80, 0xc7, 0x52, 0xe6, 0x21, 0x70, 0xce, 0xfd, 0x90, 0xd0, 0x0f, 0xd6, 0x9e, + 0x6d, 0xd1, 0x2f, 0x22, 0xac, 0xc0, 0xe7, 0xee, 0xaf, 0x35, 0x3f, 0xcb, 0x52, 0xe2, 0xe4, 0xa1, 0x5c, 0x24, 0x32, + 0xe5, 0xbf, 0xbc, 0x7b, 0x69, 0x91, 0xc7, 0x43, 0x05, 0xbd, 0x44, 0x30, 0xa4, 0x71, 0xca, 0xaf, 0xb3, 0x84, 0xcf, + 0xfe, 0x78, 0xb0, 0xe9, 0xcc, 0xa8, 0x5e, 0x93, 0xfa, 0xf0, 0x8f, 0x28, 0x08, 0xf4, 0x18, 0xfc, 0xf1, 0x60, 0x93, + 0xd5, 0x87, 0x0f, 0x36, 0xd5, 0x28, 0x95, 0x00, 0xef, 0x0d, 0xbf, 0x65, 0xfd, 0x60, 0x53, 0xc2, 0x0f, 0x5e, 0xff, + 0xe1, 0x01, 0xb3, 0xd9, 0x06, 0x79, 0x7d, 0xb0, 0xca, 0x2b, 0x87, 0x09, 0x7a, 0x4f, 0xc1, 0xc2, 0x14, 0xea, 0xf0, + 0xa8, 0xd6, 0x9e, 0xdc, 0x6f, 0xaa, 0xbb, 0x4e, 0x08, 0x5c, 0x23, 0xdd, 0xc0, 0x21, 0x54, 0x96, 0x60, 0x27, 0x1d, + 0x9d, 0x12, 0xc4, 0xd4, 0x7c, 0x18, 0x28, 0x5b, 0x5f, 0x2f, 0x58, 0xb1, 0x6b, 0x26, 0xc6, 0x77, 0x1a, 0x03, 0x1b, + 0x2e, 0xba, 0x5a, 0xcc, 0xd9, 0x1f, 0xa6, 0xc7, 0xbb, 0x55, 0x48, 0x82, 0x18, 0xd9, 0x7e, 0x9f, 0x78, 0x3d, 0x4b, + 0x79, 0x15, 0x67, 0x39, 0x8b, 0xf3, 0xfc, 0x0f, 0x94, 0x45, 0x7c, 0xff, 0x45, 0xa0, 0xfb, 0xa3, 0xd1, 0x28, 0x2e, + 0x2e, 0xf1, 0xea, 0x6f, 0xc8, 0x2d, 0xc2, 0x62, 0x67, 0xbc, 0xb4, 0x81, 0x55, 0x96, 0x71, 0x79, 0xa6, 0x23, 0x1a, + 0x95, 0x96, 0x60, 0x97, 0x4b, 0x79, 0x73, 0x06, 0xd1, 0x1d, 0x2c, 0x05, 0x8f, 0x71, 0x00, 0xd5, 0xbd, 0xc9, 0x84, + 0x5d, 0x5e, 0xeb, 0x77, 0xe7, 0x71, 0xc9, 0xdf, 0xc6, 0xd5, 0x92, 0xc1, 0x5e, 0xd0, 0x54, 0xbd, 0x90, 0xeb, 0x95, + 0xab, 0xe4, 0x6c, 0x2d, 0x3e, 0x0a, 0x79, 0x23, 0x14, 0xed, 0x3d, 0xe3, 0xd7, 0xd0, 0x22, 0xb6, 0x41, 0x9d, 0x95, + 0xe0, 0x49, 0xe5, 0x71, 0xe2, 0x2a, 0x16, 0x40, 0x46, 0x4d, 0x34, 0x80, 0x8e, 0x1c, 0x34, 0xb4, 0x7b, 0x4d, 0x3b, + 0x96, 0x1b, 0x95, 0x45, 0x06, 0x96, 0xb0, 0xcf, 0xa1, 0x74, 0x40, 0x6c, 0x87, 0x70, 0x21, 0x70, 0xf5, 0xc4, 0xab, + 0x51, 0x03, 0xb1, 0x87, 0x96, 0xbe, 0xbb, 0x90, 0x62, 0xb5, 0x0c, 0xda, 0x65, 0x63, 0x98, 0xf0, 0x7a, 0x8d, 0x2e, + 0xc3, 0xa0, 0xf8, 0x2f, 0xdc, 0xa2, 0x44, 0x5c, 0xa4, 0x2c, 0x55, 0x46, 0x67, 0x3d, 0x94, 0x85, 0xe1, 0xb3, 0xa7, + 0xa3, 0xd4, 0x61, 0xe5, 0x3c, 0xb3, 0xbc, 0x8d, 0xd2, 0xc4, 0xcf, 0xc1, 0x24, 0xcc, 0xaf, 0x65, 0x2e, 0x75, 0x5c, + 0xf2, 0x33, 0xb1, 0xbe, 0xe2, 0x45, 0x96, 0x9c, 0x2d, 0xb3, 0xb2, 0x92, 0xc5, 0xdd, 0xc2, 0xc0, 0x5d, 0xe8, 0xb2, + 0x5a, 0x93, 0xb8, 0xf7, 0x3b, 0xf0, 0x79, 0x57, 0x01, 0x4c, 0x86, 0x4f, 0xc6, 0xa4, 0xd6, 0xd6, 0xf2, 0xd0, 0x40, + 0x6a, 0x7f, 0xab, 0x7d, 0xe2, 0x9e, 0x6d, 0xd7, 0x68, 0xd3, 0xcf, 0xa1, 0x5d, 0x23, 0x35, 0x4b, 0xa9, 0xe0, 0x7f, + 0xad, 0xb9, 0x89, 0x76, 0x10, 0x3a, 0x24, 0xef, 0xb0, 0xd4, 0x87, 0x91, 0x26, 0xd1, 0x0a, 0x09, 0x4a, 0x51, 0xdf, + 0xd6, 0x0b, 0xd5, 0x06, 0x42, 0xd4, 0x6d, 0x31, 0x4d, 0x9f, 0x23, 0x68, 0x3b, 0x48, 0x49, 0x70, 0x6f, 0xd9, 0x98, + 0x5f, 0x5d, 0xcb, 0x67, 0x0e, 0xdd, 0x59, 0xcc, 0x3e, 0x97, 0x61, 0x30, 0x88, 0x3e, 0x97, 0x85, 0x4d, 0xee, 0x59, + 0xa5, 0x2a, 0xcb, 0xb1, 0xb1, 0xbd, 0x9c, 0xa2, 0x29, 0x4b, 0xf8, 0x76, 0x1d, 0x36, 0xd7, 0x3e, 0xc5, 0xd9, 0xa7, + 0x9b, 0x2b, 0x5e, 0x2d, 0x65, 0x1a, 0x05, 0xdf, 0x3f, 0xff, 0x10, 0x18, 0xd5, 0x75, 0xa1, 0x41, 0x8b, 0xb4, 0x36, + 0x27, 0x97, 0x97, 0x20, 0xcb, 0xec, 0x15, 0x23, 0xf9, 0x71, 0x27, 0xca, 0xe7, 0x1f, 0x3f, 0x7c, 0xf8, 0xf0, 0xf6, + 0x00, 0x15, 0x3e, 0xbd, 0x83, 0xf7, 0x0a, 0x3d, 0xe0, 0xe0, 0xc1, 0xa6, 0xd0, 0x2a, 0xf6, 0xfa, 0x0f, 0x7b, 0x56, + 0x15, 0x2d, 0x05, 0xb9, 0x01, 0x05, 0xf4, 0xaa, 0x68, 0x0d, 0x6b, 0xe1, 0xb4, 0xd8, 0x7e, 0x66, 0xa5, 0x5d, 0x0a, + 0x50, 0x77, 0xa2, 0x6a, 0x8e, 0x94, 0x5e, 0x1e, 0x22, 0x2d, 0x84, 0xd5, 0x9e, 0xad, 0x56, 0x75, 0x6d, 0x35, 0x59, + 0x54, 0x99, 0xb8, 0x3c, 0xc3, 0xdd, 0xff, 0x45, 0x5b, 0xce, 0xcc, 0xb0, 0xa2, 0x17, 0xed, 0xdd, 0xd6, 0x80, 0x2a, + 0xd3, 0x46, 0xb9, 0x7a, 0x0f, 0x81, 0xc0, 0xac, 0xac, 0xa7, 0xfe, 0xc7, 0xc6, 0x62, 0xc4, 0x4f, 0x53, 0x40, 0x6e, + 0xc0, 0x03, 0xb1, 0x93, 0x78, 0x64, 0xda, 0x77, 0x83, 0x72, 0x93, 0xe3, 0xa4, 0x95, 0x30, 0x1b, 0x4e, 0xa2, 0x09, + 0xb1, 0xf1, 0x25, 0x34, 0x0d, 0xfb, 0x7e, 0xf4, 0xfc, 0xf5, 0x87, 0x97, 0x1f, 0xfe, 0x79, 0xf6, 0xf4, 0xf4, 0xc3, + 0xf3, 0xef, 0xdf, 0xbc, 0x7b, 0xf9, 0xfc, 0x3d, 0x9e, 0x10, 0x1a, 0xb0, 0x32, 0xdc, 0x68, 0xab, 0xe8, 0x66, 0x59, + 0x91, 0xa8, 0x49, 0xb3, 0x29, 0x0a, 0x31, 0x0a, 0x33, 0xdb, 0x22, 0x7f, 0x79, 0xfd, 0xec, 0xf9, 0x8b, 0x97, 0xaf, + 0x9f, 0x3f, 0x6b, 0x7f, 0x3d, 0x9c, 0xd4, 0xa4, 0x76, 0x33, 0xa7, 0x23, 0xa4, 0x70, 0x3b, 0x5e, 0x1d, 0xf4, 0x09, + 0xb5, 0xf2, 0x3e, 0x7d, 0xca, 0x60, 0x45, 0x32, 0x25, 0xa7, 0xc7, 0xdf, 0x1e, 0xfe, 0xaf, 0xda, 0x78, 0xdb, 0x2d, + 0xf0, 0x10, 0x48, 0xc6, 0x94, 0xac, 0x1f, 0x46, 0x35, 0xa3, 0xea, 0x65, 0x24, 0xa8, 0x2d, 0x0d, 0x6c, 0xa0, 0x53, + 0xaa, 0x42, 0x2a, 0x9c, 0x25, 0x71, 0xc5, 0x2f, 0x65, 0x71, 0x17, 0x65, 0xa3, 0x56, 0x0a, 0x6d, 0x2c, 0x80, 0x28, + 0x04, 0xc1, 0x72, 0x23, 0x89, 0xf4, 0x14, 0x01, 0xf0, 0x86, 0xc0, 0x8d, 0xea, 0xdc, 0x45, 0x0b, 0x68, 0x17, 0x4c, + 0x16, 0xdb, 0x6d, 0xc7, 0xa0, 0x75, 0xd2, 0xbe, 0x68, 0x9e, 0x29, 0xa2, 0xb8, 0x00, 0xc6, 0x1c, 0x8e, 0x37, 0x75, + 0x76, 0x31, 0x73, 0xdc, 0x9d, 0xea, 0xa8, 0x9f, 0x60, 0x8d, 0xe8, 0x5e, 0x9b, 0xc0, 0x32, 0xcd, 0xf3, 0x70, 0xdc, + 0xa2, 0xb8, 0x06, 0xe3, 0xb7, 0x95, 0xaa, 0x96, 0x99, 0xc6, 0x56, 0x84, 0x99, 0x82, 0x70, 0x5c, 0x46, 0x74, 0x1b, + 0xe6, 0x60, 0x21, 0xd3, 0x98, 0x5f, 0x33, 0x0e, 0x79, 0x24, 0x0d, 0x4c, 0x1e, 0x98, 0x0c, 0xee, 0xc9, 0xb5, 0x8c, + 0x8a, 0x06, 0xe0, 0xa5, 0x6c, 0x0e, 0xea, 0xf1, 0xff, 0x69, 0xef, 0x69, 0xb7, 0xdb, 0xb6, 0x91, 0xfd, 0xdf, 0xa7, + 0x60, 0x98, 0x6c, 0x4a, 0x26, 0x24, 0x4d, 0x4a, 0x96, 0xad, 0x48, 0x96, 0xdc, 0xe6, 0x6b, 0x9b, 0xd6, 0x6d, 0x7a, + 0x62, 0x37, 0x7b, 0x77, 0x5d, 0x1f, 0x8b, 0x92, 0x20, 0x89, 0x1b, 0x8a, 0xd4, 0x25, 0x29, 0x4b, 0xae, 0xc2, 0x7d, + 0x96, 0x7d, 0x84, 0xfb, 0x0c, 0x7d, 0xb2, 0x7b, 0x66, 0x06, 0x20, 0xc1, 0x0f, 0xc9, 0x72, 0x93, 0xb6, 0x7b, 0xcf, + 0xb9, 0xa7, 0x4d, 0x22, 0x82, 0x00, 0x08, 0x0c, 0x80, 0x99, 0xc1, 0x7c, 0x46, 0xc5, 0x67, 0xd8, 0xc6, 0xa5, 0x2a, + 0x28, 0xb2, 0x2d, 0x56, 0x02, 0xd1, 0xc2, 0xe8, 0x94, 0x3e, 0x6f, 0x25, 0xe1, 0x59, 0xb8, 0x12, 0xe2, 0xe1, 0x93, + 0xa8, 0xa6, 0x10, 0xcf, 0x46, 0xc7, 0x3d, 0x19, 0xd1, 0x0f, 0x27, 0xdd, 0x42, 0x04, 0xd2, 0x1c, 0xc0, 0x19, 0x73, + 0x3a, 0xa0, 0x2b, 0xd3, 0xf5, 0xa3, 0x8d, 0xd8, 0x78, 0xe9, 0xc0, 0xcb, 0x92, 0xbf, 0x16, 0x18, 0x8b, 0x94, 0x83, + 0x5e, 0x8e, 0x35, 0x5a, 0x53, 0x8d, 0xef, 0x8f, 0x9e, 0x57, 0xcb, 0x9d, 0x58, 0xf4, 0xc8, 0x28, 0x17, 0x66, 0x7d, + 0x15, 0xb6, 0x66, 0x23, 0xad, 0x6e, 0x60, 0x24, 0x5e, 0x12, 0x53, 0xc0, 0xf0, 0xcb, 0x88, 0xf1, 0x1f, 0x55, 0x30, + 0x3e, 0x5a, 0xd9, 0x65, 0x08, 0xff, 0xc7, 0xb7, 0xe7, 0x17, 0xa0, 0xbd, 0x72, 0x51, 0xdd, 0xbc, 0x51, 0xb9, 0xa5, + 0x8a, 0x09, 0xfa, 0x20, 0xb5, 0xa3, 0xba, 0x0b, 0xa0, 0xc7, 0x78, 0x2f, 0x38, 0x58, 0x9b, 0xab, 0xd5, 0xca, 0x04, + 0xbb, 0x55, 0x73, 0x19, 0xf9, 0xc4, 0x03, 0x8e, 0xd5, 0x54, 0x20, 0x72, 0x56, 0x42, 0xe4, 0x10, 0xf4, 0x96, 0x67, + 0x4d, 0x39, 0x9f, 0x85, 0xab, 0xaf, 0x7d, 0x5f, 0x16, 0xce, 0x08, 0x56, 0x8d, 0xcb, 0x2b, 0x0a, 0x88, 0x41, 0x03, + 0x1d, 0x93, 0xe5, 0xc5, 0xd7, 0xdc, 0x2a, 0x60, 0x7c, 0x3d, 0xbc, 0xbd, 0xe6, 0x9a, 0x87, 0x2c, 0xea, 0xf0, 0xf9, + 0xe0, 0x64, 0xec, 0xdd, 0x28, 0xc8, 0x4f, 0xf6, 0x54, 0x70, 0xd9, 0xf2, 0xd9, 0x70, 0x99, 0x24, 0x61, 0x60, 0x46, + 0xe1, 0x4a, 0xed, 0x9f, 0xd0, 0x83, 0xa8, 0xe0, 0xd2, 0xa3, 0xaa, 0x7c, 0x35, 0xf2, 0xbd, 0xd1, 0x87, 0x9e, 0xfa, + 0x68, 0xe3, 0xf5, 0xfa, 0x25, 0xae, 0xd1, 0x4e, 0xd5, 0x3e, 0x8c, 0x55, 0xf9, 0xda, 0xf7, 0x4f, 0x0e, 0xa8, 0x45, + 0xff, 0xe4, 0x60, 0xec, 0xdd, 0xf4, 0xa5, 0x04, 0x30, 0x5c, 0x3b, 0xda, 0xe3, 0x81, 0x36, 0x33, 0x7b, 0xb2, 0x18, + 0x23, 0x37, 0x8c, 0x98, 0x96, 0x5f, 0x71, 0x21, 0xa2, 0x0c, 0x8d, 0x57, 0x1b, 0xa1, 0xd0, 0xdc, 0x87, 0x0b, 0xdd, + 0xc7, 0x8f, 0x5a, 0x66, 0x6d, 0x3a, 0x93, 0x42, 0xb1, 0xa1, 0x32, 0x0f, 0xab, 0x18, 0x18, 0x4f, 0x46, 0xd7, 0x44, + 0xc0, 0x38, 0x5f, 0x37, 0x46, 0xa9, 0x81, 0x79, 0x74, 0xdc, 0x05, 0xe8, 0x15, 0xf9, 0x4f, 0xe9, 0xde, 0x3b, 0x82, + 0xdc, 0xd9, 0x12, 0xe2, 0xd6, 0x25, 0xcd, 0x0a, 0x9d, 0x42, 0x1e, 0x0d, 0x10, 0x54, 0x22, 0xf8, 0x1d, 0xd2, 0x76, + 0x68, 0xbe, 0x0e, 0xb9, 0xdb, 0xb2, 0x10, 0x3c, 0x6e, 0x2a, 0xb2, 0xa5, 0x09, 0xb8, 0x9c, 0x16, 0x56, 0xa8, 0x53, + 0x5e, 0x2f, 0x11, 0x1b, 0xf2, 0x41, 0xbc, 0x6d, 0xc9, 0x40, 0x53, 0xa7, 0x25, 0x46, 0x89, 0xce, 0x82, 0xef, 0x9e, + 0xa4, 0x1e, 0x62, 0x86, 0x76, 0x19, 0x1b, 0xe1, 0x55, 0x4e, 0x9b, 0x62, 0x42, 0x94, 0x9d, 0x30, 0xcd, 0xc3, 0x34, + 0xd3, 0xaa, 0xf7, 0x1f, 0x6d, 0x02, 0x24, 0x66, 0x71, 0xaf, 0x5f, 0xdc, 0x07, 0x89, 0x3b, 0x34, 0x69, 0x33, 0xab, + 0xca, 0x57, 0xe3, 0xa1, 0x9f, 0x2d, 0x36, 0x1d, 0x82, 0x99, 0x1b, 0x8c, 0x7d, 0x76, 0xe1, 0x0e, 0xbf, 0xc1, 0x3a, + 0x2f, 0x87, 0xfe, 0x0b, 0xa8, 0x90, 0xaa, 0xfd, 0x47, 0x1b, 0x22, 0xd7, 0x75, 0x08, 0x3b, 0xa5, 0x2d, 0x50, 0xfe, + 0x0e, 0x4f, 0xac, 0xc4, 0x22, 0x6a, 0x8d, 0x83, 0x25, 0x12, 0x4b, 0x18, 0xb5, 0x38, 0x32, 0x9e, 0xd8, 0x07, 0xf6, + 0xa6, 0xc2, 0x4f, 0x2d, 0x8c, 0x2b, 0x14, 0x27, 0x58, 0xde, 0x99, 0xf2, 0x60, 0x89, 0x94, 0xbe, 0x0b, 0x57, 0x62, + 0xa4, 0x1c, 0x00, 0x14, 0x88, 0xf2, 0xf4, 0x7c, 0x70, 0x22, 0x2b, 0x7f, 0x50, 0x42, 0x4e, 0xfd, 0xc2, 0xaf, 0x54, + 0x55, 0xf2, 0x34, 0x4f, 0x8b, 0xb5, 0xda, 0x3f, 0x39, 0x90, 0x6b, 0xf7, 0x07, 0x9d, 0x57, 0xd2, 0xe4, 0xb0, 0x57, + 0x71, 0x3b, 0xbe, 0xcc, 0x1f, 0xd2, 0x2b, 0x05, 0xee, 0xc2, 0x29, 0x94, 0x00, 0x8c, 0x8a, 0x4d, 0x2a, 0xe4, 0x07, + 0x12, 0x23, 0xe6, 0x04, 0x8a, 0x76, 0x8f, 0xc0, 0x8f, 0xa1, 0xde, 0xc9, 0x96, 0x90, 0xec, 0x2f, 0x45, 0x6f, 0x03, + 0xfe, 0x6f, 0x0e, 0x12, 0x94, 0x67, 0xb3, 0x20, 0x0e, 0x23, 0x15, 0xa6, 0x59, 0xce, 0x8e, 0xa4, 0x48, 0x59, 0xd9, + 0x70, 0xc2, 0xb5, 0x64, 0x15, 0x00, 0x76, 0x50, 0x6e, 0x2a, 0xcd, 0x7b, 0xa0, 0xe7, 0x3f, 0x14, 0x3e, 0x99, 0x12, + 0xd2, 0xca, 0x06, 0xb8, 0x3d, 0xeb, 0xd4, 0xe5, 0x5b, 0xcf, 0xf8, 0x5b, 0x68, 0xcc, 0x5d, 0x63, 0xe8, 0x1a, 0xe7, + 0xc1, 0x55, 0x5a, 0xbb, 0x78, 0x59, 0xc6, 0x38, 0x83, 0x75, 0x35, 0x88, 0xb3, 0x54, 0xbc, 0x57, 0x78, 0x16, 0xb7, + 0x0c, 0xb9, 0x70, 0xa3, 0x29, 0x13, 0x89, 0xda, 0xc4, 0x5b, 0x21, 0x21, 0xd0, 0x25, 0xb0, 0x40, 0x10, 0xb2, 0x07, + 0xdc, 0x80, 0xce, 0xb3, 0x46, 0x49, 0xe4, 0x7f, 0xc7, 0x6e, 0xe1, 0x3a, 0x19, 0x27, 0xe1, 0x02, 0x24, 0x53, 0xee, + 0x94, 0x6b, 0x1a, 0x0c, 0x60, 0x6a, 0xf6, 0xf9, 0xdc, 0xc7, 0x8f, 0x4c, 0xca, 0x1d, 0x96, 0x84, 0xd3, 0xa9, 0xcf, + 0x34, 0x29, 0xc7, 0x58, 0xf6, 0x99, 0xd3, 0x07, 0xb6, 0x88, 0x4f, 0xad, 0xa7, 0xdb, 0x0e, 0x56, 0xce, 0x01, 0x0a, + 0x9d, 0x3e, 0x20, 0x2e, 0x32, 0xa1, 0x42, 0x26, 0x5c, 0x13, 0xe7, 0x22, 0x3f, 0xb8, 0xe6, 0x38, 0x5c, 0x0e, 0x7d, + 0x66, 0xe2, 0x69, 0x80, 0x4f, 0x6e, 0x86, 0xcb, 0xe1, 0xd0, 0xa7, 0xa4, 0x60, 0x10, 0x65, 0x2d, 0x8c, 0x51, 0xfa, + 0x99, 0xea, 0x5d, 0xe4, 0xd4, 0x92, 0xf2, 0xf0, 0xc1, 0x32, 0x12, 0x6e, 0x0b, 0xf4, 0x81, 0x04, 0x24, 0x9d, 0xd5, + 0x33, 0xdd, 0x53, 0xe1, 0x96, 0xc2, 0x62, 0xb5, 0x5b, 0xc3, 0xd2, 0xf5, 0x2e, 0xd5, 0x73, 0x84, 0xb0, 0xe2, 0x06, + 0x63, 0xe5, 0x05, 0xed, 0x5d, 0xd5, 0x50, 0xc9, 0xc0, 0x8b, 0xe7, 0x90, 0x53, 0x0d, 0xf5, 0xa5, 0xe7, 0x4e, 0x83, + 0x30, 0x4e, 0xbc, 0x91, 0x7a, 0xd5, 0x7d, 0xe9, 0x69, 0x97, 0xf3, 0x44, 0xd3, 0xaf, 0x8c, 0xbf, 0xca, 0xd9, 0xbe, + 0x04, 0xa6, 0xc4, 0x64, 0x5f, 0x5b, 0xea, 0xc8, 0xa7, 0x67, 0x57, 0x3d, 0x81, 0x91, 0xb1, 0xce, 0x5f, 0x7b, 0x50, + 0xab, 0x94, 0x37, 0x0c, 0x13, 0x42, 0x42, 0xde, 0xb0, 0xbf, 0xea, 0x5d, 0x12, 0xb5, 0x7c, 0xbd, 0xdc, 0x20, 0xd3, + 0x90, 0xe4, 0xc4, 0x17, 0x43, 0xdd, 0x0b, 0xff, 0x50, 0x7a, 0x7e, 0x20, 0xfb, 0x36, 0x14, 0xc8, 0xf8, 0xe0, 0xeb, + 0x22, 0x07, 0xf2, 0x68, 0x93, 0xa4, 0x60, 0x58, 0x18, 0x84, 0x89, 0x02, 0xf1, 0xdb, 0xe0, 0x83, 0x83, 0xb2, 0x2d, + 0x34, 0xef, 0x55, 0xd3, 0x53, 0x8e, 0x05, 0x9e, 0x23, 0x2d, 0x45, 0xf9, 0x24, 0x84, 0x9b, 0x80, 0x50, 0xa4, 0x85, + 0x68, 0x4d, 0xdc, 0x03, 0x0f, 0x96, 0xaf, 0xc0, 0xbf, 0x49, 0x78, 0xbf, 0x48, 0xcf, 0x1f, 0x6d, 0xe2, 0x53, 0x41, + 0xd4, 0xdf, 0xc4, 0xb8, 0x96, 0xc0, 0xae, 0x70, 0x2a, 0x9f, 0xaa, 0xca, 0xa9, 0xa0, 0x44, 0x58, 0xb7, 0x80, 0x5e, + 0x35, 0xc1, 0xee, 0x46, 0x22, 0x32, 0x3e, 0x4f, 0x3f, 0x2e, 0x18, 0xb0, 0xd2, 0xd1, 0x83, 0x90, 0x4c, 0x19, 0x6f, + 0x95, 0x80, 0x5d, 0x35, 0x12, 0x0c, 0xc0, 0x5c, 0x9c, 0x47, 0x18, 0xa4, 0xd7, 0xc0, 0x48, 0x42, 0x9c, 0x32, 0x31, + 0x47, 0x23, 0x94, 0x53, 0xc5, 0x79, 0xc1, 0x62, 0x99, 0x60, 0xfc, 0x79, 0x18, 0x00, 0x4b, 0x55, 0x05, 0x2f, 0x89, + 0x80, 0xeb, 0xf3, 0xcb, 0x4f, 0xaa, 0x2a, 0xde, 0xb8, 0x5a, 0xc6, 0xe5, 0x31, 0x80, 0xe3, 0x70, 0x1a, 0xa8, 0xbd, + 0x81, 0xc7, 0x88, 0x4f, 0x63, 0x64, 0xe4, 0xc9, 0x5b, 0xb4, 0x11, 0x5a, 0x39, 0xd4, 0x20, 0x90, 0x11, 0xf5, 0xd3, + 0xd5, 0xfc, 0xda, 0xc9, 0x42, 0x4c, 0xea, 0xc2, 0x34, 0x07, 0x20, 0x89, 0x3c, 0x05, 0xd8, 0xf5, 0x1e, 0x6d, 0xdc, + 0xcc, 0x80, 0x4e, 0xbd, 0x50, 0xc9, 0x7a, 0x6e, 0x80, 0x60, 0x18, 0xa4, 0xd7, 0xb9, 0x3b, 0x6b, 0x3e, 0x5f, 0xd8, + 0x92, 0x54, 0xae, 0xa0, 0x3d, 0x5b, 0x8f, 0x5b, 0xad, 0x2d, 0x22, 0x6f, 0xee, 0x46, 0xb7, 0x64, 0xe4, 0x66, 0xc8, + 0x96, 0x70, 0xba, 0xaa, 0x10, 0x3d, 0x20, 0x00, 0x10, 0x69, 0x50, 0x95, 0xaf, 0xb2, 0x32, 0xc6, 0x67, 0x9b, 0x59, + 0xfa, 0xc0, 0xb7, 0xae, 0xd5, 0xa7, 0xcc, 0x22, 0x29, 0x23, 0x35, 0xe9, 0x6a, 0xf1, 0x96, 0xe9, 0xc5, 0xc5, 0xe9, + 0x05, 0xc5, 0x8d, 0x86, 0x93, 0x21, 0x4a, 0x41, 0xe3, 0xc6, 0x99, 0x61, 0xaa, 0xcb, 0xfa, 0x15, 0xa5, 0x77, 0x7f, + 0xe8, 0x72, 0x30, 0x58, 0x8e, 0x00, 0x96, 0xa3, 0x46, 0x00, 0xeb, 0x8a, 0x15, 0x01, 0x5e, 0x04, 0xb8, 0x90, 0x08, + 0x39, 0x10, 0xca, 0x82, 0xa9, 0x64, 0x5b, 0x28, 0x82, 0xa3, 0x41, 0x63, 0xa7, 0xa3, 0x11, 0xf5, 0x7a, 0x21, 0xb6, + 0x8a, 0xd2, 0x93, 0x03, 0xaa, 0x4d, 0x44, 0x91, 0x2a, 0x01, 0x18, 0x22, 0x98, 0x61, 0x0e, 0x05, 0x48, 0x03, 0xde, + 0x73, 0xf2, 0x8b, 0x8e, 0x35, 0x47, 0xe5, 0xb3, 0x73, 0x5a, 0x64, 0x78, 0xb0, 0x95, 0xda, 0x3f, 0xc1, 0xc4, 0x9e, + 0x40, 0xd6, 0x21, 0xf4, 0xd5, 0xc9, 0x01, 0x3d, 0x2a, 0xa5, 0x13, 0x91, 0x77, 0x22, 0xa4, 0x8e, 0x1d, 0xde, 0xc1, + 0xbd, 0x8e, 0x4a, 0x9c, 0xb0, 0x05, 0x94, 0xba, 0xa9, 0xaa, 0xcc, 0x39, 0x83, 0xc5, 0x63, 0xec, 0x41, 0x00, 0x1e, + 0x1b, 0x1c, 0x1f, 0x54, 0x65, 0xee, 0xae, 0x71, 0xe6, 0xe2, 0x8d, 0xbb, 0xd6, 0x1c, 0xfe, 0x2a, 0x3f, 0x6b, 0x71, + 0xf1, 0xac, 0x8d, 0xf8, 0xe2, 0x82, 0x77, 0x9d, 0x60, 0xac, 0xb5, 0x19, 0x5a, 0x2d, 0xd5, 0x2c, 0xee, 0x4c, 0x2c, + 0xee, 0x78, 0xcb, 0xe2, 0x8e, 0x77, 0x2c, 0xae, 0xcf, 0x17, 0x52, 0xc9, 0x40, 0x17, 0xa1, 0xc7, 0x74, 0x06, 0x3c, + 0xce, 0x8f, 0x74, 0xf8, 0x39, 0x43, 0x38, 0x99, 0xb1, 0x0f, 0x16, 0xc3, 0x5b, 0x60, 0x55, 0x07, 0x17, 0x09, 0x10, + 0xd5, 0x89, 0x67, 0xa7, 0x6e, 0x24, 0x49, 0x06, 0x34, 0xbf, 0x3c, 0x5f, 0xd8, 0xa5, 0xd8, 0xd0, 0xd0, 0x66, 0x5b, + 0x66, 0x3a, 0xdb, 0x31, 0xd3, 0x51, 0xe1, 0xe8, 0xf2, 0x69, 0xd3, 0x21, 0x94, 0x27, 0x05, 0x7b, 0x10, 0xbc, 0x28, + 0x70, 0xcb, 0x14, 0xf7, 0xe1, 0x76, 0x1c, 0x2b, 0xed, 0xa8, 0x85, 0x1b, 0xc7, 0xab, 0x30, 0x02, 0x33, 0x04, 0xe8, + 0xe6, 0x7e, 0x5b, 0x6a, 0xee, 0x05, 0x3c, 0xc2, 0xd9, 0xd6, 0xcd, 0x94, 0xbf, 0x97, 0xb7, 0x54, 0xa3, 0xd5, 0xa2, + 0x1a, 0x0b, 0x37, 0x49, 0x58, 0x84, 0x40, 0x77, 0x21, 0x15, 0xc6, 0x7f, 0xc8, 0x36, 0xab, 0xc1, 0x21, 0xbe, 0x84, + 0xd5, 0x11, 0x43, 0x2f, 0x80, 0x05, 0x23, 0xbd, 0x63, 0xa0, 0x6f, 0xa4, 0x68, 0xa9, 0x51, 0x06, 0xf8, 0x1f, 0xf0, + 0xb8, 0x6a, 0x91, 0xe4, 0xcf, 0xeb, 0x1c, 0xe9, 0xd6, 0xc2, 0x1d, 0x9f, 0x83, 0xb5, 0x8b, 0xd6, 0x30, 0xc0, 0x73, + 0x45, 0x8e, 0x8d, 0x1a, 0x11, 0x4f, 0x38, 0xca, 0x91, 0x24, 0x62, 0x49, 0x6e, 0x17, 0x0c, 0x21, 0x05, 0x5c, 0x73, + 0x72, 0xb5, 0x69, 0xa4, 0x07, 0x53, 0x4f, 0xaf, 0x60, 0x4d, 0x40, 0x6d, 0x7e, 0xaf, 0x9f, 0x09, 0xdd, 0x7c, 0xc3, + 0x39, 0xd2, 0x41, 0x1d, 0x7a, 0x09, 0x49, 0xcf, 0x6d, 0x71, 0x99, 0x1e, 0x44, 0x40, 0xb5, 0x40, 0x79, 0xf8, 0x78, + 0x8a, 0xbf, 0x9c, 0xab, 0xf4, 0xf1, 0x10, 0x7f, 0x35, 0xae, 0x32, 0x55, 0x55, 0x49, 0x8a, 0x20, 0xcd, 0x59, 0xed, + 0x17, 0xf6, 0x13, 0x19, 0x65, 0xdf, 0x63, 0xdb, 0xf0, 0x05, 0x7e, 0xf8, 0x68, 0x13, 0x43, 0x18, 0x02, 0x79, 0x0e, + 0x81, 0x15, 0xe9, 0x69, 0x6d, 0xf9, 0x74, 0x4b, 0xf9, 0x50, 0xff, 0x83, 0x09, 0x3f, 0xee, 0x92, 0x30, 0xa7, 0x29, + 0x45, 0x19, 0xc8, 0xf5, 0xd0, 0x0b, 0xdc, 0xe8, 0xf6, 0x9a, 0x6e, 0x21, 0x9a, 0x24, 0xe4, 0x7d, 0x90, 0x0b, 0x07, + 0x6e, 0x8b, 0x36, 0x20, 0x89, 0xa4, 0xa0, 0xba, 0xe5, 0x84, 0xbe, 0xf7, 0x5d, 0x24, 0xf1, 0x77, 0x85, 0x6b, 0x2c, + 0x5f, 0x90, 0xc2, 0x87, 0xae, 0x1f, 0x6d, 0x34, 0x56, 0xed, 0xa6, 0x34, 0xdb, 0x12, 0x03, 0x09, 0xcb, 0x83, 0x57, + 0xe2, 0xf9, 0xd8, 0xeb, 0xa0, 0x91, 0xc7, 0x30, 0x5c, 0x9b, 0x8f, 0x36, 0xc9, 0xa9, 0x3a, 0x77, 0xa3, 0x0f, 0x6c, + 0x6c, 0x8e, 0xbc, 0x68, 0xe4, 0x03, 0xf3, 0x38, 0xf4, 0xdd, 0xe0, 0x03, 0x7f, 0x34, 0xc3, 0x65, 0x82, 0x66, 0x5b, + 0x77, 0xde, 0xa0, 0x05, 0x4c, 0x48, 0x90, 0x88, 0x5c, 0x6d, 0x0d, 0x14, 0x94, 0xf3, 0x81, 0xb8, 0xd6, 0xe7, 0x8c, + 0x62, 0x5e, 0xcb, 0x00, 0xaf, 0x03, 0xb0, 0x24, 0x83, 0x30, 0x0e, 0x86, 0x8a, 0xeb, 0xa5, 0x1a, 0xf2, 0x54, 0x49, + 0x8f, 0x96, 0xe5, 0x21, 0xbe, 0xc6, 0x1e, 0x7e, 0xfb, 0xe7, 0xa0, 0xe4, 0x3e, 0x9f, 0xcb, 0x7a, 0xf9, 0xb4, 0x19, + 0x42, 0xa9, 0x49, 0xee, 0x83, 0xf7, 0xf8, 0x38, 0x67, 0x30, 0xb7, 0x7f, 0x5a, 0x6e, 0xec, 0xc6, 0xf1, 0x72, 0xce, + 0xc6, 0xa4, 0x0c, 0x3b, 0xcd, 0x07, 0x55, 0xbc, 0x87, 0xc8, 0x03, 0xfb, 0x79, 0xd9, 0x38, 0x3e, 0x7c, 0x01, 0x66, + 0x7c, 0xc0, 0x50, 0x86, 0x93, 0x89, 0x9a, 0x8b, 0x02, 0xee, 0x68, 0xe6, 0x1c, 0xfe, 0xbc, 0x7c, 0xfd, 0xca, 0x7e, + 0x9d, 0x35, 0x0e, 0x80, 0x31, 0x16, 0x36, 0x49, 0x9c, 0x2f, 0x96, 0xc6, 0x2b, 0x66, 0x34, 0x71, 0x83, 0xed, 0xd3, + 0xb9, 0x2c, 0x6c, 0xf1, 0x05, 0x63, 0x63, 0x60, 0xb8, 0x8d, 0x4a, 0xe9, 0xb5, 0xcf, 0x6e, 0x58, 0x66, 0xef, 0x54, + 0xfd, 0x58, 0x4d, 0x0b, 0x0c, 0xc8, 0xca, 0x75, 0x8f, 0x9c, 0xab, 0x93, 0xa6, 0x34, 0xc0, 0x39, 0xf0, 0x99, 0xcb, + 0x47, 0xac, 0x74, 0xa4, 0x06, 0x86, 0x2a, 0x0d, 0x60, 0xeb, 0xc8, 0x4e, 0xb7, 0x94, 0x77, 0x00, 0x51, 0x6f, 0x19, + 0x9b, 0xe1, 0xe8, 0x1d, 0x48, 0x60, 0xc1, 0xe1, 0xe4, 0xc3, 0xc9, 0xd3, 0x72, 0xa9, 0xc9, 0x36, 0x88, 0xd5, 0x89, + 0xda, 0x54, 0x12, 0xd2, 0x08, 0x17, 0x00, 0xf4, 0x85, 0x11, 0xe2, 0xaa, 0xda, 0xb5, 0x51, 0x8a, 0x33, 0x1f, 0x62, + 0x7a, 0xf7, 0x80, 0xc5, 0xf1, 0x56, 0x80, 0x65, 0x8b, 0x6e, 0xa8, 0x79, 0xed, 0x22, 0x3c, 0xf2, 0x72, 0xc3, 0x36, + 0x80, 0x25, 0xc0, 0x09, 0x96, 0xbf, 0x85, 0xe4, 0xe5, 0x7a, 0xce, 0x8d, 0x38, 0xa3, 0xe9, 0x50, 0xe5, 0x06, 0x76, + 0xdb, 0xde, 0xaf, 0x54, 0x3e, 0xa8, 0x02, 0x99, 0xae, 0x1d, 0x9a, 0x56, 0x40, 0xbd, 0x15, 0xa9, 0x12, 0x76, 0x20, + 0xc6, 0x54, 0xc2, 0xaf, 0x6c, 0x32, 0x61, 0xa3, 0x24, 0xd6, 0x85, 0x8c, 0x29, 0x0b, 0xa9, 0x0e, 0x4a, 0xbb, 0x07, + 0x3d, 0xf5, 0x07, 0x08, 0x2c, 0x23, 0x22, 0x0f, 0xf2, 0x01, 0x89, 0x3b, 0x53, 0x3d, 0x98, 0xa8, 0xc7, 0x22, 0x88, + 0xf8, 0x57, 0x40, 0x0a, 0x5d, 0x53, 0x8e, 0x43, 0xe3, 0xf4, 0x27, 0xdf, 0x17, 0x61, 0x66, 0xea, 0xb9, 0x1b, 0x15, + 0xed, 0x3a, 0xbe, 0x1b, 0xe7, 0x75, 0xcb, 0xb1, 0x53, 0xd5, 0x00, 0x87, 0xe6, 0x0f, 0xa5, 0x6d, 0x4c, 0x04, 0xaa, + 0xa7, 0x9e, 0xbd, 0x7d, 0xf1, 0xdd, 0xab, 0x97, 0xfb, 0x62, 0x04, 0xec, 0xb2, 0x09, 0x5d, 0x2e, 0x83, 0x1d, 0x9d, + 0xfe, 0xf4, 0xc3, 0xfd, 0xba, 0x6d, 0x38, 0xcf, 0x1c, 0xd5, 0x20, 0x1b, 0x74, 0x09, 0x2f, 0x8e, 0xc2, 0x1b, 0x16, + 0x7d, 0x32, 0x18, 0xe4, 0xce, 0xeb, 0x87, 0xfb, 0xf6, 0xc7, 0x57, 0x3f, 0xec, 0x3d, 0xd4, 0x23, 0xc7, 0x06, 0xdc, + 0x9e, 0x84, 0x8b, 0x7b, 0xcc, 0xae, 0xa9, 0x1a, 0xea, 0xc8, 0x0f, 0x63, 0xb6, 0x65, 0x04, 0x2f, 0xce, 0xde, 0x9e, + 0x23, 0xb8, 0x72, 0x16, 0x84, 0xba, 0xfa, 0xb4, 0xc9, 0xff, 0xf8, 0xee, 0xd5, 0xf9, 0xb9, 0x6a, 0x60, 0x4a, 0xee, + 0x58, 0xee, 0x9d, 0x6f, 0xe2, 0x3b, 0x28, 0x4e, 0xed, 0x5e, 0x27, 0xaa, 0x46, 0x17, 0xe9, 0xe2, 0x6c, 0xa8, 0xac, + 0xb2, 0xcd, 0x39, 0xb5, 0xe3, 0x5f, 0xa6, 0xdb, 0xef, 0x5e, 0xf3, 0xaa, 0xc1, 0x47, 0xbb, 0x49, 0x6a, 0xa1, 0x64, + 0xee, 0x05, 0xd7, 0x35, 0xa5, 0xee, 0xba, 0xa6, 0x14, 0xae, 0x8f, 0x15, 0xfc, 0xb8, 0x0c, 0xe7, 0x12, 0x3b, 0xc2, + 0xd6, 0x77, 0x83, 0x4b, 0xba, 0xc3, 0x7d, 0xc2, 0xa0, 0x79, 0x4a, 0x95, 0xf2, 0xa8, 0x6b, 0x8a, 0xf9, 0xc5, 0x2b, + 0x83, 0xed, 0xc8, 0x07, 0xcb, 0x7b, 0x26, 0xab, 0x21, 0x8b, 0xac, 0x2a, 0xf7, 0x9b, 0xe9, 0x95, 0x6e, 0x05, 0xd4, + 0x8c, 0x54, 0x37, 0x9c, 0xa6, 0x2c, 0xdc, 0x31, 0x98, 0xb3, 0x9b, 0xc3, 0x30, 0x49, 0xc2, 0x79, 0xc7, 0xb1, 0x17, + 0x6b, 0x55, 0xe9, 0x0a, 0x61, 0x07, 0xb7, 0xb6, 0xef, 0xfc, 0xfa, 0xef, 0x12, 0x9a, 0xa7, 0xf2, 0xeb, 0x84, 0xcd, + 0x17, 0x2c, 0x72, 0x93, 0x65, 0xc4, 0x52, 0xe5, 0xd7, 0xff, 0x79, 0x51, 0xba, 0xd8, 0x77, 0xe5, 0x36, 0xc4, 0xd2, + 0xcb, 0x4d, 0xae, 0xfd, 0x70, 0xf5, 0x20, 0xf7, 0xab, 0xbb, 0xa3, 0xf2, 0xcc, 0x9b, 0xce, 0xb2, 0xda, 0xa7, 0xc9, + 0x8e, 0xb9, 0x89, 0xd1, 0x93, 0x3e, 0x40, 0x39, 0x0b, 0x57, 0x9d, 0x5f, 0xff, 0x9d, 0x09, 0x6c, 0xee, 0xdc, 0x75, + 0xf5, 0x03, 0x2d, 0xae, 0x68, 0x7d, 0x9d, 0xca, 0x12, 0xc3, 0xfb, 0xca, 0x02, 0x57, 0x0a, 0x69, 0x57, 0x56, 0x75, + 0x73, 0x3b, 0xe6, 0xf4, 0x8d, 0x37, 0x9d, 0x7d, 0xea, 0xa4, 0x00, 0xa0, 0x77, 0xce, 0x0a, 0x2a, 0x7d, 0x86, 0x69, + 0x0d, 0x3a, 0xfb, 0x2f, 0xd8, 0x27, 0xce, 0xeb, 0xae, 0x29, 0x7d, 0x8e, 0xd9, 0x70, 0xc9, 0xed, 0xf9, 0x60, 0x90, + 0xa5, 0xa4, 0x95, 0xdb, 0x83, 0x67, 0xe0, 0x69, 0xa5, 0x84, 0xb3, 0x17, 0x1d, 0x5b, 0xa7, 0x90, 0x3d, 0x7b, 0x00, + 0x04, 0x6d, 0xdc, 0x6b, 0xc0, 0xb1, 0x1d, 0x5f, 0x93, 0xab, 0x5a, 0xe5, 0xdb, 0x15, 0x64, 0x0d, 0xa5, 0x98, 0xce, + 0x34, 0xd3, 0x1a, 0x1a, 0xf5, 0xc3, 0x59, 0x45, 0xee, 0x82, 0x94, 0x04, 0x0a, 0x6a, 0x4c, 0x40, 0xe8, 0x52, 0xba, + 0x45, 0xdf, 0xb8, 0xfe, 0xcd, 0x7e, 0x17, 0xaa, 0xed, 0x14, 0x0c, 0x49, 0xf3, 0x9f, 0x47, 0xbc, 0x91, 0x2e, 0xdf, + 0x9b, 0x76, 0xaf, 0xdc, 0x84, 0x45, 0xd7, 0x33, 0xf0, 0xe9, 0x15, 0xd2, 0x03, 0x88, 0x96, 0xbb, 0x0b, 0x29, 0x17, + 0xd8, 0xd2, 0x1a, 0x34, 0x9a, 0x63, 0xb8, 0xdf, 0x86, 0xbb, 0x3f, 0x13, 0xe6, 0xee, 0xbc, 0x02, 0xaf, 0xcb, 0xdf, + 0x0d, 0x7b, 0xef, 0xa2, 0x4c, 0xff, 0x8f, 0xbd, 0xff, 0x13, 0xb1, 0xf7, 0xce, 0xef, 0xfc, 0x96, 0x85, 0xfd, 0x3f, + 0x80, 0xe5, 0x3b, 0xac, 0xf7, 0x8a, 0x63, 0x7a, 0x4d, 0x73, 0x7b, 0x42, 0xb9, 0x2a, 0xe3, 0xd5, 0x8a, 0x82, 0x95, + 0x87, 0x54, 0xe3, 0x96, 0x83, 0x2e, 0x22, 0xfb, 0x1d, 0x47, 0xf9, 0xf7, 0x47, 0xf4, 0x31, 0xe5, 0xa1, 0x92, 0x30, + 0x7d, 0xe7, 0x95, 0x11, 0x17, 0x66, 0xe2, 0xae, 0xdc, 0xdb, 0x7d, 0xf0, 0x8e, 0x18, 0xec, 0xd7, 0x2b, 0xf7, 0xb6, + 0x6e, 0xb0, 0x5b, 0xd1, 0x6b, 0xf9, 0x63, 0xa7, 0xe0, 0xcb, 0xd3, 0x41, 0x47, 0x1e, 0x63, 0x10, 0xb3, 0xe4, 0x14, + 0x0a, 0x7b, 0x8f, 0x36, 0x0f, 0xca, 0x15, 0xd3, 0x01, 0x78, 0x39, 0x4b, 0x03, 0x0f, 0x0b, 0x03, 0xf7, 0xe2, 0xeb, + 0x30, 0xb8, 0xcf, 0xc8, 0x7f, 0x04, 0xe1, 0xcf, 0x6f, 0x1e, 0x3a, 0x7e, 0xae, 0x32, 0x76, 0x2c, 0x2d, 0x0f, 0x1e, + 0x0b, 0xcb, 0xa3, 0xef, 0xd6, 0xcb, 0xea, 0x4b, 0x84, 0x16, 0x69, 0x2c, 0x23, 0x42, 0xab, 0x80, 0x5e, 0x45, 0x01, + 0x1d, 0x97, 0x20, 0xb9, 0x98, 0xa0, 0xf4, 0xd5, 0x36, 0xa7, 0xae, 0x37, 0x77, 0x3b, 0x75, 0x5d, 0xec, 0xe5, 0xd4, + 0xf5, 0xe6, 0xb3, 0x3b, 0x75, 0xbd, 0x92, 0x9d, 0xba, 0xe0, 0x50, 0xbd, 0x62, 0x7b, 0xf9, 0xd0, 0x08, 0x3b, 0xd7, + 0x70, 0x15, 0xf7, 0x1c, 0x2e, 0x6e, 0x8b, 0x47, 0x33, 0x06, 0xfa, 0x0b, 0xbe, 0xff, 0xfd, 0x70, 0x0a, 0xae, 0x2e, + 0xdb, 0x9d, 0x59, 0x3e, 0x97, 0x2b, 0x8b, 0x1f, 0x4e, 0x55, 0x29, 0x45, 0x4b, 0x20, 0x52, 0xb4, 0x40, 0x58, 0x9a, + 0x9f, 0xd7, 0xce, 0xf3, 0x4b, 0xa7, 0xdb, 0x74, 0x20, 0xc4, 0x19, 0x88, 0xa4, 0xb1, 0xc0, 0xee, 0x36, 0x9b, 0x50, + 0xb0, 0x92, 0x0a, 0x1a, 0x50, 0xe0, 0x49, 0x05, 0x2d, 0x28, 0x18, 0x49, 0x05, 0x47, 0x50, 0x30, 0x96, 0x0a, 0x8e, + 0xa1, 0xe0, 0x46, 0x4d, 0x2f, 0x83, 0xcc, 0x65, 0xed, 0x58, 0xbf, 0x2a, 0x64, 0xe7, 0xca, 0xf4, 0x27, 0xa2, 0xca, + 0xb1, 0x21, 0x42, 0x45, 0x9b, 0x87, 0x3a, 0x77, 0x8e, 0x1a, 0x7c, 0x31, 0x80, 0x20, 0x2e, 0xa0, 0x4e, 0x32, 0x40, + 0x19, 0x47, 0x35, 0x9b, 0xe2, 0xb5, 0xda, 0xc9, 0x5c, 0xbc, 0x6c, 0xa3, 0x21, 0x5c, 0xa6, 0x3a, 0xe8, 0xc0, 0x2b, + 0x2a, 0xb7, 0x9e, 0xce, 0xb2, 0xb8, 0x91, 0xcb, 0x5e, 0xee, 0x07, 0xdf, 0x84, 0xe8, 0xf9, 0x60, 0x14, 0xf5, 0x12, + 0x6f, 0xa6, 0x56, 0x12, 0x82, 0x9b, 0xb3, 0x88, 0x97, 0x28, 0x3e, 0xa0, 0xa0, 0x1f, 0x5c, 0xd7, 0xcd, 0x43, 0x5b, + 0xf2, 0x28, 0xab, 0x34, 0xfa, 0x79, 0x16, 0xbc, 0x92, 0x04, 0xac, 0x4b, 0x23, 0x71, 0xa7, 0x9d, 0x99, 0x41, 0xda, + 0xd5, 0xce, 0x14, 0xa2, 0x91, 0x9f, 0x8e, 0x3b, 0x0b, 0x63, 0x35, 0x63, 0x41, 0x67, 0xc2, 0xfd, 0x0f, 0x60, 0xfd, + 0xc9, 0xbc, 0x74, 0xae, 0x0b, 0xbb, 0x68, 0xdc, 0x13, 0xf9, 0x5b, 0x1a, 0xa5, 0x99, 0x6d, 0xa5, 0xdc, 0xa4, 0x57, + 0x93, 0x35, 0xaf, 0x9f, 0xc3, 0x00, 0xf3, 0x25, 0x1b, 0x2e, 0xa7, 0xca, 0x59, 0x38, 0xbd, 0xd3, 0xd8, 0x52, 0x7e, + 0x05, 0xa3, 0x54, 0xc9, 0xc4, 0xc4, 0x14, 0xdb, 0x9b, 0x7f, 0x8b, 0x1e, 0xd3, 0x62, 0xfd, 0x04, 0xc6, 0xa6, 0x24, + 0x84, 0xdc, 0xe0, 0x3b, 0x00, 0x6d, 0xc9, 0x9c, 0xf1, 0x0c, 0xe0, 0x27, 0x3d, 0x5f, 0xb8, 0xd2, 0x78, 0xfa, 0xdf, + 0xb3, 0x38, 0x76, 0xa7, 0xa2, 0x7e, 0x75, 0x9c, 0xe0, 0xd9, 0x9b, 0xc9, 0x98, 0x11, 0x80, 0xa0, 0xad, 0xf4, 0x2a, + 0x46, 0xaa, 0xe0, 0x3b, 0x03, 0xc6, 0xdb, 0xb0, 0x68, 0xb9, 0x45, 0xa7, 0x67, 0xc1, 0xf2, 0x14, 0x8d, 0x2b, 0x01, + 0x89, 0xdc, 0x30, 0xbf, 0x5c, 0x98, 0xb8, 0xd3, 0x72, 0x11, 0xad, 0x75, 0x2a, 0x8f, 0x2d, 0xb3, 0x6d, 0x2c, 0x14, + 0x7e, 0x8a, 0xb1, 0x9e, 0x1f, 0x4e, 0x7f, 0x57, 0x4b, 0xbd, 0x1d, 0x16, 0x96, 0xe7, 0x81, 0x11, 0x24, 0x03, 0x0b, + 0x61, 0xac, 0x58, 0x00, 0xc2, 0x4e, 0x90, 0xcc, 0x4c, 0x8c, 0x29, 0xa3, 0x35, 0x02, 0xdd, 0xb0, 0x70, 0x6d, 0x37, + 0xe5, 0x48, 0x5a, 0x9d, 0x68, 0x3a, 0x74, 0x35, 0xa7, 0x71, 0x6c, 0x88, 0x3f, 0x96, 0xdd, 0xd2, 0x53, 0xec, 0x41, + 0x19, 0x7b, 0x37, 0x9b, 0x49, 0x18, 0x24, 0xe6, 0xc4, 0x9d, 0x7b, 0xfe, 0x6d, 0x67, 0x1e, 0x06, 0x61, 0xbc, 0x70, + 0x47, 0xac, 0x9b, 0x2b, 0x0d, 0xba, 0x18, 0xa3, 0x91, 0x87, 0x09, 0x72, 0xac, 0x46, 0xc4, 0xe6, 0xd4, 0x3a, 0x0b, + 0xc1, 0x38, 0xf1, 0xd9, 0x3a, 0xe5, 0x9f, 0x2f, 0x54, 0xa6, 0xaa, 0xb8, 0xe5, 0xa8, 0x05, 0x48, 0xc0, 0x78, 0x7c, + 0x47, 0x88, 0x6a, 0xdc, 0xe5, 0x57, 0x91, 0x8e, 0xd5, 0x68, 0x45, 0x6c, 0xae, 0x58, 0xad, 0xad, 0x9d, 0x47, 0xe1, + 0xaa, 0x0f, 0xa3, 0xc5, 0xc6, 0x66, 0xcc, 0xfc, 0x09, 0xbe, 0x31, 0x31, 0xa4, 0x84, 0xe8, 0xc7, 0x44, 0x65, 0x03, + 0xf4, 0xc6, 0xe6, 0x5d, 0x78, 0xdd, 0x69, 0x28, 0x76, 0x77, 0xee, 0x05, 0x26, 0x4d, 0xe7, 0xd8, 0x5e, 0x48, 0x7d, + 0xc9, 0xf0, 0xd3, 0x37, 0x58, 0xdd, 0x51, 0xec, 0x2e, 0x08, 0x95, 0x27, 0x7e, 0xb8, 0xea, 0xcc, 0xbc, 0xf1, 0x98, + 0x05, 0x5d, 0x1c, 0x73, 0x56, 0xc8, 0x7c, 0xdf, 0x5b, 0xc4, 0x5e, 0xdc, 0x9d, 0xbb, 0x6b, 0xde, 0xeb, 0xe1, 0xb6, + 0x5e, 0x9b, 0xbc, 0xd7, 0xe6, 0xde, 0xbd, 0x4a, 0xdd, 0x40, 0xf8, 0x0a, 0xea, 0x87, 0x0f, 0xad, 0xa5, 0xd8, 0xa5, + 0x79, 0xee, 0xdd, 0xeb, 0x22, 0x62, 0x9b, 0xb9, 0x1b, 0x4d, 0xbd, 0xa0, 0x63, 0xa7, 0xd6, 0xcd, 0x86, 0x36, 0xc6, + 0xc3, 0x76, 0xbb, 0x9d, 0x5a, 0x63, 0xf1, 0x64, 0x8f, 0xc7, 0xa9, 0x35, 0x12, 0x4f, 0x93, 0x89, 0x6d, 0x4f, 0x26, + 0xa9, 0xe5, 0x89, 0x82, 0x66, 0x63, 0x34, 0x6e, 0x36, 0x52, 0x6b, 0x25, 0xd5, 0x48, 0x2d, 0xc6, 0x9f, 0x22, 0x36, + 0xee, 0xe2, 0x46, 0xe2, 0x8e, 0x50, 0xc7, 0xb6, 0x9d, 0x22, 0x06, 0xb8, 0x2c, 0xe0, 0x26, 0xd4, 0x67, 0x5d, 0x6d, + 0xf6, 0xae, 0xa9, 0xe4, 0x9f, 0x1b, 0x8d, 0x6a, 0xeb, 0x8d, 0xdd, 0xe8, 0xc3, 0x95, 0x22, 0xcd, 0xc2, 0x75, 0xa9, + 0xda, 0x46, 0x80, 0xc1, 0x5c, 0x77, 0x20, 0x56, 0x77, 0x77, 0x18, 0x46, 0x70, 0x66, 0x23, 0x77, 0xec, 0x2d, 0xe3, + 0x8e, 0xd3, 0x58, 0xac, 0x45, 0x11, 0xdf, 0xeb, 0x79, 0x01, 0x9e, 0xbd, 0x4e, 0x1c, 0xfa, 0xde, 0x58, 0x14, 0x6d, + 0x3b, 0x4b, 0x4e, 0x43, 0xef, 0x62, 0xa4, 0x3a, 0x0f, 0xe3, 0x2d, 0xba, 0xbe, 0xaf, 0x58, 0xcd, 0x58, 0x61, 0x6e, + 0x8c, 0x3a, 0x74, 0xc5, 0x8e, 0x09, 0x2e, 0x18, 0x95, 0xce, 0x39, 0x5c, 0xac, 0xb3, 0x3d, 0xef, 0x1c, 0x2d, 0xd6, + 0xe9, 0x57, 0x73, 0x36, 0xf6, 0x5c, 0x45, 0xcb, 0x77, 0x93, 0x63, 0x83, 0x9e, 0x5d, 0xdf, 0x6c, 0xd9, 0xa6, 0xe2, + 0x58, 0x40, 0x4e, 0x83, 0x07, 0xde, 0x7c, 0x11, 0x46, 0x89, 0x1b, 0x24, 0x69, 0x3a, 0xb8, 0x4a, 0xd3, 0xee, 0x85, + 0xa7, 0x5d, 0xfe, 0x5d, 0x23, 0x5a, 0x48, 0x76, 0x29, 0xa9, 0x7e, 0x65, 0xbc, 0x62, 0xb2, 0x0d, 0x2d, 0x90, 0x31, + 0xb4, 0x9f, 0x95, 0x2b, 0x13, 0xbd, 0xad, 0x56, 0x26, 0x20, 0x67, 0xd5, 0xc9, 0x24, 0xb7, 0x58, 0x05, 0x29, 0x10, + 0x54, 0x78, 0xc5, 0x7a, 0x17, 0x92, 0x41, 0x2e, 0x30, 0x3d, 0x58, 0x99, 0x02, 0x0a, 0xbc, 0xdc, 0xc6, 0x7b, 0x5e, + 0xdc, 0xcd, 0x7b, 0xfe, 0x23, 0xd9, 0x87, 0xf7, 0xbc, 0xf8, 0xec, 0xbc, 0xe7, 0xcb, 0x6a, 0x40, 0x81, 0x8b, 0xb0, + 0xa7, 0x66, 0x56, 0x14, 0x40, 0x9a, 0x22, 0x0a, 0xd5, 0xfb, 0x32, 0xf9, 0xad, 0x9e, 0xdd, 0xa2, 0x37, 0x4a, 0x3e, + 0x4f, 0x94, 0x1b, 0xee, 0x5e, 0x6f, 0x83, 0xde, 0x77, 0x91, 0xfc, 0x3c, 0x99, 0xf4, 0x5e, 0x86, 0x52, 0x41, 0xf6, + 0xc4, 0x0d, 0x4c, 0x0b, 0x61, 0x15, 0xe9, 0x4d, 0x66, 0x02, 0x0c, 0x89, 0x27, 0x21, 0x2a, 0x1b, 0xf9, 0x7b, 0x8d, + 0x33, 0x43, 0xfc, 0x6e, 0x71, 0x08, 0x5a, 0xe6, 0xf9, 0x22, 0x62, 0x6f, 0x54, 0xd4, 0xa5, 0x53, 0x96, 0xf0, 0x60, + 0x59, 0xcf, 0x6f, 0xdf, 0x8c, 0xb5, 0x8b, 0x50, 0x4f, 0xbd, 0xf8, 0x6d, 0x39, 0xf2, 0x85, 0x10, 0x7e, 0xc9, 0xd3, + 0x49, 0xb9, 0x31, 0xbd, 0x14, 0xe0, 0x0e, 0x5f, 0x53, 0xf3, 0xd3, 0xc2, 0x4c, 0x3b, 0x72, 0x43, 0x9e, 0xe1, 0xba, + 0x42, 0x8c, 0xb9, 0x87, 0xf8, 0x86, 0x73, 0x79, 0x98, 0xb4, 0x1b, 0x03, 0x86, 0x8d, 0xa9, 0xb9, 0x37, 0x4e, 0x53, + 0xbd, 0x2b, 0x00, 0x21, 0x11, 0x5a, 0x76, 0x17, 0x13, 0x17, 0xe7, 0x57, 0x3f, 0x6e, 0x05, 0x45, 0x26, 0x4e, 0x17, + 0x60, 0x34, 0xc8, 0x0d, 0xa2, 0x38, 0xcc, 0x54, 0x85, 0xc0, 0x47, 0xc6, 0xa4, 0xd2, 0x84, 0xc0, 0xca, 0x4d, 0x36, + 0xc1, 0x2e, 0x2c, 0x48, 0xd5, 0xdb, 0x85, 0x80, 0x83, 0x56, 0x8f, 0x10, 0xde, 0x4f, 0xc8, 0xea, 0x08, 0xed, 0xf0, + 0x3a, 0xf8, 0x90, 0xaa, 0x19, 0xef, 0x87, 0xdb, 0xaf, 0x7f, 0x72, 0x00, 0x0d, 0xfa, 0x25, 0x49, 0xdc, 0x1d, 0xce, + 0x1a, 0xc0, 0x4a, 0xc4, 0x2b, 0xc3, 0x8a, 0x57, 0xca, 0x93, 0x8d, 0x08, 0x8d, 0x99, 0xb8, 0x0b, 0x13, 0x44, 0x3f, + 0x88, 0x7b, 0x39, 0xc6, 0x93, 0xa2, 0x70, 0x76, 0x97, 0x31, 0xe0, 0x46, 0x14, 0x2d, 0x20, 0xfe, 0xe9, 0x8e, 0x96, + 0x51, 0x1c, 0x46, 0x9d, 0x45, 0xe8, 0x05, 0x09, 0x8b, 0x52, 0x04, 0xd5, 0x25, 0xc2, 0x47, 0x80, 0xe7, 0x6a, 0x13, + 0x2e, 0xdc, 0x91, 0x97, 0xdc, 0x76, 0x6c, 0xce, 0x52, 0xd8, 0x5d, 0xce, 0x1d, 0xd8, 0xb5, 0xf5, 0x3b, 0x1c, 0x9a, + 0x4f, 0x91, 0xf1, 0x8b, 0xaa, 0xec, 0x8c, 0xbc, 0xcd, 0xbb, 0xd2, 0x5b, 0x0a, 0x0e, 0x0a, 0xec, 0x87, 0x1b, 0x99, + 0x53, 0xc0, 0xf2, 0xb0, 0xd4, 0xf6, 0x98, 0x4d, 0x0d, 0xc4, 0xda, 0x60, 0x7b, 0x20, 0xfe, 0x58, 0x2d, 0x5d, 0xb1, + 0xeb, 0x8b, 0x81, 0xe3, 0xd1, 0xf7, 0x19, 0x59, 0xc7, 0x85, 0x54, 0xda, 0xc6, 0x3e, 0x35, 0x87, 0x6c, 0x12, 0x46, + 0x8c, 0x12, 0xc9, 0x38, 0xed, 0xc5, 0x7a, 0xff, 0xee, 0x77, 0x4f, 0xbf, 0xbe, 0x9f, 0x20, 0x4c, 0x34, 0xd1, 0x99, + 0x7e, 0x47, 0x6f, 0x55, 0x7a, 0x06, 0xac, 0x21, 0x41, 0x7e, 0x44, 0x9e, 0x90, 0x10, 0x04, 0xa4, 0x36, 0x5e, 0xf7, + 0x22, 0xe4, 0x34, 0x2f, 0x62, 0xbe, 0x9b, 0x78, 0x37, 0x82, 0x67, 0x6c, 0x1e, 0x2d, 0xd6, 0x62, 0x8d, 0x91, 0xe0, + 0xdd, 0x63, 0x91, 0x4a, 0x43, 0x11, 0x8b, 0x54, 0x2e, 0xc6, 0x45, 0xea, 0x56, 0x66, 0x23, 0x42, 0x58, 0x96, 0x28, + 0x7d, 0x6b, 0xb1, 0x96, 0x49, 0x74, 0xde, 0x2c, 0xa3, 0xd4, 0xe5, 0xd8, 0xe3, 0x73, 0x6f, 0x3c, 0xf6, 0x59, 0x5a, + 0x58, 0xe8, 0xe2, 0x5a, 0x4a, 0xc0, 0xc9, 0xe0, 0xe0, 0x0e, 0xe3, 0xd0, 0x5f, 0x26, 0xac, 0x1e, 0x5c, 0x04, 0x9c, + 0x86, 0x9d, 0x03, 0x07, 0x7f, 0x17, 0xc7, 0xda, 0x02, 0x76, 0x1b, 0xb6, 0x89, 0xdd, 0x85, 0x54, 0x43, 0x66, 0xb3, + 0x38, 0x74, 0x78, 0x95, 0x0d, 0xda, 0xa8, 0x99, 0x88, 0x01, 0x64, 0x89, 0xb0, 0xb7, 0x62, 0x39, 0xbc, 0x2c, 0x4b, + 0xb7, 0x92, 0x15, 0xa5, 0xc5, 0xc9, 0xfc, 0x3e, 0x67, 0xec, 0x59, 0xfd, 0x19, 0x7b, 0x26, 0xce, 0xd8, 0xee, 0x9d, + 0xf9, 0x70, 0xe2, 0xc0, 0x7f, 0xdd, 0x7c, 0x42, 0x1d, 0x5b, 0x69, 0x2e, 0xd6, 0x8a, 0xb3, 0x58, 0x2b, 0x66, 0x63, + 0xb1, 0x56, 0xb0, 0x6b, 0xb4, 0x79, 0x35, 0xac, 0x86, 0x6e, 0xd8, 0x0a, 0x14, 0xc2, 0x1f, 0xbb, 0xf0, 0xca, 0x39, + 0x84, 0x77, 0xd0, 0xaa, 0x55, 0x7d, 0xd7, 0xd8, 0x7d, 0xd4, 0xe9, 0x2c, 0x09, 0xa4, 0xad, 0x5b, 0x89, 0x3b, 0x1c, + 0xb2, 0x71, 0x67, 0x12, 0x8e, 0x96, 0xf1, 0xbf, 0xf8, 0xf8, 0x39, 0x10, 0x77, 0x22, 0x82, 0x52, 0x3f, 0xa2, 0x29, + 0x48, 0x0f, 0x6f, 0x98, 0xe8, 0x61, 0x93, 0xad, 0x53, 0x87, 0xf2, 0x22, 0x35, 0xac, 0xc3, 0x9a, 0x4d, 0x5e, 0x0f, + 0xe8, 0xdf, 0x6d, 0x95, 0xb6, 0xa3, 0x98, 0x4f, 0x00, 0xcb, 0x4e, 0x70, 0xdc, 0x1f, 0x1a, 0x7c, 0x35, 0xed, 0x76, + 0xfd, 0x70, 0x2f, 0xc5, 0x97, 0xae, 0x04, 0x51, 0xe1, 0x74, 0x8b, 0xfb, 0xe7, 0xee, 0xee, 0x75, 0xdb, 0x1e, 0xa9, + 0xf4, 0xba, 0x83, 0x20, 0xe4, 0x75, 0xf7, 0xc4, 0xf2, 0x0f, 0x9f, 0x1d, 0xc2, 0x7f, 0xc4, 0xd5, 0xff, 0x23, 0xa9, + 0x63, 0xd4, 0x5f, 0x26, 0x05, 0x46, 0x9d, 0x58, 0x25, 0x64, 0xc4, 0xf7, 0xaf, 0x3f, 0x99, 0xdc, 0xaf, 0xc1, 0xde, + 0xb5, 0xc9, 0x5c, 0xbc, 0x5c, 0xfb, 0x79, 0x18, 0xfa, 0xcc, 0x0d, 0xaa, 0xd5, 0x05, 0x78, 0xc8, 0xf7, 0x2f, 0xe9, + 0x41, 0x23, 0x71, 0x8f, 0x20, 0x4b, 0x45, 0x15, 0xdb, 0xd0, 0x55, 0xe2, 0x6c, 0xdb, 0x55, 0xe2, 0xdd, 0xdd, 0x57, + 0x89, 0x6f, 0xf7, 0xba, 0x4a, 0xbc, 0xfb, 0xec, 0x57, 0x89, 0xb3, 0xea, 0x55, 0xe2, 0x2c, 0x14, 0x3e, 0x42, 0xc6, + 0xeb, 0x25, 0xff, 0xf9, 0x9e, 0x8c, 0x80, 0xde, 0x85, 0xbd, 0x96, 0x4d, 0xb9, 0x8e, 0x2e, 0x7e, 0xf3, 0xc5, 0x02, + 0x37, 0xe2, 0x3b, 0x34, 0x99, 0xcf, 0xaf, 0x16, 0x1c, 0xb3, 0xe3, 0x77, 0xa4, 0x62, 0x3f, 0x0c, 0xa6, 0x3f, 0x82, + 0x11, 0x18, 0x88, 0x03, 0x23, 0xe9, 0x85, 0x17, 0xff, 0x18, 0x2e, 0x96, 0x8b, 0x37, 0xd0, 0xd7, 0x7b, 0x2f, 0xf6, + 0x86, 0x3e, 0xcb, 0x82, 0x4b, 0x91, 0x89, 0x3f, 0x97, 0xad, 0x83, 0x57, 0x8d, 0xf8, 0xe9, 0xae, 0xc5, 0x4f, 0xf4, + 0xbb, 0xe1, 0xbf, 0xc9, 0x77, 0x40, 0xad, 0xbf, 0x88, 0x08, 0xb5, 0xb1, 0x34, 0xe8, 0xfb, 0x5f, 0x46, 0xce, 0x42, + 0xbd, 0x66, 0x96, 0xc2, 0xa6, 0x73, 0x6b, 0x3f, 0xac, 0xdc, 0xcf, 0xeb, 0xa5, 0x6e, 0x64, 0xb1, 0xb7, 0xab, 0xe2, + 0xfc, 0x79, 0xb8, 0x8c, 0xd9, 0x38, 0x5c, 0x05, 0xaa, 0x11, 0x70, 0x47, 0x04, 0x4a, 0x5f, 0x9c, 0xb5, 0xf9, 0x6f, + 0xc8, 0x73, 0x70, 0x8e, 0x8c, 0x32, 0x84, 0xe8, 0xb1, 0x16, 0x00, 0x43, 0x93, 0x4c, 0xdb, 0x4c, 0x9c, 0xa2, 0x9a, + 0xa5, 0x37, 0x7e, 0xa0, 0x69, 0x61, 0xef, 0x7e, 0x2d, 0x85, 0x39, 0x6a, 0x68, 0x71, 0xa9, 0x70, 0xac, 0x05, 0x42, + 0xb8, 0x28, 0x02, 0x60, 0xd6, 0x2c, 0x1c, 0x7f, 0x43, 0x91, 0xa3, 0xf2, 0xb7, 0x10, 0x8a, 0x28, 0x5d, 0xf2, 0xf5, + 0xe0, 0xe1, 0x20, 0xe9, 0xf1, 0x85, 0x04, 0xc6, 0xb7, 0x37, 0x2c, 0xf2, 0xdd, 0x5b, 0x4d, 0x4f, 0xc3, 0xe0, 0x7b, + 0x00, 0xc0, 0xcb, 0x70, 0x15, 0xc8, 0x15, 0x30, 0x4b, 0x6b, 0xcd, 0x5e, 0xaa, 0x0d, 0x5c, 0x0a, 0x8e, 0xbc, 0xd2, + 0x08, 0x3c, 0x6b, 0xe1, 0x4e, 0xd9, 0x7f, 0x19, 0xf4, 0xef, 0xdf, 0xf5, 0xd4, 0x78, 0x17, 0x66, 0x1f, 0xfa, 0x69, + 0xb1, 0xc7, 0x67, 0x1e, 0x3f, 0x7e, 0xb0, 0x7d, 0xda, 0xda, 0xc8, 0x67, 0x6e, 0x24, 0x46, 0x51, 0xd3, 0x5a, 0xdf, + 0x7a, 0x0a, 0x60, 0x14, 0x17, 0xe1, 0x72, 0x34, 0x43, 0x67, 0x9e, 0xcf, 0x37, 0xdf, 0x04, 0xfa, 0x64, 0xf1, 0xa5, + 0x7d, 0x95, 0x4d, 0xbd, 0x54, 0x94, 0x43, 0x01, 0xbf, 0xff, 0x0a, 0x32, 0x6f, 0xfc, 0x89, 0x60, 0xa8, 0xee, 0x9a, + 0x2c, 0x0e, 0xc8, 0xbd, 0x36, 0x6f, 0xd7, 0x03, 0x57, 0x7d, 0x8a, 0x69, 0x29, 0x94, 0x74, 0xf5, 0x48, 0x26, 0x2d, + 0x03, 0x4d, 0x8e, 0x1f, 0xbf, 0x2d, 0x34, 0xbe, 0xf8, 0x0a, 0xb3, 0xe8, 0x9a, 0xce, 0x9d, 0x29, 0x0d, 0xc6, 0xb1, + 0x55, 0x09, 0xc9, 0x70, 0x13, 0x4b, 0x86, 0xe8, 0xab, 0xfc, 0x6e, 0xee, 0x05, 0x06, 0xa6, 0x7f, 0xab, 0xbe, 0x71, + 0xd7, 0x90, 0x00, 0x09, 0x90, 0x5b, 0xf9, 0x15, 0x14, 0x1a, 0x72, 0x08, 0x01, 0xc8, 0xf1, 0xac, 0xd6, 0x42, 0x42, + 0x68, 0x03, 0x07, 0x5f, 0x28, 0x8a, 0xa2, 0x64, 0xd7, 0x08, 0x25, 0xbb, 0x47, 0x60, 0x39, 0x5e, 0x07, 0x40, 0x5b, + 0x92, 0x2e, 0xd6, 0x54, 0x02, 0x37, 0x03, 0x34, 0xaa, 0x12, 0x05, 0x3c, 0xc6, 0x7f, 0xcb, 0x16, 0x05, 0xe2, 0x42, + 0x0f, 0xf1, 0xd9, 0xdd, 0x08, 0x52, 0x01, 0x75, 0x14, 0xbc, 0xb0, 0xe3, 0x5b, 0x2e, 0x09, 0x56, 0x6c, 0x7a, 0x1c, + 0x74, 0x59, 0x7d, 0x30, 0xf8, 0x40, 0xc2, 0x82, 0xa0, 0x75, 0x28, 0xe5, 0x76, 0x32, 0x58, 0x0d, 0x6e, 0xc4, 0x7b, + 0xd1, 0x3a, 0x99, 0xb3, 0x60, 0xa9, 0x62, 0x32, 0x68, 0x0c, 0xce, 0x0f, 0x75, 0x5e, 0x12, 0xb3, 0x05, 0xd8, 0xa6, + 0xbe, 0xe5, 0x8c, 0x68, 0x61, 0xcc, 0x51, 0xaa, 0x6b, 0x8c, 0xb8, 0x57, 0x7c, 0xcc, 0x71, 0x5b, 0x99, 0x42, 0xf0, + 0x25, 0x0d, 0x8b, 0xd8, 0x9c, 0xc7, 0xc1, 0x40, 0x4e, 0x81, 0x02, 0x1e, 0x72, 0x71, 0x91, 0x00, 0xbb, 0xe6, 0x96, + 0x17, 0x2d, 0xd3, 0xc8, 0xb8, 0x25, 0x41, 0x51, 0xa4, 0x57, 0xbb, 0xe1, 0xe3, 0x84, 0x88, 0xc4, 0x5b, 0xfb, 0x19, + 0x55, 0xfa, 0xd9, 0x32, 0xe9, 0x0f, 0xec, 0x96, 0x08, 0x09, 0x81, 0xea, 0x03, 0xbb, 0x05, 0x8b, 0xb1, 0x57, 0x20, + 0x4d, 0x51, 0x77, 0xa0, 0x6b, 0x03, 0x72, 0xfc, 0x8d, 0x20, 0x4a, 0xf5, 0x8e, 0x03, 0x64, 0xa7, 0x3b, 0xb0, 0x38, + 0x82, 0x38, 0x30, 0xe2, 0xae, 0x38, 0xc4, 0xdc, 0x8d, 0x51, 0xab, 0x85, 0xb1, 0x59, 0x73, 0x34, 0xf4, 0x27, 0x8e, + 0x6d, 0x1f, 0x54, 0xea, 0x83, 0x20, 0xbb, 0xae, 0xb6, 0x6e, 0x24, 0x3d, 0xc7, 0x36, 0xbd, 0x27, 0x56, 0xa3, 0x5b, + 0xa1, 0xd1, 0x52, 0x12, 0x89, 0x01, 0x8a, 0xbf, 0xfa, 0x8f, 0x36, 0x5a, 0xe5, 0x40, 0xea, 0x65, 0xb7, 0x40, 0x1c, + 0x5b, 0xca, 0xe5, 0x5f, 0x83, 0x2a, 0xe9, 0xa7, 0x14, 0x16, 0x94, 0xd0, 0x74, 0x00, 0x69, 0x90, 0x34, 0x38, 0x46, + 0x7f, 0x51, 0x9e, 0x2a, 0x1a, 0x1d, 0x1f, 0x5d, 0x1f, 0x74, 0x05, 0x46, 0x11, 0x7e, 0xf3, 0x72, 0x07, 0xa5, 0x2f, + 0xc6, 0x65, 0x0c, 0xc7, 0x13, 0xae, 0xb0, 0x5c, 0xa3, 0xb7, 0x93, 0x5b, 0xc0, 0xfe, 0xb7, 0x90, 0x4f, 0x6b, 0x08, + 0x81, 0x9f, 0xa0, 0x06, 0x24, 0x4d, 0xbb, 0xb3, 0x43, 0x88, 0xd3, 0x27, 0x77, 0x57, 0x24, 0x92, 0xfb, 0x77, 0x86, + 0x44, 0x07, 0x75, 0x68, 0x59, 0x7f, 0xf5, 0xe4, 0xee, 0x9e, 0x5d, 0xb2, 0x60, 0x5c, 0xec, 0xb0, 0x44, 0xbf, 0xf6, + 0xef, 0xae, 0x80, 0x51, 0x20, 0x9b, 0x60, 0x58, 0x83, 0x51, 0xd2, 0x30, 0xc0, 0xcd, 0x4f, 0xc7, 0xcd, 0xdb, 0x8b, + 0x8b, 0xc1, 0x06, 0x14, 0x0a, 0x3c, 0x6b, 0x26, 0x09, 0xc5, 0x21, 0x65, 0x15, 0x86, 0xd5, 0xd0, 0x04, 0x23, 0xba, + 0x75, 0x27, 0x26, 0xc2, 0x87, 0x21, 0x6f, 0xe3, 0xf1, 0x24, 0x14, 0xfb, 0x4a, 0xad, 0xbd, 0xbb, 0xa5, 0xd6, 0xc9, + 0x5d, 0x52, 0x6b, 0x72, 0x19, 0x27, 0x7b, 0xa0, 0xcc, 0x75, 0x5e, 0x30, 0xe7, 0x72, 0xf0, 0x81, 0x82, 0xa8, 0x1b, + 0x3d, 0xcc, 0x45, 0xab, 0x4a, 0x6f, 0xe4, 0x95, 0x80, 0xe2, 0x6f, 0xe9, 0x82, 0x22, 0x14, 0xea, 0xb2, 0x6c, 0xfc, + 0x2c, 0x97, 0x8d, 0xd3, 0xad, 0x26, 0x77, 0x16, 0x16, 0xdc, 0xbf, 0xe4, 0x88, 0x9f, 0xdd, 0x0e, 0x72, 0x87, 0xfc, + 0x7c, 0xa4, 0x92, 0x8b, 0x79, 0x7e, 0xd1, 0x90, 0x02, 0x17, 0x88, 0x5b, 0x46, 0x31, 0x7e, 0x41, 0xb1, 0x6a, 0xee, + 0x61, 0x9e, 0x97, 0x83, 0xd4, 0x1d, 0x87, 0x9c, 0x15, 0xcb, 0xdb, 0xa6, 0xe8, 0x62, 0x2c, 0xbf, 0x96, 0x36, 0x49, + 0xe6, 0x0b, 0x4c, 0x00, 0x16, 0x62, 0xfa, 0x92, 0x5e, 0x3b, 0xb3, 0x81, 0xc0, 0x41, 0xd6, 0x84, 0x2e, 0xb8, 0x5b, + 0x3a, 0x4f, 0x89, 0x12, 0x73, 0xd5, 0xb5, 0x83, 0xd4, 0x9d, 0x34, 0xc1, 0xb2, 0x3c, 0x02, 0x61, 0x7d, 0x25, 0x49, + 0x10, 0x3a, 0xb6, 0x62, 0x77, 0x6b, 0x18, 0x00, 0xa4, 0xff, 0xe5, 0x67, 0xce, 0x0a, 0x80, 0x24, 0x52, 0xb1, 0x65, + 0x9d, 0x3f, 0x1e, 0x62, 0x93, 0xcc, 0xdb, 0xb0, 0x6a, 0xf5, 0x9b, 0x24, 0xef, 0xd9, 0x70, 0x47, 0xe1, 0xa2, 0x38, + 0x9f, 0xd7, 0xe8, 0x88, 0x71, 0xf0, 0x5d, 0x16, 0x2d, 0x03, 0xcc, 0x7f, 0x67, 0x26, 0x91, 0x3b, 0xfa, 0xb0, 0x91, + 0xbe, 0xc7, 0x45, 0xa2, 0x20, 0x2e, 0x2e, 0x2a, 0x15, 0xba, 0x2e, 0xa6, 0x8b, 0x60, 0x1d, 0xab, 0x11, 0x4b, 0x82, + 0x9a, 0xce, 0x43, 0xbb, 0xe9, 0x3e, 0x9b, 0x1c, 0x96, 0xe4, 0xa7, 0x8d, 0x56, 0x51, 0xba, 0x9e, 0x8d, 0x63, 0x1e, + 0xfe, 0xc2, 0x43, 0x2a, 0xfc, 0xf1, 0x9f, 0x8e, 0xf9, 0x37, 0x4b, 0x6b, 0xf4, 0x29, 0x43, 0x80, 0xf6, 0x05, 0xc5, + 0xb4, 0xac, 0xa6, 0xa9, 0x94, 0x6c, 0x1b, 0xd6, 0xc4, 0xf3, 0x7d, 0xd3, 0x07, 0xdb, 0xc6, 0xcd, 0x27, 0x4d, 0x0f, + 0xfb, 0x59, 0x42, 0xa2, 0xa2, 0x4f, 0xe8, 0xa7, 0xb8, 0x53, 0x92, 0xd9, 0x72, 0x3e, 0xdc, 0xc8, 0x82, 0x72, 0x49, + 0x7e, 0x5e, 0x95, 0x99, 0xcb, 0x9f, 0x9d, 0x4c, 0x26, 0x45, 0xa9, 0xb1, 0xad, 0x1c, 0xa2, 0xe4, 0xf7, 0xa1, 0x6d, + 0xdb, 0x65, 0xf8, 0x6e, 0x3b, 0x28, 0x74, 0x30, 0x4c, 0x14, 0xc2, 0xb7, 0xef, 0xde, 0x53, 0x7f, 0xd0, 0x68, 0xa9, + 0xab, 0x6d, 0xe7, 0x91, 0xb6, 0xda, 0x7f, 0xc4, 0x50, 0x10, 0x35, 0xdc, 0x75, 0xfc, 0xab, 0x7b, 0x65, 0x47, 0x4f, + 0xe5, 0x03, 0x7c, 0xbf, 0xc6, 0x77, 0xec, 0xf5, 0x3d, 0x9a, 0x6e, 0xdb, 0xde, 0xa9, 0x95, 0x93, 0xdd, 0x82, 0xcd, + 0x52, 0x97, 0x2c, 0x95, 0xbc, 0x84, 0xcd, 0xe3, 0xce, 0x88, 0xa1, 0x82, 0xd4, 0x92, 0xa8, 0x2d, 0x5a, 0xf5, 0x98, + 0x53, 0xb0, 0xe3, 0x72, 0x04, 0x1e, 0xb6, 0x15, 0x54, 0x56, 0x55, 0x34, 0x6b, 0xe2, 0x23, 0x48, 0xc5, 0x36, 0x55, + 0x85, 0x13, 0x6e, 0xd3, 0x96, 0xfd, 0x97, 0x42, 0x3d, 0x05, 0xb8, 0xd3, 0x8d, 0xb0, 0x36, 0x21, 0xe5, 0x09, 0xfe, + 0x9d, 0x29, 0xe7, 0x9e, 0x2d, 0xd6, 0x45, 0xe3, 0xae, 0x36, 0xa8, 0x9b, 0x72, 0x52, 0x46, 0xa3, 0xae, 0x43, 0x7d, + 0x99, 0x09, 0xd0, 0x44, 0xb6, 0x6e, 0x01, 0x0b, 0x9a, 0x42, 0x5a, 0xde, 0x1a, 0xdd, 0x18, 0x5e, 0x67, 0x61, 0xe7, + 0xe5, 0xf2, 0x7d, 0xfc, 0x05, 0x09, 0x4a, 0x21, 0xea, 0xf9, 0x5f, 0x8c, 0xa7, 0x6d, 0x54, 0xee, 0x15, 0xb6, 0x2a, + 0x9a, 0xca, 0xe0, 0x1e, 0x10, 0x37, 0x52, 0x65, 0x19, 0xf9, 0x26, 0xe5, 0xac, 0xd5, 0xf4, 0x4d, 0x75, 0xde, 0xdb, + 0xbb, 0x77, 0x5a, 0xa0, 0xd7, 0xa8, 0x82, 0x6a, 0x2f, 0xd5, 0x5e, 0x59, 0x87, 0x2d, 0xc6, 0x09, 0x2b, 0x00, 0x8e, + 0x34, 0x0a, 0x1a, 0x0d, 0x29, 0x25, 0xdc, 0x47, 0x93, 0xce, 0xde, 0xca, 0xc8, 0x5a, 0xcc, 0x13, 0xbb, 0xab, 0xaf, + 0x42, 0x7d, 0x0b, 0xcd, 0x20, 0xc0, 0x8e, 0x63, 0x27, 0x7c, 0x36, 0x61, 0xc7, 0xc8, 0xe8, 0xca, 0xc1, 0x1d, 0x84, + 0xa7, 0xd4, 0xa4, 0x58, 0xe8, 0x74, 0x4a, 0x51, 0x97, 0xf0, 0x6d, 0xad, 0xf0, 0xfe, 0xa2, 0x20, 0x8d, 0xe7, 0x9e, + 0xa8, 0x0d, 0x7d, 0xaf, 0xda, 0x73, 0x2f, 0xd8, 0xbf, 0xae, 0xbb, 0xde, 0xbb, 0x2e, 0x30, 0x87, 0x7b, 0x57, 0x06, + 0xee, 0x92, 0xac, 0x94, 0x92, 0xde, 0xb7, 0x92, 0xf2, 0x40, 0x8e, 0xa2, 0xa4, 0x62, 0x2b, 0xba, 0xd1, 0xff, 0xb0, + 0xec, 0x0d, 0x4e, 0x4e, 0xd7, 0x73, 0x5f, 0xb9, 0x61, 0x11, 0xe4, 0xef, 0xee, 0xa9, 0x8e, 0x65, 0xab, 0x0a, 0xc6, + 0x04, 0xf2, 0x82, 0x69, 0x4f, 0xfd, 0xe9, 0xe2, 0xb5, 0xd9, 0x56, 0x4f, 0xc1, 0x1c, 0xe3, 0x66, 0x8a, 0x2c, 0xee, + 0x99, 0x7b, 0xcb, 0xa2, 0xeb, 0x86, 0xaa, 0x60, 0x9a, 0x6e, 0x62, 0x6e, 0xb1, 0x4c, 0x69, 0xa8, 0x7b, 0x64, 0x83, + 0x55, 0x6e, 0x3c, 0xb6, 0x7a, 0x1e, 0xae, 0x7b, 0x2a, 0x20, 0x56, 0xa7, 0xd1, 0x56, 0x9c, 0xc6, 0xa1, 0x75, 0xd4, + 0x56, 0xfb, 0x5f, 0x28, 0xca, 0xc9, 0x98, 0x4d, 0xe2, 0x3e, 0x8a, 0x63, 0x4e, 0x90, 0x1f, 0xa4, 0xdf, 0x8a, 0x62, + 0x8d, 0xfc, 0xd8, 0x74, 0x94, 0x0d, 0x7f, 0x54, 0x14, 0x40, 0x46, 0x1d, 0xe5, 0xe1, 0xa4, 0x31, 0x39, 0x9c, 0x3c, + 0xeb, 0xf2, 0xe2, 0xf4, 0x8b, 0x42, 0x75, 0x83, 0xfe, 0x6d, 0x48, 0xcd, 0xe2, 0x24, 0x0a, 0x3f, 0x30, 0xce, 0x4b, + 0x2a, 0x99, 0xa0, 0xa8, 0xdc, 0xb4, 0x51, 0xfd, 0x92, 0xd3, 0x1e, 0x8e, 0x26, 0x8d, 0xbc, 0x3a, 0x8e, 0xf1, 0x20, + 0x1b, 0xe4, 0xc9, 0x81, 0x18, 0xfa, 0x89, 0x0c, 0x26, 0xc7, 0xac, 0x03, 0x94, 0xa3, 0xf2, 0x39, 0x4e, 0xc5, 0xfc, + 0x4e, 0x20, 0xd9, 0x4a, 0xee, 0xd2, 0x10, 0x63, 0xb3, 0x9e, 0xfa, 0xbd, 0xd3, 0x68, 0x1b, 0x8e, 0x73, 0x64, 0x1d, + 0xb5, 0x47, 0xb6, 0x71, 0x68, 0x1d, 0x9a, 0x4d, 0xeb, 0xc8, 0x68, 0x9b, 0x6d, 0xa3, 0xfd, 0x4d, 0x7b, 0x64, 0x1e, + 0x5a, 0x87, 0x86, 0x6d, 0xb6, 0xa1, 0xd0, 0x6c, 0x9b, 0xed, 0x1b, 0xf3, 0xb0, 0x3d, 0xb2, 0xb1, 0xb4, 0x61, 0xb5, + 0x5a, 0xa6, 0x63, 0x5b, 0xad, 0x96, 0xd1, 0xb2, 0x8e, 0x8e, 0x4c, 0xa7, 0x69, 0x1d, 0x1d, 0x9d, 0xb5, 0xda, 0x56, + 0x13, 0xde, 0x35, 0x9b, 0xa3, 0xa6, 0xe5, 0x38, 0x26, 0xfc, 0x65, 0xb4, 0xad, 0x06, 0xfd, 0x70, 0x1c, 0xab, 0xe9, + 0x18, 0xb6, 0xdf, 0x6a, 0x58, 0x47, 0xcf, 0x0c, 0xfc, 0x1b, 0xab, 0x19, 0xf8, 0x17, 0x74, 0x63, 0x3c, 0xb3, 0x1a, + 0x47, 0xf4, 0x0b, 0x3b, 0xbc, 0x39, 0x6c, 0xff, 0x43, 0x3d, 0xd8, 0x3a, 0x07, 0x87, 0xe6, 0xd0, 0x6e, 0x59, 0xcd, + 0xa6, 0x71, 0xe8, 0x58, 0xed, 0xe6, 0xcc, 0x3c, 0x6c, 0x58, 0x47, 0xc7, 0x23, 0xd3, 0xb1, 0x8e, 0x8f, 0x0d, 0xdb, + 0x6c, 0x5a, 0x0d, 0xc3, 0xb1, 0x0e, 0x9b, 0xf8, 0xa3, 0x69, 0x35, 0x6e, 0x8e, 0x9f, 0x59, 0x47, 0xad, 0xd9, 0x91, + 0x75, 0xf8, 0xfe, 0xb0, 0x6d, 0x35, 0x9a, 0xb3, 0xe6, 0x91, 0xd5, 0x38, 0xbe, 0x39, 0xb2, 0x0e, 0x67, 0x66, 0xe3, + 0x68, 0x67, 0x4b, 0xa7, 0x61, 0x01, 0x8c, 0xf0, 0x35, 0xbc, 0x30, 0xf8, 0x0b, 0xf8, 0x33, 0xc3, 0xb6, 0x7f, 0x60, + 0x37, 0x71, 0xb5, 0xe9, 0x33, 0xab, 0x7d, 0x3c, 0xa2, 0xea, 0x50, 0x60, 0x8a, 0x1a, 0xd0, 0xe4, 0xc6, 0xa4, 0xcf, + 0x62, 0x77, 0xa6, 0xe8, 0x48, 0xfc, 0xe1, 0x1f, 0xbb, 0x31, 0xe1, 0xc3, 0xf4, 0xdd, 0x3f, 0xb5, 0x9f, 0x6c, 0xc9, + 0x4f, 0x0e, 0xa6, 0xb4, 0xf5, 0xa7, 0xfd, 0x2f, 0x28, 0xb3, 0xf3, 0xc0, 0xf8, 0x65, 0x9b, 0x52, 0xf2, 0x9f, 0x77, + 0x2b, 0x25, 0x9f, 0x2f, 0xf7, 0x51, 0x4a, 0xfe, 0xf3, 0xb3, 0x2b, 0x25, 0x7f, 0x29, 0xfb, 0xd6, 0xbc, 0x2e, 0x27, + 0xa0, 0xfc, 0x76, 0x53, 0x16, 0x39, 0x04, 0xae, 0x76, 0xf9, 0xc3, 0xf2, 0x0a, 0x82, 0xca, 0xbe, 0x0e, 0x7b, 0xcf, + 0x97, 0x05, 0x83, 0xcf, 0x10, 0x70, 0xec, 0xeb, 0x90, 0x70, 0xec, 0xfb, 0x65, 0x0f, 0xac, 0xcc, 0x38, 0x9b, 0xe3, + 0x8d, 0xcd, 0x99, 0xeb, 0x4f, 0x32, 0x16, 0x09, 0x4a, 0xba, 0x58, 0x0c, 0xce, 0x74, 0x40, 0x9e, 0xe1, 0x26, 0xb3, + 0x9c, 0x07, 0x31, 0x58, 0x04, 0x83, 0x25, 0xc7, 0x24, 0x4a, 0x4b, 0x8d, 0x2d, 0x11, 0x86, 0xf7, 0x9a, 0x7b, 0x59, + 0x6d, 0x7d, 0x8f, 0x06, 0xc0, 0xf5, 0xbd, 0x3b, 0xd5, 0x7e, 0x15, 0xb0, 0xac, 0x13, 0x06, 0xd2, 0xc0, 0xed, 0xd7, + 0xbd, 0x2f, 0x9a, 0xe1, 0x96, 0x0c, 0xaf, 0xb7, 0x8f, 0x14, 0x46, 0x52, 0x6e, 0xef, 0x14, 0xcd, 0x78, 0xef, 0x9a, + 0x66, 0xcd, 0xe7, 0x0b, 0xcd, 0x77, 0xd8, 0x10, 0x67, 0x1d, 0x97, 0x41, 0xb5, 0x29, 0xf0, 0x69, 0xf5, 0x00, 0xc9, + 0x2f, 0xa8, 0xb9, 0xa1, 0x71, 0xce, 0xa9, 0xda, 0x0a, 0xf2, 0x3b, 0xb6, 0xf4, 0xae, 0xd0, 0xa7, 0x6c, 0x9c, 0xfc, + 0x64, 0x83, 0xf7, 0x0a, 0xef, 0x17, 0xe0, 0x44, 0x39, 0xc7, 0x33, 0x0c, 0x65, 0x38, 0x6f, 0xa4, 0x7e, 0x49, 0x1a, + 0x91, 0xce, 0x9c, 0x4d, 0x95, 0x17, 0xdd, 0xea, 0x96, 0xe0, 0xb0, 0xb9, 0xe0, 0x82, 0xf0, 0xf3, 0xe4, 0x04, 0x90, + 0x92, 0xa3, 0x06, 0xfa, 0x39, 0xec, 0xea, 0x4c, 0xd4, 0x7b, 0x08, 0x9b, 0x98, 0x67, 0x03, 0x50, 0xe4, 0x38, 0x67, + 0x9b, 0x89, 0x1f, 0xba, 0x49, 0x07, 0xd9, 0x34, 0x89, 0xe5, 0x6d, 0xa0, 0xc7, 0x42, 0x77, 0x87, 0x31, 0x9d, 0xdc, + 0x31, 0xef, 0x04, 0x3d, 0x1f, 0x76, 0xd9, 0xdf, 0x65, 0x0e, 0x67, 0x9b, 0x82, 0x39, 0x8a, 0xd3, 0x3a, 0x36, 0x9c, + 0x23, 0xc3, 0x3a, 0x6e, 0xe9, 0xa9, 0x38, 0x70, 0x72, 0x97, 0x05, 0x80, 0x80, 0x03, 0x44, 0x36, 0x4c, 0x2f, 0xf0, + 0x12, 0xcf, 0xf5, 0x53, 0xe0, 0x87, 0x8b, 0x97, 0x94, 0x7f, 0x2e, 0xe3, 0x04, 0xe6, 0x28, 0x98, 0x5e, 0x74, 0xfe, + 0x30, 0x87, 0x2c, 0x59, 0x31, 0x16, 0x6c, 0x31, 0x8c, 0x29, 0xfb, 0x92, 0xfc, 0x7e, 0x96, 0xf5, 0x29, 0x59, 0xad, + 0x0d, 0x93, 0x80, 0xef, 0x0f, 0xe1, 0xf8, 0x90, 0x0e, 0x8c, 0x6f, 0xb6, 0x21, 0xdc, 0x9f, 0xee, 0x46, 0xb8, 0x09, + 0xdb, 0x07, 0xe1, 0xfe, 0xf4, 0xd9, 0x11, 0xee, 0x37, 0x32, 0xc2, 0x2d, 0xf8, 0x0f, 0xe6, 0x1a, 0xa6, 0x73, 0x7c, + 0xd6, 0x20, 0x27, 0xd7, 0x53, 0xf5, 0x80, 0x18, 0x78, 0x55, 0xcf, 0x13, 0xd7, 0xfd, 0xad, 0x90, 0x22, 0x1c, 0x05, + 0xa0, 0x98, 0xef, 0x89, 0xd2, 0x11, 0x7b, 0xe0, 0xea, 0x96, 0xa5, 0x24, 0x66, 0x2b, 0xe5, 0x4d, 0x90, 0xf8, 0xd6, + 0x3b, 0x7e, 0x8f, 0x04, 0x85, 0xee, 0xeb, 0x30, 0x9a, 0xbb, 0x18, 0x77, 0x5c, 0xd5, 0xc1, 0x9d, 0x0e, 0x1e, 0x6c, + 0xf0, 0x0e, 0x1e, 0x85, 0xc1, 0x38, 0xd3, 0x4a, 0xb2, 0xde, 0x25, 0x71, 0xdc, 0xea, 0x2d, 0x73, 0x23, 0xd5, 0xa0, + 0xd7, 0xb0, 0xb8, 0x4f, 0x9a, 0xf6, 0x93, 0xc6, 0xe1, 0x93, 0x23, 0x1b, 0xfe, 0x77, 0x58, 0x33, 0x35, 0x78, 0xc5, + 0x79, 0x18, 0x40, 0x76, 0x63, 0x51, 0x73, 0x5b, 0xb5, 0x15, 0x63, 0x1f, 0xf2, 0x5a, 0xc7, 0xf5, 0x95, 0xc6, 0xee, + 0x6d, 0x5e, 0xa7, 0xb6, 0xc6, 0x2c, 0x5c, 0x4a, 0xc3, 0xaa, 0x19, 0x8d, 0x17, 0x2c, 0x41, 0xce, 0x2e, 0xd5, 0x90, + 0x5f, 0xf3, 0xe9, 0xe6, 0xf3, 0x62, 0xcd, 0xf4, 0x2a, 0x4f, 0xa1, 0x2e, 0x32, 0xe9, 0xdd, 0x09, 0x41, 0xae, 0xa2, + 0xb4, 0x31, 0x11, 0x05, 0xa6, 0x38, 0x82, 0x34, 0x14, 0x59, 0xe2, 0x6b, 0x97, 0x16, 0x28, 0x89, 0x96, 0xc1, 0x48, + 0xc3, 0x9f, 0xee, 0x30, 0xd6, 0xbc, 0x83, 0xc8, 0xe2, 0x1f, 0xd6, 0x71, 0xd5, 0xdc, 0xbe, 0x9d, 0xe7, 0x9b, 0x8d, + 0xc5, 0xaa, 0xb8, 0x4f, 0x12, 0x23, 0x42, 0x3d, 0x36, 0x2d, 0xad, 0xd9, 0x73, 0x9f, 0x64, 0x0d, 0x9f, 0x24, 0x46, + 0xf0, 0x14, 0x74, 0x9f, 0x3d, 0xfb, 0xf1, 0x63, 0xaa, 0xf5, 0xa0, 0x27, 0xa6, 0x75, 0x3a, 0xca, 0xc3, 0x55, 0x2b, + 0xee, 0x34, 0xa4, 0x88, 0xd5, 0x9d, 0x91, 0x11, 0x3e, 0x7d, 0xda, 0xef, 0x39, 0x3a, 0xe6, 0x32, 0x4f, 0x45, 0x0c, + 0xd0, 0x00, 0x53, 0xd3, 0x9d, 0xed, 0x67, 0x68, 0xa4, 0xd7, 0xba, 0xd2, 0x2e, 0xe0, 0xce, 0x64, 0x0b, 0x77, 0x04, + 0x8e, 0xbd, 0x20, 0x6d, 0x2d, 0x19, 0x14, 0xb8, 0xc2, 0xe0, 0x47, 0xd4, 0xc9, 0x6e, 0x5d, 0x4d, 0xcb, 0xb6, 0x6c, + 0x35, 0x6b, 0x38, 0xf1, 0xa6, 0xbd, 0x4d, 0x98, 0xb8, 0x90, 0x00, 0xdc, 0x0f, 0xa7, 0xe0, 0x47, 0x97, 0x78, 0x89, + 0x0f, 0xd9, 0xa4, 0xc1, 0xa1, 0x6e, 0x4e, 0xf7, 0xf2, 0x94, 0x7b, 0x37, 0xb8, 0x11, 0x64, 0x2c, 0x8d, 0x6e, 0x85, + 0x2b, 0x2e, 0x46, 0x50, 0xfd, 0x1e, 0x88, 0xa1, 0xa6, 0x6a, 0x20, 0x1b, 0x60, 0x51, 0x6c, 0xca, 0xde, 0x42, 0x1d, + 0x05, 0xda, 0xe8, 0x2a, 0x9f, 0xc4, 0x24, 0x72, 0xe7, 0x90, 0x52, 0x6f, 0x93, 0x1a, 0x1c, 0xd3, 0xaa, 0x1c, 0xd5, + 0x2a, 0xce, 0xb3, 0x23, 0x43, 0x69, 0x38, 0x86, 0x62, 0x03, 0xba, 0x55, 0x53, 0x63, 0x93, 0x5e, 0x75, 0xef, 0x32, + 0x78, 0x20, 0xfc, 0xf2, 0x90, 0xe6, 0x41, 0xa6, 0x0e, 0x5c, 0x95, 0x94, 0x50, 0xe4, 0x7c, 0x4d, 0x4a, 0xa5, 0xe5, + 0x91, 0xd2, 0xf3, 0x82, 0xad, 0x13, 0x1d, 0xb3, 0x2d, 0xf3, 0x2a, 0x9e, 0xbe, 0x41, 0x87, 0x61, 0x2f, 0x50, 0xbc, + 0x8f, 0x1f, 0x35, 0x0f, 0x9c, 0x99, 0x7a, 0x12, 0x7c, 0xe0, 0x59, 0x2f, 0x00, 0xcc, 0xcb, 0xd5, 0xf4, 0x08, 0x2c, + 0xf0, 0x34, 0x84, 0x7f, 0xf3, 0x62, 0xf1, 0x83, 0x9b, 0x49, 0x58, 0xbe, 0x1b, 0x4c, 0x01, 0xa5, 0xb9, 0xc1, 0xb4, + 0x62, 0x8e, 0x45, 0x3e, 0xcf, 0xa5, 0xd2, 0xbc, 0xab, 0xdc, 0x54, 0x2a, 0x7e, 0x7e, 0x7b, 0x41, 0xd9, 0xe4, 0x35, + 0x15, 0xa8, 0x1c, 0xba, 0xe8, 0xe6, 0x9a, 0xdc, 0xa7, 0xbd, 0x2f, 0x4f, 0xe6, 0x2c, 0x71, 0x49, 0x0d, 0x04, 0x97, + 0x5f, 0x60, 0x07, 0x14, 0x4e, 0x68, 0x78, 0x54, 0xc2, 0x1e, 0x25, 0xd8, 0x20, 0x3a, 0x61, 0x28, 0x9c, 0x4e, 0x99, + 0x68, 0xf1, 0xd9, 0x73, 0x0c, 0x72, 0x38, 0x18, 0xb9, 0x18, 0x64, 0xbf, 0x17, 0x84, 0x6a, 0xff, 0xcb, 0xcc, 0x37, + 0x73, 0xdb, 0x22, 0xf8, 0x5e, 0xf0, 0xe1, 0x32, 0x62, 0xfe, 0xbf, 0x7a, 0x5f, 0x02, 0xe1, 0xfe, 0xf2, 0x4a, 0xd5, + 0xbb, 0x89, 0x35, 0x8b, 0xd8, 0xa4, 0xf7, 0x25, 0xa6, 0xdd, 0x45, 0xf3, 0x58, 0xe0, 0xda, 0x9f, 0xae, 0xe7, 0xbe, + 0x81, 0xd7, 0x7b, 0x1a, 0x8b, 0xda, 0x6c, 0xe4, 0xc1, 0x4e, 0x9b, 0x7b, 0x5d, 0xea, 0xfb, 0xfc, 0xb6, 0x0e, 0x37, + 0xc0, 0x4d, 0xe1, 0x8e, 0xed, 0x74, 0xf1, 0xfe, 0x3c, 0xf4, 0xdd, 0xd1, 0x87, 0x2e, 0xbd, 0x29, 0x3c, 0x98, 0x40, + 0xad, 0x47, 0xee, 0xa2, 0x83, 0xe4, 0x55, 0x2e, 0x04, 0xef, 0x69, 0x2a, 0xcd, 0x38, 0xbb, 0xda, 0xbd, 0x8c, 0x5b, + 0x79, 0x83, 0x5f, 0xc6, 0x4f, 0xad, 0x66, 0x5e, 0xc2, 0xc4, 0xa7, 0xf0, 0x21, 0x4d, 0xc5, 0x45, 0x9d, 0xae, 0xa8, + 0x78, 0xb1, 0xb6, 0x9a, 0x8a, 0xd3, 0xfe, 0xa6, 0x75, 0xe3, 0xd8, 0xb3, 0x86, 0x63, 0xb5, 0xdf, 0x3b, 0xed, 0x59, + 0xd3, 0x3a, 0xf6, 0xcd, 0xa6, 0x75, 0x0c, 0x7f, 0xde, 0x1f, 0x5b, 0xed, 0x99, 0xd9, 0xb0, 0x0e, 0xdf, 0x3b, 0x0d, + 0xdf, 0x6c, 0x5b, 0xc7, 0xf0, 0xe7, 0x8c, 0x5a, 0xc1, 0x05, 0x88, 0xee, 0x3b, 0x5f, 0x16, 0xb0, 0x80, 0xf4, 0x3b, + 0xd3, 0xc9, 0x1a, 0x05, 0xf2, 0x56, 0xa3, 0xd7, 0x05, 0x94, 0x41, 0xb9, 0xe6, 0xd0, 0x14, 0xa1, 0xab, 0x05, 0x3d, + 0x46, 0xd9, 0xe5, 0x84, 0x79, 0x9b, 0xf0, 0x43, 0x17, 0x29, 0xbe, 0x6a, 0x8f, 0x11, 0x6f, 0x53, 0x9f, 0xd6, 0x4a, + 0xa4, 0x9f, 0x27, 0x45, 0xf0, 0x4f, 0x0b, 0x0c, 0xca, 0x2a, 0xb2, 0x31, 0x4a, 0x58, 0x09, 0x7c, 0xcf, 0xad, 0x20, + 0x5c, 0xa1, 0x6d, 0xc5, 0x5d, 0x03, 0x47, 0x6f, 0x7e, 0x96, 0xa5, 0xe0, 0xfe, 0xac, 0x7d, 0x4b, 0x49, 0x2f, 0x3f, + 0xa9, 0x1f, 0x4c, 0x07, 0x98, 0x67, 0xf2, 0x83, 0x5c, 0x16, 0x63, 0x2f, 0xca, 0x86, 0x27, 0xa1, 0x68, 0xa7, 0x3e, + 0x1f, 0x98, 0x0e, 0xb9, 0x8a, 0xdf, 0x00, 0x97, 0x7c, 0xe3, 0xfa, 0x92, 0x21, 0x9b, 0xd4, 0xf2, 0x41, 0x86, 0xf9, + 0x1f, 0x3f, 0xce, 0x07, 0x67, 0x96, 0xc6, 0x7d, 0xe2, 0xb4, 0x80, 0xec, 0xb6, 0x58, 0x73, 0xa7, 0x4d, 0x25, 0xdd, + 0x74, 0x76, 0xf9, 0x56, 0xe7, 0xe9, 0x0f, 0x84, 0xdd, 0x94, 0xb0, 0xd8, 0xd8, 0x6a, 0xd8, 0x59, 0xb1, 0xd7, 0x80, + 0xfc, 0x31, 0xa5, 0xab, 0x8e, 0xaa, 0x77, 0x03, 0x61, 0x7e, 0x10, 0xec, 0xc8, 0xfc, 0xc2, 0xef, 0x62, 0x2a, 0x80, + 0x66, 0xc7, 0x3c, 0xee, 0x70, 0x10, 0xff, 0xb3, 0x27, 0x81, 0xce, 0x9a, 0x60, 0x2f, 0x51, 0x3a, 0xad, 0x05, 0xe7, + 0xbd, 0x8c, 0xae, 0x12, 0x41, 0x65, 0xf1, 0xa9, 0x0a, 0x45, 0x90, 0xc4, 0x1e, 0x70, 0xa3, 0x9a, 0x19, 0x8b, 0x66, + 0xd4, 0x22, 0x2f, 0x30, 0x3c, 0xcc, 0xb2, 0x25, 0x1c, 0x47, 0xf5, 0xc7, 0x8f, 0xb7, 0x12, 0x21, 0x32, 0xce, 0x89, + 0x59, 0x92, 0x65, 0xd6, 0x56, 0x65, 0xfc, 0xa6, 0xca, 0x28, 0x26, 0xeb, 0x17, 0xb1, 0x86, 0xb0, 0x71, 0xa5, 0xbd, + 0x87, 0x3f, 0x87, 0xcc, 0x4d, 0x2c, 0xae, 0x2c, 0xd5, 0x24, 0xe2, 0x6e, 0x38, 0xac, 0x09, 0xd6, 0xad, 0x3c, 0x76, + 0x73, 0x96, 0xa0, 0xf7, 0x6f, 0x4b, 0x1e, 0xd5, 0x01, 0xfa, 0xf8, 0x68, 0xe7, 0xc1, 0xb9, 0xde, 0x26, 0x2e, 0x85, + 0x24, 0x93, 0x49, 0x6e, 0x98, 0xb8, 0x22, 0x59, 0x34, 0xf0, 0xe5, 0xf5, 0x01, 0x59, 0xa4, 0xc8, 0x0f, 0xfd, 0xb7, + 0x17, 0x5f, 0x2b, 0x7c, 0xff, 0x93, 0xb5, 0x00, 0x5e, 0x64, 0x28, 0xe7, 0x5c, 0x8f, 0x72, 0xce, 0x29, 0x3c, 0xbd, + 0xa1, 0x8a, 0xd9, 0x82, 0x09, 0x82, 0x28, 0x80, 0x26, 0x1b, 0x8a, 0xf9, 0xd2, 0x4f, 0xbc, 0x85, 0x1b, 0x25, 0x07, + 0x98, 0x70, 0x0e, 0x90, 0x9c, 0xba, 0x2d, 0x1e, 0x04, 0x99, 0x61, 0x88, 0x90, 0xe1, 0x49, 0x20, 0xec, 0x30, 0x26, + 0x9e, 0x9f, 0x99, 0x61, 0x88, 0x0f, 0xb8, 0xa3, 0x11, 0x5b, 0x24, 0xbd, 0x42, 0x62, 0xbb, 0x70, 0x94, 0xb0, 0xc4, + 0x8c, 0x93, 0x88, 0xb9, 0x73, 0x35, 0x4b, 0x4d, 0x51, 0xed, 0x2f, 0x5e, 0x0e, 0xe7, 0x5e, 0x92, 0xc5, 0x76, 0xa7, + 0x09, 0x82, 0x41, 0x04, 0x0c, 0x11, 0x82, 0xcc, 0x10, 0x08, 0xcf, 0xc2, 0x69, 0x69, 0x47, 0xe5, 0x9c, 0xcb, 0x29, + 0x66, 0x0e, 0xa1, 0x9b, 0x0c, 0x48, 0x8b, 0x47, 0xa1, 0x7f, 0xcd, 0x63, 0x58, 0x64, 0x21, 0xe8, 0xd5, 0xfe, 0x09, + 0xbf, 0xde, 0x2a, 0x18, 0xbe, 0x45, 0x6d, 0xd9, 0x90, 0x1b, 0x65, 0x5b, 0x74, 0x8b, 0x03, 0x5e, 0x19, 0x48, 0x13, + 0xf5, 0x8c, 0xe9, 0xad, 0x68, 0x2c, 0x17, 0xc0, 0x08, 0x15, 0x0c, 0x66, 0x66, 0x4e, 0x3f, 0x73, 0xa7, 0xc4, 0x51, + 0x21, 0xaf, 0xf4, 0xf1, 0xe3, 0xf9, 0xe0, 0xd7, 0x7f, 0x43, 0x0e, 0xae, 0x99, 0x23, 0x62, 0x4a, 0x5c, 0xca, 0xb5, + 0x38, 0xf7, 0x69, 0x0c, 0xd0, 0x58, 0x8a, 0x8d, 0x45, 0x08, 0x20, 0xb1, 0xb5, 0xd2, 0xc1, 0x95, 0x08, 0x0e, 0x0c, + 0xd9, 0xfb, 0x74, 0x11, 0xf9, 0x02, 0x93, 0x41, 0x0f, 0x44, 0x4c, 0x14, 0xe5, 0xe7, 0xf5, 0xf3, 0x63, 0x25, 0x8f, + 0xa9, 0x54, 0x67, 0xd1, 0x43, 0x7b, 0xa8, 0x7f, 0xe2, 0x2a, 0xc8, 0xb4, 0x20, 0xfb, 0x11, 0x77, 0x0e, 0x60, 0x9a, + 0xb3, 0x70, 0xce, 0x2c, 0x2f, 0x3c, 0x58, 0xb1, 0xa1, 0xe9, 0x2e, 0x3c, 0xb2, 0xcb, 0x41, 0xb9, 0x9b, 0x42, 0x9c, + 0x5f, 0x66, 0xee, 0x42, 0xfc, 0x75, 0x9a, 0x83, 0x32, 0x2c, 0x46, 0x83, 0x6e, 0x35, 0x72, 0x3d, 0xe0, 0x21, 0x05, + 0x80, 0x13, 0x70, 0x0c, 0xfb, 0x27, 0x07, 0x6e, 0xbf, 0x18, 0x8e, 0xde, 0x12, 0x69, 0xd5, 0x8a, 0x44, 0xe0, 0x94, + 0xa2, 0xca, 0x8b, 0x00, 0xf2, 0xf9, 0x83, 0x19, 0x4e, 0x26, 0x72, 0x08, 0x79, 0xab, 0x38, 0xbc, 0x0c, 0x68, 0xf9, + 0x96, 0x0e, 0x17, 0xf4, 0xa5, 0xea, 0x27, 0xb2, 0x9f, 0x6a, 0x07, 0x73, 0x47, 0xc0, 0x9c, 0xe1, 0xb8, 0x57, 0x42, + 0xd1, 0x67, 0x10, 0x7b, 0x48, 0x95, 0x38, 0x1e, 0x29, 0x27, 0x3e, 0xda, 0xc2, 0xb9, 0x3c, 0xe8, 0xf5, 0x08, 0xcd, + 0x95, 0xb1, 0x1d, 0x00, 0xb1, 0x26, 0xc5, 0x1c, 0x4c, 0x36, 0x81, 0x86, 0x26, 0xb9, 0xcb, 0x62, 0xa3, 0xf2, 0x74, + 0xaa, 0x63, 0x3c, 0x70, 0xc5, 0xf6, 0x2b, 0x6c, 0x50, 0xd8, 0x78, 0x7c, 0xdd, 0x01, 0xbf, 0x8b, 0x7e, 0x4a, 0x68, + 0x5e, 0xf9, 0x8a, 0x30, 0xba, 0xe9, 0xbb, 0xb7, 0xa1, 0x64, 0xc6, 0xc4, 0x23, 0x9a, 0x9c, 0x61, 0xe9, 0x85, 0xf0, + 0x24, 0xae, 0x1c, 0x34, 0x24, 0x61, 0x90, 0x8a, 0xab, 0x7a, 0xd8, 0x72, 0xfa, 0xeb, 0xb3, 0xbb, 0xce, 0x9a, 0x5c, + 0xb7, 0x38, 0x19, 0x44, 0x9e, 0x69, 0x7e, 0x0e, 0x0b, 0x2f, 0x11, 0x2d, 0xa4, 0x27, 0x07, 0x30, 0x3f, 0x88, 0xc2, + 0x52, 0x60, 0x9c, 0x3c, 0x1d, 0x42, 0xbd, 0xb8, 0x31, 0x99, 0x62, 0xbd, 0x19, 0x0b, 0x9e, 0x0f, 0x2f, 0x96, 0x52, + 0x82, 0x59, 0xa9, 0x4a, 0x95, 0x97, 0xb1, 0xeb, 0x99, 0xc0, 0xbb, 0xf3, 0xd6, 0xbd, 0x5f, 0x62, 0xd2, 0xba, 0xb4, + 0x9b, 0x30, 0x11, 0xe4, 0xe0, 0x2c, 0xd9, 0x12, 0x07, 0x61, 0x5b, 0x15, 0xe2, 0x67, 0x77, 0x54, 0xc8, 0xf7, 0xf1, + 0xae, 0x5a, 0x39, 0xe7, 0x94, 0x55, 0x9b, 0xba, 0x9a, 0xfa, 0x10, 0x77, 0x7c, 0xa5, 0x36, 0x96, 0x42, 0xbd, 0xb3, + 0xa4, 0x07, 0x55, 0x85, 0x2c, 0xde, 0x5d, 0x2c, 0xa8, 0xb2, 0xde, 0x3d, 0x39, 0xa0, 0x6b, 0x69, 0x9f, 0x76, 0x58, + 0xff, 0x04, 0x4c, 0xb9, 0x69, 0xd1, 0xdd, 0xc5, 0x82, 0x2f, 0x29, 0xfd, 0xa2, 0x37, 0x07, 0xb3, 0x64, 0xee, 0xf7, + 0xff, 0x17, 0x99, 0x05, 0x40, 0xf2, 0xa6, 0x72, 0x03, 0x00}; -} // namespace web_server -} // namespace esphome +#else // Brotli (default, smaller) +const uint8_t INDEX_BR[] PROGMEM = { + 0x5b, 0xa5, 0x72, 0x53, 0x82, 0x27, 0x1b, 0xbc, 0x80, 0x75, 0x03, 0x48, 0xa5, 0xee, 0x7f, 0x2b, 0x0f, 0x47, 0x11, + 0x6c, 0x9c, 0x30, 0x00, 0xbc, 0x78, 0xe3, 0x55, 0x06, 0x02, 0xe7, 0x01, 0x48, 0x39, 0xae, 0xfd, 0x5e, 0x40, 0x55, + 0x4d, 0x39, 0x3a, 0x86, 0x68, 0xb0, 0x4b, 0x00, 0x54, 0x6c, 0xdf, 0xed, 0x57, 0x28, 0x4a, 0x70, 0x44, 0x0e, 0x0a, + 0x69, 0x65, 0x69, 0xd3, 0x0b, 0x32, 0x81, 0x19, 0x99, 0x45, 0x41, 0x5c, 0x10, 0x08, 0x62, 0xa3, 0x51, 0x83, 0xcd, + 0x39, 0x4f, 0x9c, 0xe3, 0x34, 0x65, 0x23, 0x09, 0xb7, 0x1c, 0x9c, 0x0d, 0xdd, 0x8c, 0x40, 0x15, 0x18, 0xc2, 0xe1, + 0x0d, 0x6d, 0x41, 0xba, 0xf2, 0xe0, 0xce, 0x7b, 0x72, 0x4e, 0x34, 0xfe, 0x50, 0x23, 0x59, 0xda, 0x96, 0x63, 0x22, + 0x70, 0xf0, 0x6a, 0xb7, 0x8c, 0x88, 0x6e, 0x48, 0x1a, 0xf6, 0x45, 0xf5, 0xa0, 0xf6, 0x37, 0xf1, 0x4b, 0x22, 0x1c, + 0x2f, 0x73, 0x23, 0x8a, 0x31, 0x70, 0x24, 0x71, 0x67, 0x77, 0x29, 0xa1, 0x7d, 0xad, 0xb1, 0x07, 0x6b, 0x81, 0x56, + 0x9e, 0x58, 0x49, 0x4c, 0x4f, 0x54, 0x3f, 0x88, 0x99, 0x48, 0x93, 0x67, 0x63, 0xed, 0x72, 0xc1, 0x0d, 0x75, 0x3e, + 0x51, 0xff, 0x97, 0xe7, 0x7b, 0xc9, 0x07, 0xd3, 0x4d, 0xf1, 0x33, 0xbf, 0xf5, 0x5a, 0xc7, 0x8d, 0x5a, 0x44, 0x59, + 0xb7, 0xb4, 0xf1, 0x5d, 0x70, 0x1b, 0xcf, 0xdd, 0x66, 0xb5, 0xe0, 0x04, 0x41, 0x10, 0xcb, 0x00, 0xc7, 0x98, 0xa8, + 0x1f, 0x22, 0x6d, 0x84, 0x78, 0xf1, 0x7e, 0x44, 0xdf, 0xfe, 0x4b, 0xb5, 0xff, 0xfa, 0x0d, 0x7b, 0xb6, 0x6e, 0xaf, + 0x4d, 0xcf, 0xbc, 0x4d, 0x53, 0x8c, 0xb3, 0xd8, 0x7b, 0x56, 0xdb, 0x59, 0x59, 0x2a, 0x19, 0xc6, 0x3d, 0xe4, 0x45, + 0x0c, 0x01, 0x1c, 0x00, 0x1d, 0x49, 0xee, 0xa7, 0x7d, 0x9b, 0xf6, 0x9d, 0xee, 0xf3, 0xd8, 0xd6, 0x6f, 0xdb, 0x09, + 0xa9, 0x7d, 0xca, 0xc5, 0xdf, 0x31, 0x02, 0x2b, 0x31, 0x12, 0x63, 0x89, 0x96, 0xfb, 0x2a, 0x67, 0xfd, 0x79, 0x2e, + 0x37, 0x57, 0x8c, 0x46, 0x4e, 0x9e, 0x5a, 0x22, 0xb3, 0x00, 0xef, 0xa9, 0xfe, 0x18, 0x84, 0x77, 0xd9, 0xac, 0xb6, + 0xa9, 0xf6, 0x87, 0xa4, 0x14, 0x52, 0x85, 0x5d, 0x44, 0xc8, 0x19, 0x21, 0xb6, 0x92, 0xef, 0x97, 0xf6, 0x7b, 0x7f, + 0xe6, 0xfb, 0xf5, 0x0d, 0xc7, 0xb9, 0x19, 0x8e, 0x76, 0x1d, 0x86, 0x94, 0x3a, 0x29, 0xb5, 0x39, 0xb2, 0x18, 0x16, + 0x5e, 0x64, 0x69, 0x9f, 0x24, 0x5c, 0xc7, 0xff, 0x7a, 0x5f, 0x57, 0x5f, 0xbf, 0x76, 0x17, 0x2b, 0xce, 0x1d, 0x1d, + 0x29, 0x4b, 0xd8, 0xe7, 0x15, 0x37, 0x3d, 0x19, 0xc2, 0x23, 0xbc, 0x42, 0x91, 0xcc, 0x23, 0xa5, 0x72, 0x19, 0xc7, + 0xea, 0x18, 0xed, 0x1a, 0xd9, 0xa5, 0x56, 0x88, 0x8c, 0xd5, 0xf0, 0xff, 0xf5, 0x35, 0xad, 0xaf, 0x5f, 0x89, 0xb0, + 0xca, 0x5c, 0xea, 0x40, 0xa1, 0x99, 0xfd, 0x3d, 0x92, 0x9c, 0x65, 0xd9, 0x3d, 0x15, 0x82, 0x96, 0x69, 0x1b, 0x2f, + 0xe0, 0x00, 0x7a, 0xcd, 0xba, 0xf7, 0xbe, 0xaa, 0xfe, 0xfb, 0xe7, 0x0b, 0xa1, 0x3b, 0x44, 0x1b, 0xb8, 0xdc, 0x1d, + 0x26, 0x4b, 0x33, 0x6b, 0xaa, 0xe9, 0xce, 0xd8, 0x14, 0x05, 0x4a, 0x4c, 0x25, 0xca, 0x8f, 0x80, 0x95, 0x49, 0x4b, + 0x5d, 0x55, 0x94, 0xce, 0x7e, 0x48, 0x05, 0x65, 0x53, 0x36, 0x34, 0xc3, 0xb6, 0x75, 0xea, 0xfb, 0x93, 0xcc, 0xf7, + 0x13, 0x5e, 0x90, 0x8d, 0x0d, 0xcd, 0xb7, 0x2f, 0x4f, 0xd7, 0x71, 0x23, 0x2d, 0xb0, 0xc4, 0x22, 0xbd, 0x6f, 0x90, + 0x13, 0xa5, 0x12, 0xf3, 0x3f, 0xdc, 0x72, 0xe2, 0x12, 0x88, 0xd6, 0x66, 0x6f, 0x0b, 0xf1, 0x8a, 0x2f, 0x12, 0x3d, + 0x1f, 0x44, 0x3c, 0x4d, 0x2d, 0x5f, 0x6f, 0xfb, 0x25, 0x5f, 0xa5, 0x73, 0x29, 0x2f, 0xa5, 0x58, 0x49, 0x37, 0x57, + 0xba, 0xc0, 0xbb, 0xb7, 0xa9, 0x44, 0x69, 0x3a, 0xb0, 0x81, 0x1c, 0x8a, 0xa4, 0x8c, 0x2d, 0x50, 0xd8, 0x7b, 0x7f, + 0x3f, 0xfb, 0xaf, 0x5f, 0x9d, 0x5a, 0x9c, 0x39, 0x0c, 0x22, 0xde, 0x77, 0x34, 0xb0, 0x8f, 0xc7, 0x5b, 0xad, 0x1b, + 0x35, 0xc6, 0xec, 0xd2, 0xe0, 0x25, 0x51, 0xdb, 0xd3, 0x70, 0xcc, 0x65, 0x55, 0x7d, 0x8a, 0x0a, 0x8d, 0x18, 0x42, + 0x97, 0x67, 0xbf, 0xcf, 0x64, 0x1f, 0xd3, 0x0d, 0xcc, 0x60, 0xd8, 0xc8, 0x73, 0x82, 0x71, 0xbd, 0x10, 0xd1, 0xd6, + 0x60, 0xa9, 0xda, 0x8a, 0x21, 0x49, 0x97, 0xed, 0x91, 0x5d, 0x78, 0x51, 0xf6, 0x07, 0x7f, 0xeb, 0x3c, 0x3c, 0x12, + 0x95, 0x0f, 0x0f, 0xc9, 0x07, 0xd4, 0xea, 0x1b, 0x7e, 0xdf, 0xd7, 0xa6, 0x7b, 0x97, 0xc9, 0x61, 0x2b, 0xf1, 0x2f, + 0x8d, 0x52, 0x01, 0x68, 0x0b, 0xe0, 0x51, 0x80, 0x2e, 0x45, 0x3d, 0x35, 0x58, 0xfe, 0xff, 0xde, 0x4a, 0xcb, 0xed, + 0x8f, 0xb4, 0x20, 0xda, 0x01, 0xe4, 0x38, 0xc3, 0x91, 0xf5, 0xfb, 0xce, 0xca, 0x02, 0x5b, 0x0d, 0x90, 0xec, 0x71, + 0x66, 0x25, 0xad, 0xb5, 0xd8, 0x54, 0x5c, 0xf3, 0x2e, 0xf3, 0xbb, 0xe8, 0x8c, 0x1f, 0x86, 0x15, 0x26, 0xb3, 0x91, + 0xae, 0x9a, 0x65, 0x1b, 0x59, 0x4e, 0x03, 0x80, 0xe0, 0x7d, 0xef, 0xff, 0x28, 0xfc, 0xff, 0x23, 0x8b, 0xf3, 0x23, + 0xb2, 0x40, 0x45, 0x66, 0x15, 0xe7, 0x64, 0x15, 0x30, 0xa3, 0x2a, 0x90, 0x33, 0x2a, 0x80, 0x7d, 0x74, 0x40, 0x8e, + 0x13, 0xbd, 0xa6, 0xe9, 0x8e, 0x86, 0x94, 0xf7, 0x3b, 0x2d, 0x56, 0x6c, 0xca, 0xfb, 0xdd, 0x0e, 0x94, 0x35, 0x4b, + 0x69, 0xa5, 0xa3, 0xff, 0xd5, 0x99, 0xeb, 0x3f, 0xd6, 0x5d, 0x11, 0x26, 0x5f, 0x27, 0xdd, 0x30, 0x6c, 0xf1, 0xff, + 0x92, 0x41, 0x72, 0x1a, 0x60, 0x39, 0x28, 0x17, 0xe5, 0x94, 0xec, 0x32, 0x8d, 0x1d, 0xbb, 0xad, 0xc4, 0xc3, 0xc6, + 0x63, 0x5f, 0xd5, 0x1f, 0xc3, 0xef, 0xe1, 0x68, 0x41, 0x98, 0x3a, 0xd3, 0x34, 0xfd, 0x77, 0xfb, 0x63, 0xd9, 0xf7, + 0x3a, 0x3d, 0xe7, 0xe8, 0xef, 0xac, 0x42, 0x08, 0x24, 0x04, 0x14, 0x84, 0x60, 0xdd, 0xd9, 0x46, 0xb5, 0x2a, 0x5d, + 0x6d, 0xdd, 0x71, 0xd5, 0xaa, 0x69, 0xbe, 0x86, 0x3f, 0x04, 0x08, 0x81, 0x99, 0xbb, 0xc7, 0x70, 0x76, 0x39, 0x03, + 0x11, 0x09, 0xd1, 0x7f, 0x8f, 0x53, 0xca, 0x99, 0xd2, 0x83, 0xb4, 0x05, 0x42, 0xa4, 0xc6, 0x6b, 0x7b, 0xfd, 0xfd, + 0xf3, 0x78, 0xb9, 0x45, 0x74, 0x52, 0xdf, 0x2b, 0xd0, 0x1e, 0xa1, 0xed, 0x1f, 0x89, 0xa7, 0x3c, 0xe4, 0x11, 0xaf, + 0x04, 0x8b, 0x4d, 0xd2, 0xd5, 0x70, 0x9f, 0x00, 0x57, 0xf8, 0x16, 0x97, 0xb6, 0x76, 0x97, 0x9b, 0xac, 0xc5, 0x35, + 0x13, 0x6c, 0xee, 0x2c, 0x26, 0xce, 0x2d, 0x0e, 0x10, 0x4e, 0xc7, 0x88, 0xb6, 0xe0, 0x32, 0xd0, 0x1c, 0xe7, 0x0e, + 0x7e, 0xf9, 0xbc, 0x10, 0x4c, 0xa3, 0x3d, 0x94, 0x6c, 0xc7, 0x28, 0xc4, 0x14, 0x44, 0x9e, 0x17, 0xce, 0xde, 0x7d, + 0x60, 0x72, 0x3f, 0xa5, 0x62, 0x30, 0x0b, 0xa3, 0x27, 0x2c, 0x19, 0x68, 0x5a, 0x2f, 0xf9, 0x15, 0x80, 0x77, 0xf8, + 0x2e, 0x9c, 0xb0, 0x08, 0x94, 0xf1, 0xed, 0x17, 0xdc, 0x67, 0x18, 0x3f, 0x0b, 0xd3, 0xce, 0x0a, 0x75, 0xf0, 0x40, + 0xe0, 0xfd, 0xba, 0xf1, 0x8c, 0x5f, 0x28, 0x37, 0xec, 0x96, 0x61, 0xe6, 0x91, 0xce, 0x2f, 0xa7, 0xe8, 0x0a, 0x49, + 0x95, 0x0b, 0x62, 0xcf, 0x3d, 0x2e, 0xcd, 0xf9, 0x8f, 0x68, 0x79, 0x19, 0xb8, 0xa3, 0x37, 0x81, 0x98, 0xfa, 0x7b, + 0xeb, 0xaa, 0xd9, 0xde, 0x69, 0x11, 0x00, 0x48, 0xae, 0x5d, 0x1c, 0x66, 0xb4, 0x2d, 0xae, 0xaf, 0xe5, 0xd5, 0x6e, + 0xbc, 0x95, 0x7a, 0x98, 0xa0, 0x21, 0xd9, 0x30, 0x83, 0x33, 0xdb, 0x7f, 0x78, 0xf9, 0xf6, 0xda, 0x62, 0x5c, 0x46, + 0xcd, 0xfe, 0x3a, 0xad, 0x6d, 0xe8, 0x48, 0x1b, 0x90, 0xc3, 0x5d, 0xe9, 0xfa, 0x90, 0x52, 0x50, 0xd8, 0xb0, 0xc9, + 0xe0, 0xb9, 0xe8, 0x8e, 0xfb, 0x98, 0xd4, 0x12, 0x93, 0x75, 0x39, 0xe7, 0xd2, 0x00, 0xb3, 0x99, 0xbd, 0xbd, 0x76, + 0xa5, 0x01, 0x77, 0xf8, 0x71, 0xb7, 0x3f, 0x32, 0xb9, 0x17, 0x4d, 0xfc, 0x62, 0xc6, 0xe5, 0x71, 0x08, 0xec, 0xed, + 0x8f, 0xb4, 0xaf, 0x16, 0x34, 0x9b, 0x5b, 0x3f, 0xe6, 0x1b, 0x5f, 0x76, 0xd7, 0xa3, 0x08, 0x1a, 0x9a, 0x7c, 0x69, + 0xd0, 0xbc, 0x68, 0x7a, 0xf5, 0x19, 0xb5, 0x42, 0x7d, 0xe3, 0x27, 0x52, 0x78, 0xec, 0x26, 0xf1, 0xd7, 0x2e, 0xb7, + 0xef, 0x7e, 0xf7, 0x38, 0x78, 0xf4, 0x45, 0x0c, 0x2f, 0xdf, 0x5e, 0x3f, 0x6e, 0x9e, 0xe1, 0x39, 0xa3, 0x3a, 0x6c, + 0x86, 0x84, 0xa1, 0xe6, 0xf7, 0x5f, 0x32, 0xcc, 0xbe, 0x2c, 0x7b, 0x32, 0xab, 0xcb, 0x7f, 0xee, 0xeb, 0x23, 0x3e, + 0xa8, 0x5d, 0x3e, 0xe3, 0xd7, 0x3f, 0x10, 0x9e, 0xfe, 0x8b, 0xa4, 0x87, 0x75, 0x87, 0xf6, 0x67, 0x7d, 0x33, 0x7e, + 0xec, 0xaa, 0xb0, 0xef, 0xc3, 0x7c, 0x43, 0x68, 0x7b, 0x1e, 0x53, 0xd3, 0x2b, 0x51, 0xc1, 0x44, 0x1c, 0x07, 0x21, + 0x18, 0xb5, 0x75, 0x7a, 0x6b, 0x06, 0xe7, 0xf6, 0x0c, 0xc6, 0xec, 0xb6, 0x5a, 0x2c, 0x6d, 0x08, 0x6f, 0xe6, 0xc3, + 0xae, 0x8f, 0x0b, 0xf4, 0x17, 0x58, 0x8c, 0x21, 0xed, 0xfa, 0xfe, 0x4a, 0x5f, 0xad, 0x2c, 0xcb, 0x00, 0x40, 0x60, + 0xa8, 0x67, 0xc3, 0xd1, 0xb3, 0x44, 0xc6, 0x3c, 0x9b, 0xcf, 0x0d, 0xe9, 0x32, 0xf4, 0xc8, 0xf5, 0x4d, 0xb7, 0x06, + 0x0d, 0x2f, 0x16, 0x6c, 0xc0, 0x57, 0x2b, 0x05, 0xeb, 0x15, 0x16, 0xe4, 0xce, 0x59, 0x71, 0x7f, 0x0f, 0xa5, 0x66, + 0x4d, 0xc4, 0x72, 0xdc, 0x4a, 0x0e, 0xf2, 0xbe, 0x8f, 0x30, 0xd2, 0xd8, 0xd7, 0xf6, 0xd6, 0x8b, 0xeb, 0x3b, 0xa8, + 0x08, 0x7b, 0x28, 0xf7, 0xb2, 0x4a, 0x41, 0x6c, 0xef, 0x18, 0xd2, 0xb3, 0x7c, 0xdc, 0xd7, 0xd7, 0x6e, 0xe6, 0x84, + 0x0f, 0x46, 0x23, 0x43, 0x19, 0x37, 0xd6, 0xe4, 0x89, 0xd8, 0x8f, 0xab, 0x94, 0xe6, 0x31, 0xca, 0x0f, 0x3a, 0xf2, + 0x42, 0x8c, 0xc6, 0xa4, 0x17, 0xec, 0x3b, 0x59, 0x0a, 0x8d, 0xe3, 0xbc, 0x24, 0x1e, 0x6b, 0x71, 0x44, 0x0a, 0x68, + 0x14, 0x1f, 0xc8, 0x66, 0x46, 0x24, 0x40, 0xd5, 0x22, 0xed, 0x2c, 0x9c, 0x44, 0x2d, 0x4e, 0xb4, 0xc1, 0x7b, 0xbf, + 0xdf, 0x71, 0xe7, 0xa4, 0x84, 0x8b, 0x2f, 0x2e, 0xdb, 0x9e, 0x26, 0xe4, 0xe1, 0x97, 0x72, 0xb2, 0xe6, 0x89, 0x9e, + 0x92, 0x5b, 0x1e, 0xb1, 0xa9, 0xb0, 0x4c, 0xc6, 0x42, 0x0f, 0x14, 0x87, 0x6e, 0x99, 0x57, 0xf1, 0x8e, 0x2f, 0x9f, + 0xe1, 0x6a, 0xf7, 0x9b, 0xef, 0x2d, 0x5f, 0xcb, 0xcc, 0x57, 0xeb, 0x1e, 0xd0, 0xf8, 0xe0, 0x04, 0x9e, 0xb2, 0xbb, + 0x6f, 0xbc, 0x9b, 0xbc, 0x06, 0x78, 0xf9, 0xdc, 0x2f, 0x51, 0xbe, 0xa8, 0xfd, 0x86, 0xe8, 0x35, 0x80, 0xc0, 0x03, + 0x91, 0x6c, 0xc5, 0xd4, 0x5b, 0x4e, 0x64, 0x72, 0xff, 0x3e, 0xc3, 0x36, 0x7c, 0xf2, 0x6e, 0x1d, 0xaa, 0xa4, 0xb1, + 0x12, 0x9e, 0x49, 0xeb, 0x77, 0xaa, 0xf6, 0x39, 0x12, 0x18, 0x7b, 0x15, 0xcc, 0xb0, 0x3b, 0x86, 0x98, 0x9d, 0xa4, + 0x70, 0xdc, 0x17, 0x1b, 0x2c, 0xf3, 0x9b, 0x99, 0x43, 0xb7, 0x34, 0x3e, 0x86, 0x3b, 0x19, 0xab, 0xb4, 0x4d, 0xa5, + 0x71, 0xd7, 0xf8, 0x1b, 0x82, 0xc0, 0x55, 0x87, 0xda, 0xa3, 0x49, 0xbf, 0x64, 0x5f, 0x70, 0x1d, 0x9c, 0x09, 0x7a, + 0x59, 0x0e, 0x56, 0xb8, 0x54, 0x83, 0xe5, 0xad, 0x8f, 0x00, 0x02, 0x61, 0x5b, 0x10, 0xc2, 0x06, 0xdb, 0x6b, 0xe9, + 0x58, 0x8d, 0x9c, 0xb1, 0x79, 0xff, 0xf1, 0x66, 0x89, 0x1e, 0xca, 0x16, 0x4e, 0x53, 0x7d, 0x29, 0x6b, 0xb5, 0xdf, + 0xbb, 0x7f, 0xbe, 0xef, 0x00, 0x07, 0x09, 0xc3, 0x0b, 0x3a, 0x38, 0xfe, 0x1a, 0xbd, 0x15, 0x8b, 0x60, 0x61, 0xb4, + 0x6d, 0x7d, 0xf6, 0xed, 0xa1, 0x78, 0x66, 0x41, 0x7c, 0x33, 0x6b, 0xb7, 0xae, 0x2a, 0x8e, 0xb0, 0x55, 0xc3, 0x26, + 0x84, 0xd6, 0x10, 0x01, 0x98, 0xfa, 0x4b, 0x8d, 0x7c, 0xb7, 0x0d, 0xab, 0x4e, 0x45, 0x14, 0xdb, 0xaa, 0x10, 0x47, + 0x50, 0xc9, 0xc1, 0xa0, 0x8a, 0x42, 0x17, 0x76, 0x0f, 0x7e, 0xe2, 0x64, 0x5c, 0x50, 0xdc, 0x54, 0x3c, 0x7a, 0x7e, + 0x0b, 0x94, 0x81, 0xa9, 0xbf, 0x5c, 0x69, 0xef, 0x0f, 0x90, 0x3e, 0xd8, 0xc3, 0x4e, 0xc9, 0xe7, 0x2a, 0x9e, 0xd0, + 0x77, 0x27, 0x34, 0x81, 0xe1, 0xe1, 0xd8, 0x9b, 0xc4, 0x07, 0x12, 0xf0, 0xa0, 0x60, 0x41, 0xbe, 0xbf, 0x13, 0xea, + 0xa6, 0x4f, 0x03, 0x02, 0x88, 0xaf, 0xaa, 0xe3, 0x70, 0x84, 0x60, 0x45, 0x1d, 0x0b, 0xec, 0x89, 0x8a, 0x53, 0x72, + 0x44, 0x86, 0x71, 0x82, 0xfa, 0x06, 0xe8, 0xe6, 0x53, 0xa5, 0xfc, 0x0b, 0xb7, 0x9b, 0x44, 0xe2, 0x59, 0xdf, 0x2a, + 0xda, 0x65, 0xa4, 0x48, 0xe0, 0x8d, 0x58, 0x47, 0x95, 0x42, 0xe9, 0x06, 0xcb, 0x94, 0x22, 0x4e, 0xd3, 0xe0, 0xfa, + 0xd2, 0x4b, 0xbd, 0xd6, 0x4e, 0x2d, 0xfd, 0x7a, 0xdd, 0x04, 0xfa, 0x1a, 0x0f, 0x59, 0xf0, 0xa5, 0xfa, 0xa9, 0x26, + 0xb0, 0x1b, 0x68, 0x3b, 0x97, 0xda, 0x9f, 0xa1, 0x71, 0xb2, 0xa1, 0x7d, 0x2d, 0x6f, 0x21, 0x80, 0x29, 0xdc, 0xa0, + 0xb6, 0xbc, 0x48, 0xa6, 0x76, 0x22, 0x66, 0x7f, 0x6c, 0x22, 0x99, 0x20, 0xaf, 0x1c, 0x6c, 0x68, 0x1f, 0xda, 0xc2, + 0xf3, 0xa0, 0x04, 0xc3, 0xcf, 0x5a, 0xca, 0xc0, 0x8a, 0xb0, 0xad, 0xed, 0xd7, 0xc7, 0xb0, 0x09, 0xa6, 0x0c, 0x82, + 0x50, 0x7f, 0x09, 0xda, 0x69, 0x30, 0x79, 0xd4, 0x63, 0xc6, 0x3e, 0x9b, 0x39, 0xdf, 0x83, 0x1e, 0xa5, 0x92, 0x85, + 0x20, 0x39, 0x0c, 0xf4, 0xd7, 0x62, 0x62, 0x1c, 0xa1, 0x42, 0x22, 0xb4, 0x5b, 0x25, 0x03, 0xf7, 0x1f, 0x54, 0x0c, + 0x11, 0x76, 0x54, 0x9d, 0x4b, 0xb3, 0x6b, 0x54, 0x31, 0x50, 0xd8, 0x18, 0x3d, 0x4c, 0x9d, 0x23, 0x84, 0xd3, 0xa6, + 0xf6, 0xce, 0x5d, 0x24, 0xd2, 0x53, 0x29, 0x62, 0xb1, 0x71, 0x56, 0xba, 0xd4, 0x0c, 0x6b, 0x61, 0x2c, 0x27, 0x46, + 0xd0, 0x38, 0x73, 0x47, 0x54, 0xc7, 0xb7, 0xac, 0x61, 0x04, 0xb8, 0x47, 0xec, 0xb6, 0x07, 0xad, 0x00, 0xb9, 0x26, + 0x69, 0xa0, 0xa0, 0xbd, 0x31, 0x21, 0x8b, 0x02, 0x19, 0x19, 0x93, 0xe8, 0x46, 0x50, 0x72, 0xf7, 0x75, 0x24, 0x8f, + 0x01, 0x76, 0xe5, 0x64, 0x3d, 0x43, 0x24, 0xc4, 0xf1, 0xa6, 0x56, 0x04, 0x81, 0xb1, 0x0c, 0xb0, 0x63, 0xe9, 0xa8, + 0xe4, 0x5c, 0xdc, 0xa0, 0xa7, 0xaa, 0x99, 0x5f, 0xd8, 0xf5, 0x19, 0x8a, 0x34, 0xa2, 0x5d, 0x40, 0x40, 0xce, 0x5c, + 0x75, 0x1d, 0x18, 0x39, 0x48, 0x5c, 0xa3, 0x3b, 0xad, 0x90, 0x51, 0x44, 0x3e, 0x2e, 0x86, 0xe5, 0x05, 0xe4, 0x46, + 0x5c, 0x6c, 0x94, 0x65, 0xc8, 0x45, 0x1f, 0xb9, 0x0d, 0xde, 0xb9, 0xd1, 0x93, 0xae, 0x13, 0x96, 0x00, 0xac, 0xc7, + 0xb2, 0x98, 0x70, 0xab, 0xa6, 0x67, 0xbf, 0x01, 0x9a, 0x04, 0x5e, 0x3b, 0xea, 0x1c, 0xe2, 0xf8, 0x42, 0x8d, 0x6c, + 0xf0, 0x6f, 0x6a, 0x94, 0xb0, 0xb6, 0x0d, 0x2b, 0x67, 0x9c, 0x56, 0x51, 0xec, 0xb1, 0xf2, 0x4c, 0xc4, 0xfc, 0x05, + 0xa3, 0xe8, 0x34, 0x58, 0xf1, 0xb4, 0xd5, 0xd2, 0x54, 0xf8, 0xd8, 0x08, 0x85, 0x88, 0x3d, 0x02, 0x4d, 0x81, 0x7d, + 0x6f, 0x28, 0x4c, 0xeb, 0x23, 0xc9, 0x49, 0xda, 0x53, 0x74, 0x36, 0x67, 0xc3, 0x0c, 0xb1, 0x8b, 0x80, 0x6e, 0xcf, + 0xb9, 0x0e, 0x57, 0xf6, 0x45, 0x29, 0x3c, 0x25, 0x33, 0x6a, 0x09, 0x26, 0xc2, 0x64, 0x78, 0x95, 0x5d, 0x20, 0xf2, + 0xde, 0xa6, 0x99, 0x49, 0xef, 0xe9, 0x95, 0xfb, 0x13, 0x40, 0x1e, 0xf7, 0xe4, 0x7d, 0xa6, 0x29, 0xc3, 0x15, 0x0e, + 0x41, 0x6d, 0x57, 0xcc, 0x63, 0xb9, 0x4d, 0xad, 0x4a, 0x6e, 0x38, 0xe0, 0x62, 0x21, 0x35, 0xee, 0xee, 0x6a, 0x73, + 0x42, 0x03, 0x48, 0x55, 0x4b, 0x67, 0x83, 0xe7, 0x80, 0xe2, 0x3d, 0x01, 0x44, 0x22, 0x1a, 0x85, 0xef, 0xfc, 0x28, + 0x47, 0xe7, 0x24, 0x21, 0x14, 0xe6, 0xea, 0x76, 0x5e, 0x7e, 0xa6, 0x94, 0x59, 0x6e, 0x38, 0x3a, 0x00, 0xd8, 0xa2, + 0x7d, 0x51, 0xf8, 0x3c, 0x02, 0xf9, 0xff, 0xa4, 0xb0, 0xf1, 0xad, 0x69, 0xc7, 0x54, 0xf5, 0x6c, 0x56, 0xe7, 0x61, + 0x81, 0xe7, 0x38, 0x65, 0x82, 0xe7, 0xdf, 0x56, 0x77, 0x43, 0xd4, 0xf9, 0xa7, 0xd8, 0x01, 0x5b, 0x6d, 0xb3, 0xbb, + 0x1d, 0xbc, 0xae, 0x16, 0x27, 0x87, 0x4c, 0xaa, 0xce, 0xf6, 0xc1, 0x37, 0xfb, 0xe9, 0x5f, 0x4e, 0x7e, 0x33, 0xc6, + 0x02, 0xc8, 0xab, 0x82, 0xaa, 0xc8, 0xc8, 0x74, 0x40, 0x89, 0x57, 0x86, 0xcf, 0xc5, 0x00, 0x2d, 0x32, 0xaf, 0x5a, + 0x09, 0x14, 0x9a, 0xd5, 0x88, 0xf2, 0x06, 0x10, 0x64, 0x2b, 0xd4, 0x5c, 0xa3, 0xe0, 0x08, 0x79, 0xd2, 0x62, 0x63, + 0xde, 0xb0, 0x52, 0x8b, 0x66, 0x99, 0xb6, 0xa6, 0xc8, 0x22, 0xb0, 0x38, 0x20, 0xae, 0xbf, 0xa0, 0x75, 0x36, 0x30, + 0x95, 0xe7, 0x83, 0x11, 0x06, 0xd8, 0x0d, 0x37, 0x92, 0x4d, 0x8c, 0xbd, 0x12, 0x8a, 0x9c, 0x06, 0xd2, 0xd8, 0x8f, + 0xef, 0xae, 0xe5, 0xed, 0xae, 0x25, 0xa6, 0xbe, 0x3c, 0x9c, 0x18, 0x4d, 0x0c, 0x2d, 0xed, 0x86, 0xa5, 0x87, 0xa8, + 0x9f, 0x9d, 0xcc, 0x4c, 0x39, 0x5b, 0x7b, 0x0c, 0x0f, 0x5d, 0xe7, 0x92, 0xbc, 0x7f, 0x36, 0x03, 0x01, 0xf9, 0xad, + 0xc0, 0x1a, 0xd0, 0x26, 0x11, 0x45, 0x20, 0x1c, 0xe4, 0xf4, 0x2d, 0x71, 0xf4, 0x37, 0x03, 0x4b, 0x76, 0x3d, 0xd4, + 0x2d, 0x75, 0x8b, 0xb1, 0xac, 0x95, 0x95, 0x53, 0x50, 0x71, 0xe9, 0x99, 0x8c, 0x79, 0x20, 0xd9, 0xa0, 0x6a, 0xb0, + 0x92, 0x7d, 0xde, 0x2e, 0x1c, 0xa8, 0xa4, 0x90, 0xbd, 0x9f, 0x06, 0x79, 0x71, 0x7a, 0x91, 0xac, 0x56, 0x62, 0x8c, + 0x47, 0xa9, 0xb6, 0xb9, 0x0e, 0x88, 0xab, 0x0e, 0x6d, 0xe0, 0xc5, 0x85, 0xed, 0x76, 0xbb, 0x66, 0xab, 0x80, 0xac, + 0x95, 0x4e, 0xe4, 0x66, 0x16, 0x9d, 0xef, 0x2d, 0x22, 0x9d, 0xb5, 0xe5, 0x21, 0x2f, 0xe7, 0xb1, 0x0d, 0xb2, 0xe8, + 0x96, 0xf0, 0xc2, 0x98, 0xb0, 0x81, 0xe5, 0xee, 0xdb, 0xf8, 0x82, 0x43, 0x21, 0x29, 0xd3, 0xd3, 0x4c, 0xbb, 0x37, + 0xc4, 0x7c, 0x57, 0x4d, 0xb4, 0xc2, 0x16, 0xcc, 0x75, 0xf0, 0x8b, 0x64, 0xb5, 0x97, 0xa2, 0x5a, 0x3a, 0x1f, 0x66, + 0xef, 0xd2, 0x91, 0xda, 0xcb, 0x00, 0xd5, 0x4e, 0x63, 0x33, 0x8e, 0x73, 0x02, 0xfa, 0x28, 0x98, 0x59, 0xe1, 0x25, + 0x20, 0xbb, 0x48, 0x61, 0x85, 0x95, 0xba, 0xcd, 0x18, 0x96, 0x52, 0xf7, 0xc3, 0x44, 0x67, 0x19, 0x62, 0xe3, 0x49, + 0x5f, 0x1a, 0x7b, 0x34, 0x8a, 0x47, 0xa8, 0x22, 0xaa, 0xdd, 0x1f, 0x21, 0x4c, 0x48, 0x75, 0x86, 0x27, 0x30, 0xed, + 0xf9, 0x08, 0x1d, 0x17, 0x30, 0xbf, 0x98, 0x85, 0x76, 0xfb, 0x7a, 0x8b, 0x58, 0x78, 0xf5, 0x21, 0xc5, 0x35, 0xa5, + 0xb7, 0xf2, 0x55, 0xf8, 0x1c, 0x2b, 0xcf, 0x02, 0x1d, 0x2b, 0x15, 0x86, 0xd9, 0x5c, 0x58, 0xa1, 0x68, 0x34, 0x1f, + 0xee, 0x36, 0x03, 0x6c, 0x86, 0xa0, 0x44, 0x42, 0x71, 0x63, 0x98, 0xc5, 0x30, 0x63, 0x0d, 0x4c, 0xee, 0x16, 0xdd, + 0x9c, 0x64, 0xcd, 0x9d, 0x4c, 0x72, 0xe6, 0xbb, 0x9f, 0x18, 0xe5, 0xc6, 0x31, 0xc5, 0x5e, 0xc7, 0x57, 0x35, 0x59, + 0x5d, 0xab, 0xe9, 0x2d, 0xae, 0x9c, 0x10, 0xb4, 0xce, 0xe2, 0x3e, 0xad, 0x5d, 0x62, 0x02, 0xd8, 0x0a, 0xec, 0x4e, + 0x55, 0x24, 0x15, 0xf4, 0xa1, 0x31, 0xcc, 0x1d, 0x0c, 0x27, 0xb6, 0xa1, 0x92, 0xb5, 0x1c, 0x1d, 0xc4, 0xb6, 0x7f, + 0x1f, 0xe4, 0x0c, 0xe8, 0xf8, 0xed, 0x64, 0x4c, 0x85, 0x40, 0xcd, 0x22, 0xad, 0x2e, 0x43, 0xba, 0x14, 0xe2, 0x5a, + 0x59, 0x5e, 0x08, 0x92, 0xbc, 0x4f, 0xcc, 0x25, 0xca, 0xdb, 0x61, 0xea, 0x35, 0xac, 0xcb, 0x0e, 0x48, 0xa3, 0x17, + 0xaa, 0xf2, 0x1b, 0x16, 0xe1, 0x48, 0x13, 0x26, 0x6b, 0x67, 0x20, 0xe6, 0x35, 0x6a, 0x33, 0x45, 0xc6, 0xaa, 0x03, + 0x47, 0xa0, 0x1c, 0x9d, 0x97, 0x8d, 0xf5, 0x53, 0xb3, 0x46, 0x6f, 0x28, 0x0c, 0x6c, 0xcf, 0x73, 0x49, 0x19, 0x48, + 0x63, 0x2c, 0x84, 0x1b, 0x93, 0x4e, 0x15, 0xd4, 0x03, 0xd9, 0xb3, 0xbe, 0xc0, 0x78, 0x96, 0x98, 0xf0, 0x9d, 0x03, + 0x8e, 0xe7, 0xcb, 0x48, 0x2f, 0x5d, 0x13, 0xad, 0x68, 0x65, 0x21, 0xfe, 0xec, 0x64, 0xd6, 0x96, 0xb9, 0x0e, 0x7c, + 0x05, 0xa0, 0x3a, 0x99, 0x0a, 0x2a, 0x51, 0x25, 0x95, 0x50, 0x65, 0x3c, 0xd8, 0x94, 0xeb, 0x6a, 0xac, 0xbc, 0x71, + 0xef, 0x46, 0x86, 0x3f, 0x7b, 0x03, 0x4b, 0xeb, 0xae, 0xf0, 0xc1, 0xd9, 0x5f, 0x65, 0x90, 0x22, 0xed, 0x99, 0xb1, + 0x1b, 0x1b, 0xf3, 0xcc, 0x91, 0xd7, 0xd7, 0x8e, 0x89, 0xff, 0x5d, 0xb8, 0x63, 0x92, 0xdd, 0x7b, 0x14, 0x32, 0xb6, + 0xe5, 0x80, 0xa8, 0xdb, 0x3a, 0x0e, 0x47, 0x6a, 0xf8, 0x93, 0x9c, 0xe2, 0x3f, 0xae, 0x82, 0xa8, 0x5d, 0x69, 0x21, + 0xf9, 0x48, 0xcf, 0x21, 0x6b, 0x30, 0x9a, 0x34, 0x26, 0x30, 0xde, 0x03, 0xa3, 0x4c, 0x88, 0x89, 0x18, 0xdd, 0x84, + 0x09, 0x67, 0x9e, 0x01, 0xfe, 0xd2, 0x94, 0x85, 0x6d, 0x11, 0xb0, 0xdb, 0xc5, 0x6c, 0x2f, 0x3a, 0x4c, 0x18, 0xe4, + 0xdd, 0xe5, 0x0b, 0xf9, 0x47, 0xe2, 0x61, 0x73, 0xd9, 0xdf, 0xf2, 0x52, 0x44, 0x89, 0x2a, 0x42, 0x98, 0x06, 0x09, + 0x0d, 0x75, 0x56, 0x14, 0x69, 0xcc, 0x10, 0x6b, 0x98, 0xe3, 0x27, 0x1b, 0x45, 0x48, 0x85, 0x50, 0x28, 0x02, 0xd1, + 0x19, 0x22, 0x8c, 0x9c, 0x27, 0x0c, 0xf0, 0xae, 0x01, 0xd0, 0x12, 0xb4, 0x63, 0xc8, 0x96, 0x5b, 0x27, 0x84, 0x16, + 0x73, 0x89, 0xdf, 0xe7, 0x52, 0xca, 0x52, 0x7d, 0xad, 0x2e, 0x0b, 0xee, 0x25, 0x57, 0x2a, 0x6e, 0xa0, 0xe8, 0x28, + 0x9e, 0x86, 0x0f, 0x4c, 0x4d, 0x1f, 0xa5, 0x53, 0x1b, 0x6b, 0x9a, 0x27, 0xce, 0x39, 0xb2, 0x1f, 0xa8, 0xbb, 0xb9, + 0x3b, 0x41, 0xdc, 0xa9, 0x2a, 0xa1, 0x2c, 0xc3, 0x4d, 0xb7, 0x4c, 0x97, 0x92, 0x1a, 0x2e, 0xda, 0x92, 0xfc, 0xb6, + 0x29, 0x3e, 0x78, 0xc3, 0xf1, 0xe9, 0x7d, 0xc1, 0xec, 0x36, 0x3f, 0x7e, 0x32, 0x37, 0x02, 0xb3, 0xe0, 0xd1, 0xcd, + 0xc3, 0x1b, 0x36, 0x50, 0xc0, 0x8e, 0x04, 0x86, 0xae, 0x78, 0xa3, 0x35, 0xf1, 0x48, 0x63, 0x3d, 0x38, 0x78, 0x4f, + 0x79, 0x18, 0xb7, 0xa4, 0x8b, 0xac, 0xeb, 0xa2, 0xb2, 0xdf, 0x1f, 0x22, 0x87, 0xa7, 0xa7, 0x93, 0x3e, 0x69, 0x53, + 0x54, 0x5c, 0x91, 0xce, 0xcd, 0x47, 0x7f, 0x11, 0x2c, 0xcc, 0x63, 0xec, 0x9c, 0xc9, 0x38, 0x47, 0x67, 0xcc, 0xc6, + 0xc1, 0x0e, 0xc6, 0x8b, 0x7d, 0xcb, 0xef, 0x14, 0xc9, 0x2f, 0x65, 0x05, 0x68, 0x44, 0x21, 0xa8, 0xd3, 0x6e, 0x51, + 0x28, 0x81, 0x43, 0xff, 0xab, 0xac, 0xed, 0x7b, 0xe4, 0xe7, 0xb2, 0x8b, 0x25, 0x82, 0xd8, 0x8d, 0xcd, 0xea, 0x5c, + 0xdc, 0xad, 0x46, 0x36, 0xad, 0x5b, 0x48, 0xa8, 0x3f, 0xcc, 0xcc, 0xb3, 0x67, 0x7e, 0x35, 0x22, 0x78, 0x93, 0x6f, + 0x86, 0xf5, 0xcd, 0x98, 0xeb, 0xbf, 0x4a, 0xfa, 0x73, 0x25, 0x31, 0x8e, 0xdf, 0xc8, 0x80, 0xac, 0x39, 0x90, 0x05, + 0xa5, 0xe2, 0x56, 0x3e, 0xca, 0xe3, 0x3d, 0x7e, 0x14, 0x28, 0xa2, 0xc9, 0x94, 0x51, 0x72, 0x2d, 0x14, 0xf9, 0xc8, + 0xdb, 0xb3, 0xbb, 0x37, 0x9e, 0x8e, 0x1a, 0x2f, 0x37, 0x7f, 0xa0, 0xb7, 0x65, 0xab, 0x1b, 0x3f, 0x98, 0xbd, 0x2d, + 0xff, 0xf9, 0x88, 0x3a, 0x5d, 0x17, 0x19, 0xcf, 0x43, 0xbd, 0x85, 0x6c, 0x1a, 0xa9, 0x16, 0xdd, 0x46, 0x40, 0x37, + 0xe2, 0x98, 0x77, 0xdc, 0x6d, 0xc0, 0x17, 0xc0, 0xbb, 0x84, 0x81, 0x78, 0xff, 0xa0, 0xe7, 0xa6, 0xc9, 0xcb, 0xb3, + 0x29, 0x17, 0x77, 0x5e, 0xd9, 0xed, 0x1e, 0x80, 0xce, 0x4f, 0xab, 0x7f, 0xbc, 0xcf, 0xe1, 0x17, 0x3f, 0xf9, 0x67, + 0x95, 0x50, 0x41, 0xba, 0x6f, 0x8c, 0xab, 0xbc, 0x9c, 0xf5, 0xee, 0x7e, 0xd6, 0x6e, 0xbb, 0xe3, 0x49, 0x0d, 0xed, + 0xbf, 0x92, 0xef, 0x8a, 0x74, 0x1b, 0x65, 0xfc, 0x4b, 0xb0, 0xf8, 0x92, 0x85, 0xde, 0x0f, 0x40, 0xdc, 0x14, 0x69, + 0x0f, 0xe9, 0x6a, 0xc6, 0x0f, 0xe3, 0xdd, 0x68, 0xc7, 0xad, 0xab, 0x5d, 0x12, 0x2e, 0x6a, 0xc4, 0xfd, 0xbc, 0x76, + 0x33, 0xd7, 0x7b, 0x4c, 0xab, 0x69, 0x97, 0x7f, 0xe6, 0x20, 0xbc, 0xb4, 0x90, 0xb2, 0x36, 0xbd, 0xb6, 0x45, 0xe9, + 0x5a, 0x60, 0xf9, 0xcb, 0x59, 0x9b, 0x6e, 0x55, 0x68, 0xda, 0xa5, 0xca, 0xd7, 0xf8, 0x37, 0x4e, 0xc5, 0x2e, 0xe7, + 0x8f, 0xfe, 0x73, 0xf2, 0xf6, 0xf0, 0xd7, 0x92, 0x77, 0xcc, 0x45, 0x6e, 0xce, 0xdc, 0xdf, 0xc2, 0x15, 0x59, 0x16, + 0x17, 0xf9, 0xfc, 0xff, 0x95, 0x7b, 0xfc, 0xd7, 0xe1, 0xf9, 0xb9, 0x26, 0x64, 0x76, 0x54, 0xf3, 0x4a, 0x6a, 0x5e, + 0xfe, 0xdf, 0xe7, 0xc5, 0xd3, 0xab, 0x07, 0xa4, 0xc1, 0xf0, 0x8d, 0xd9, 0x66, 0x96, 0x95, 0xd1, 0xc2, 0x21, 0x1e, + 0xc3, 0xfb, 0x26, 0xaf, 0x41, 0x90, 0x37, 0x5d, 0x96, 0xd3, 0xe0, 0x6e, 0x2a, 0xc9, 0xec, 0x4a, 0x39, 0x2c, 0x6e, + 0xfd, 0x78, 0x59, 0x96, 0xcd, 0x65, 0x4f, 0xf3, 0x4d, 0xed, 0x85, 0xfc, 0xd3, 0x97, 0x54, 0xba, 0x04, 0xe7, 0x8f, + 0x7c, 0xbc, 0x5c, 0xfc, 0x9c, 0x8d, 0xcb, 0x8f, 0x09, 0x21, 0x0e, 0x69, 0x22, 0x4d, 0x03, 0xda, 0x31, 0x98, 0x0f, + 0x71, 0x6e, 0xb6, 0x39, 0xcc, 0x0d, 0xdf, 0xe3, 0x05, 0x60, 0x67, 0x1e, 0x5f, 0xd5, 0x8b, 0x81, 0x38, 0x1d, 0x87, + 0x40, 0x8e, 0x47, 0xff, 0xb7, 0x8e, 0x7e, 0xe8, 0x22, 0xbd, 0xc8, 0x2d, 0xdf, 0xba, 0x87, 0x95, 0xe6, 0xc6, 0xd1, + 0x68, 0xe4, 0x88, 0x6d, 0x76, 0x17, 0x81, 0x8f, 0xb1, 0xe8, 0xe4, 0xe0, 0x2d, 0xe2, 0xa5, 0x7e, 0x97, 0x98, 0x07, + 0x57, 0xf9, 0x77, 0x62, 0xf1, 0xa5, 0x38, 0xde, 0xdd, 0x3b, 0x02, 0xe0, 0xf5, 0xc4, 0x2b, 0x07, 0xa7, 0x65, 0x38, + 0x85, 0xa4, 0x4e, 0x54, 0xd6, 0x4a, 0x75, 0xcf, 0xbe, 0x8f, 0x17, 0x93, 0x08, 0xbc, 0x3f, 0xde, 0xf7, 0x09, 0x3b, + 0x5d, 0xa4, 0xe7, 0x78, 0xaf, 0x8c, 0x40, 0x80, 0x22, 0x0a, 0x90, 0x06, 0xf9, 0xa8, 0x6e, 0x91, 0xbf, 0xc0, 0x50, + 0xdf, 0x39, 0x5c, 0x7d, 0xae, 0x28, 0x90, 0x1e, 0xae, 0xd6, 0xc5, 0x7d, 0x0a, 0x50, 0x12, 0xdb, 0x84, 0x80, 0xed, + 0x3f, 0xbd, 0x6f, 0x07, 0xb6, 0xde, 0xa4, 0x5d, 0xcf, 0x20, 0x77, 0x6a, 0x62, 0xf7, 0x19, 0x46, 0xc6, 0x0b, 0xcf, + 0x0b, 0x9d, 0xf0, 0x28, 0xc7, 0x44, 0xaf, 0xa8, 0xac, 0xac, 0xe3, 0xce, 0x7c, 0xb0, 0xb2, 0xff, 0x5c, 0x42, 0x21, + 0x4c, 0x37, 0xe9, 0xcb, 0x2a, 0xa1, 0x4a, 0xf3, 0x0a, 0x6e, 0x65, 0x4c, 0x46, 0x2f, 0xa1, 0xce, 0xf8, 0x5d, 0x53, + 0x64, 0x8a, 0xb1, 0x0c, 0x98, 0xe9, 0x6f, 0xa6, 0x9c, 0x09, 0x00, 0xce, 0xe2, 0x32, 0x80, 0x04, 0xaa, 0xbe, 0xaa, + 0x95, 0x6f, 0x69, 0xc6, 0x29, 0x59, 0x47, 0xd9, 0x79, 0xae, 0x95, 0x52, 0x1e, 0x5f, 0x36, 0xb0, 0xe1, 0x4c, 0xc3, + 0x82, 0x2b, 0xf9, 0x64, 0xbb, 0x2f, 0x57, 0x73, 0x5d, 0xd6, 0xc3, 0x09, 0x30, 0xfb, 0xfd, 0x00, 0xfa, 0x96, 0x72, + 0xc5, 0x46, 0xd9, 0xcb, 0x3f, 0xf4, 0x06, 0x6b, 0xd0, 0xf0, 0xa1, 0x87, 0x29, 0x37, 0xbe, 0x50, 0xd4, 0x7f, 0x08, + 0x9c, 0x15, 0x23, 0x97, 0x6a, 0x4d, 0xa1, 0x97, 0x18, 0xa1, 0x9f, 0x65, 0x50, 0xb5, 0x7a, 0x63, 0x2a, 0x2a, 0x95, + 0xaf, 0xef, 0xb9, 0xc3, 0x95, 0xe5, 0xc0, 0x9d, 0xf9, 0xe4, 0x14, 0x90, 0x02, 0x44, 0x41, 0xac, 0x54, 0x7e, 0x1e, + 0x66, 0x9b, 0x90, 0xca, 0x20, 0x99, 0xbe, 0x50, 0x64, 0xdd, 0x37, 0xfa, 0x0b, 0x2b, 0xb6, 0xea, 0x25, 0x74, 0x0f, + 0x76, 0x78, 0xcf, 0x7d, 0x9b, 0xf7, 0x67, 0xcb, 0x59, 0xf5, 0xb4, 0xf4, 0xd6, 0x73, 0xb7, 0x6b, 0xe0, 0x52, 0xe7, + 0x00, 0x20, 0x2d, 0x97, 0x3a, 0x7f, 0xdf, 0xc4, 0xdd, 0xac, 0x36, 0x7e, 0x76, 0x51, 0x46, 0x47, 0x8f, 0x5b, 0xe9, + 0xe9, 0x91, 0x75, 0x61, 0x95, 0x74, 0x78, 0xdf, 0x44, 0xe0, 0xcb, 0x1f, 0xc3, 0xb0, 0x7a, 0x61, 0x7a, 0x6e, 0x50, + 0x9c, 0x09, 0x9b, 0x93, 0x7d, 0xf6, 0xce, 0xcd, 0x5b, 0x0d, 0x1f, 0xa2, 0x37, 0xe1, 0x73, 0x87, 0x7d, 0x2e, 0x02, + 0xe8, 0x36, 0x6b, 0x7e, 0xc6, 0x41, 0xd1, 0x2a, 0x54, 0x8b, 0x97, 0x25, 0xb0, 0x29, 0x59, 0xee, 0xe7, 0x78, 0x11, + 0x6c, 0x0a, 0xed, 0x8b, 0x20, 0x2f, 0x67, 0x54, 0xa1, 0x17, 0xd2, 0xbb, 0x0a, 0x5c, 0xb9, 0x15, 0x7c, 0x7f, 0xc5, + 0x60, 0x75, 0xd5, 0xb6, 0x14, 0x1f, 0xce, 0x18, 0xfd, 0xae, 0x02, 0xe6, 0xab, 0xaf, 0x98, 0xd9, 0xd0, 0xa7, 0x3d, + 0x0f, 0xab, 0xfb, 0xfe, 0xb5, 0xe5, 0x0b, 0x82, 0xef, 0xdf, 0x4c, 0x4d, 0xbb, 0x38, 0x9c, 0x12, 0x1b, 0x81, 0xb9, + 0x47, 0x88, 0xc3, 0x10, 0x69, 0x50, 0x77, 0x90, 0x6f, 0xef, 0x96, 0x24, 0x21, 0x4f, 0xd6, 0xbf, 0x78, 0xfc, 0xee, + 0x4c, 0x7a, 0x22, 0xbd, 0xf8, 0xe1, 0xe3, 0x75, 0x22, 0x2c, 0xdb, 0x0b, 0x35, 0xd9, 0xc1, 0xe3, 0x94, 0x5b, 0x79, + 0x80, 0x66, 0x0d, 0x5d, 0x74, 0xdb, 0x87, 0x74, 0x5c, 0x9c, 0x5f, 0x63, 0xe8, 0xfb, 0x06, 0xde, 0xcd, 0x0c, 0x0d, + 0x79, 0xcf, 0xd8, 0x5d, 0xf5, 0x81, 0x0a, 0x2b, 0xc9, 0x4b, 0xb9, 0x57, 0xf6, 0x5c, 0x74, 0xb5, 0x61, 0xe2, 0x1c, + 0x50, 0x7f, 0xb3, 0xcf, 0xcb, 0xae, 0x8a, 0x0f, 0xf8, 0x7a, 0xa5, 0x81, 0xaf, 0xeb, 0x0c, 0x35, 0x02, 0x3a, 0x48, + 0x91, 0x25, 0xe0, 0x33, 0xcc, 0xa8, 0x49, 0x38, 0xcd, 0xf4, 0x96, 0xf2, 0x3c, 0xca, 0x60, 0x51, 0x53, 0xba, 0xd0, + 0xe5, 0xdb, 0xae, 0x16, 0x73, 0x7a, 0x17, 0x13, 0xed, 0x52, 0xf3, 0xde, 0x7e, 0x00, 0x70, 0xb5, 0xdb, 0x90, 0x70, + 0x91, 0x7e, 0x14, 0xf7, 0xad, 0xf3, 0x63, 0xea, 0xeb, 0xe2, 0xe2, 0x11, 0x64, 0x2a, 0x98, 0x04, 0xb9, 0xe9, 0x73, + 0xfd, 0x17, 0x34, 0x31, 0x21, 0x3f, 0xf9, 0xb3, 0x04, 0x5f, 0xda, 0xb5, 0x5d, 0x0c, 0xc1, 0x47, 0x6a, 0x8d, 0xde, + 0x2d, 0x05, 0x64, 0x61, 0x3f, 0xec, 0x3d, 0xd7, 0x14, 0x64, 0xc7, 0x21, 0x69, 0x00, 0x7d, 0xdf, 0xa4, 0xe3, 0x17, + 0x16, 0xc6, 0x22, 0x91, 0x9a, 0xde, 0xc2, 0x76, 0x99, 0x6c, 0xa7, 0xaf, 0x6e, 0x6f, 0x19, 0x5f, 0x5d, 0xec, 0x7a, + 0x0a, 0xeb, 0x06, 0xb0, 0xc3, 0x46, 0x1b, 0x6f, 0xba, 0x80, 0xc3, 0x6d, 0x76, 0xc6, 0x94, 0x7a, 0x57, 0xdc, 0x24, + 0x0e, 0x03, 0xc1, 0x10, 0xf5, 0x22, 0x99, 0x45, 0xf9, 0x3d, 0xf5, 0x26, 0x1c, 0xf5, 0x90, 0xf6, 0x0f, 0x6e, 0xbe, + 0xff, 0x8f, 0x2a, 0x3d, 0x38, 0x1b, 0x06, 0x7e, 0xb2, 0x7b, 0x40, 0xf2, 0xd4, 0x54, 0xf4, 0x80, 0x26, 0x5b, 0x9e, + 0x04, 0xe2, 0xa6, 0x73, 0xed, 0xc3, 0xfe, 0x31, 0xe5, 0x1b, 0x02, 0x6a, 0x9e, 0x18, 0xa1, 0xda, 0x7a, 0xe4, 0x2f, + 0x6b, 0xa5, 0x37, 0xd6, 0x10, 0xcf, 0xaf, 0x08, 0xde, 0xaf, 0x5e, 0x1c, 0x7e, 0x2d, 0x69, 0xa0, 0xdc, 0x2e, 0x67, + 0xe9, 0xbf, 0xeb, 0x0a, 0x17, 0x02, 0x0f, 0xc9, 0xa7, 0x11, 0x92, 0x2b, 0x0b, 0x7c, 0xfc, 0xe2, 0x50, 0xe7, 0xd3, + 0xf7, 0xba, 0xf1, 0x59, 0xdd, 0x10, 0x85, 0x9c, 0x1f, 0xa0, 0xaa, 0x0d, 0x31, 0x46, 0x08, 0x17, 0x7c, 0xf4, 0xd1, + 0x65, 0x59, 0xa3, 0x25, 0x20, 0xed, 0xca, 0xe5, 0x8f, 0x17, 0x06, 0x5e, 0x2b, 0x7e, 0xcb, 0x61, 0x5e, 0xa6, 0x43, + 0x7c, 0xa5, 0xb1, 0x7d, 0x2d, 0x1d, 0x32, 0xd7, 0xd1, 0xa8, 0x08, 0x55, 0x15, 0xa9, 0xe7, 0xe2, 0xa3, 0xf5, 0xbb, + 0x6e, 0xe4, 0x33, 0x83, 0xc5, 0xa5, 0x65, 0x63, 0xc7, 0x49, 0x75, 0xc9, 0x33, 0x3c, 0x40, 0x67, 0xb0, 0xcf, 0xd9, + 0x76, 0xf1, 0x67, 0x95, 0xac, 0xe1, 0x00, 0x23, 0xb0, 0x07, 0x43, 0xae, 0x4a, 0x12, 0x64, 0x30, 0x36, 0x25, 0x97, + 0xa1, 0xe4, 0x7d, 0xbd, 0xb1, 0x59, 0x8e, 0xf2, 0xa0, 0xd0, 0x91, 0xe1, 0x8a, 0xff, 0xa9, 0xb7, 0x8a, 0x34, 0xbd, + 0xfc, 0xdc, 0x38, 0x5b, 0xe7, 0x74, 0xb3, 0x3b, 0xb2, 0xc3, 0x87, 0x51, 0x6e, 0x21, 0x4e, 0xa6, 0x79, 0x18, 0x09, + 0xac, 0x64, 0x6e, 0x9e, 0x0e, 0x80, 0xf8, 0x26, 0x33, 0x5a, 0xb7, 0xe4, 0x7f, 0xf2, 0xb5, 0xae, 0x23, 0x44, 0xb4, + 0xb1, 0xbe, 0xab, 0xe8, 0x0c, 0x12, 0x27, 0xb9, 0x41, 0x31, 0x9e, 0xaa, 0x98, 0x31, 0xc8, 0x96, 0xaa, 0x4e, 0xf2, + 0xfb, 0x4f, 0xbe, 0x4b, 0xa1, 0x37, 0xbd, 0x3d, 0x37, 0xeb, 0xb6, 0x93, 0xe5, 0x88, 0x1a, 0x29, 0x33, 0xbb, 0x31, + 0xe8, 0xa6, 0xa0, 0x10, 0x29, 0x29, 0xcf, 0x14, 0xe9, 0x18, 0x0e, 0xf7, 0xda, 0x1f, 0xe1, 0x89, 0xed, 0x58, 0xc2, + 0xda, 0x66, 0x81, 0x47, 0x80, 0xc0, 0x47, 0xfd, 0x16, 0x41, 0x34, 0xd5, 0x15, 0x15, 0x6a, 0x79, 0x63, 0x77, 0x76, + 0x74, 0x7b, 0x5a, 0x5b, 0xd0, 0x3e, 0x83, 0x3f, 0x15, 0x14, 0xdc, 0x76, 0xad, 0xe7, 0x64, 0x64, 0x45, 0xea, 0x42, + 0x30, 0x02, 0x32, 0xeb, 0x9f, 0x21, 0xe3, 0x53, 0x13, 0xa2, 0xee, 0x2f, 0x1b, 0x43, 0x8e, 0x84, 0x40, 0x80, 0xf0, + 0xb2, 0x7c, 0x96, 0xf0, 0x49, 0xa0, 0x08, 0x50, 0xf5, 0xb8, 0xf4, 0xca, 0x72, 0xa9, 0xd1, 0xf0, 0xa8, 0xd5, 0x80, + 0x6d, 0xbb, 0x40, 0xed, 0x80, 0x05, 0xd6, 0x4e, 0x61, 0x9d, 0x13, 0x52, 0x75, 0x29, 0x16, 0xdd, 0xaa, 0x2e, 0x52, + 0x9e, 0xcd, 0xeb, 0x4c, 0x11, 0x36, 0xad, 0x7f, 0xad, 0x7c, 0x99, 0x80, 0x68, 0x9b, 0xbf, 0x04, 0x6e, 0x8e, 0xcd, + 0xfe, 0x8f, 0x36, 0x13, 0xd3, 0x3a, 0xf5, 0x2a, 0x02, 0x94, 0x9d, 0x2a, 0xf1, 0x1a, 0x65, 0x0c, 0x4a, 0x50, 0xe7, + 0xc7, 0x5e, 0xa2, 0x82, 0x5c, 0x25, 0x7d, 0x31, 0x50, 0x80, 0x30, 0x5e, 0x3a, 0xe2, 0xa5, 0xab, 0xbc, 0xd8, 0x56, + 0xeb, 0x9c, 0x60, 0xec, 0xcd, 0xec, 0x05, 0xa4, 0x3e, 0x5d, 0xee, 0x24, 0x47, 0xd3, 0xc5, 0xb5, 0xcb, 0xab, 0x78, + 0xc8, 0x74, 0x59, 0x7c, 0x4c, 0x83, 0xa7, 0x2a, 0xe7, 0x89, 0x15, 0xc2, 0xff, 0xb6, 0x8c, 0x1b, 0xaf, 0x94, 0x69, + 0x81, 0x10, 0x6b, 0x49, 0x14, 0x38, 0xdf, 0x0c, 0x92, 0x87, 0xe5, 0x51, 0x69, 0x9a, 0xc7, 0xfe, 0xda, 0xd0, 0xec, + 0x49, 0xf6, 0x40, 0x92, 0x0f, 0xdb, 0xbe, 0x4b, 0x82, 0xb9, 0xef, 0x27, 0x1d, 0xc3, 0x44, 0x61, 0x1f, 0x34, 0xe4, + 0x71, 0xd5, 0x02, 0x08, 0x46, 0xee, 0x57, 0x5f, 0xcb, 0xdd, 0xb6, 0xed, 0x36, 0x08, 0x3e, 0xc7, 0x42, 0xc4, 0x5f, + 0x0c, 0x49, 0xf0, 0xed, 0xd5, 0x0b, 0x2a, 0x17, 0xab, 0x75, 0xc8, 0xbc, 0x3c, 0x25, 0xd9, 0xce, 0x93, 0xae, 0xef, + 0x9e, 0xf7, 0xfc, 0x8a, 0x88, 0xd3, 0x15, 0xcd, 0x4c, 0x9c, 0x23, 0xe9, 0xa8, 0xc4, 0x0b, 0xee, 0x0e, 0xea, 0xec, + 0xfd, 0x9c, 0xe2, 0x14, 0x93, 0xe6, 0x16, 0x15, 0x42, 0x17, 0x12, 0xba, 0xd6, 0xb9, 0x7c, 0x5d, 0x58, 0xbb, 0x79, + 0xa2, 0xf4, 0xfe, 0xa5, 0x99, 0x51, 0x54, 0xea, 0xe7, 0x62, 0x09, 0x24, 0x13, 0x72, 0xa2, 0xdf, 0xd8, 0xea, 0xa4, + 0xbb, 0x87, 0x6f, 0x6a, 0xa3, 0xc5, 0x3c, 0x88, 0x73, 0xc0, 0xca, 0x97, 0x61, 0x6f, 0x1b, 0x93, 0xe2, 0xf6, 0xd7, + 0x25, 0x64, 0xb5, 0xdd, 0x1f, 0x4a, 0x7f, 0xce, 0x05, 0x2e, 0xd1, 0x98, 0x18, 0x31, 0xc3, 0x2f, 0x44, 0x5a, 0xa3, + 0x44, 0xce, 0x3d, 0xce, 0x6d, 0x42, 0xfe, 0x2b, 0x53, 0x6f, 0xa4, 0xbb, 0x42, 0xc8, 0xff, 0xf3, 0x3c, 0xe2, 0x8e, + 0xe9, 0xe6, 0xde, 0xde, 0xc9, 0x30, 0x72, 0x0e, 0xcc, 0xda, 0x6e, 0xca, 0x2c, 0xdc, 0x45, 0x7a, 0x8b, 0x19, 0xd3, + 0xec, 0x10, 0xbc, 0x0c, 0x9d, 0x74, 0x52, 0x7c, 0xea, 0x00, 0xa1, 0xea, 0x08, 0x60, 0x4a, 0x16, 0xfa, 0x17, 0x28, + 0x5d, 0xbd, 0x58, 0xa6, 0x96, 0x4a, 0xcd, 0x75, 0x27, 0x16, 0x3f, 0xa1, 0xc0, 0x20, 0x7e, 0x71, 0xab, 0x35, 0x9d, + 0x1d, 0x52, 0x44, 0xa2, 0x27, 0xfd, 0x18, 0x1e, 0x63, 0xe5, 0x31, 0xeb, 0xa1, 0x50, 0x13, 0x5c, 0xef, 0x64, 0xd5, + 0xb3, 0x92, 0x20, 0x8d, 0x74, 0x0f, 0xb0, 0x37, 0x4f, 0xed, 0x51, 0xa2, 0x15, 0x02, 0x2f, 0x91, 0xc6, 0x0c, 0x89, + 0xf6, 0x21, 0xf6, 0x90, 0x98, 0x00, 0x6f, 0x0a, 0x26, 0xd8, 0x52, 0x68, 0x3b, 0x07, 0xce, 0x3b, 0x0a, 0x58, 0x9b, + 0x6b, 0xd4, 0x60, 0xe6, 0x91, 0x23, 0x26, 0xe2, 0x38, 0xfb, 0x5d, 0xd4, 0x21, 0x81, 0xe4, 0x10, 0xed, 0x9c, 0x6a, + 0x1a, 0xb4, 0x38, 0x73, 0x5e, 0x23, 0x57, 0x08, 0xc7, 0xa7, 0xa0, 0x8c, 0x23, 0xd8, 0x70, 0x7d, 0xcc, 0x25, 0xeb, + 0xb2, 0x22, 0x0a, 0x9b, 0x3b, 0x4b, 0xde, 0xaf, 0xe3, 0xf7, 0xa6, 0xb0, 0x92, 0x65, 0xe1, 0x9b, 0xa6, 0xd4, 0x33, + 0xe5, 0x73, 0x2f, 0xac, 0x4a, 0x7a, 0x76, 0x00, 0xf7, 0x88, 0xff, 0xc1, 0xe5, 0x66, 0xe4, 0xa7, 0x94, 0x82, 0x1a, + 0xf0, 0x47, 0x13, 0xda, 0x95, 0x0a, 0x8a, 0xc5, 0xc0, 0x48, 0xd3, 0x69, 0x5b, 0xa8, 0x97, 0x1a, 0x36, 0x30, 0xcc, + 0x63, 0xb2, 0x50, 0xe8, 0xd4, 0xfe, 0x86, 0xe7, 0xf3, 0x88, 0x46, 0xde, 0x4c, 0x1b, 0x64, 0xf9, 0x1d, 0xba, 0xd7, + 0x2a, 0x27, 0xf3, 0x6d, 0x05, 0x10, 0x3f, 0xf3, 0xb2, 0x60, 0x34, 0x54, 0x34, 0x29, 0x66, 0x30, 0x5c, 0x9a, 0x3f, + 0x71, 0x15, 0xa0, 0xc7, 0xf4, 0xd5, 0xda, 0xa2, 0x3a, 0xef, 0x40, 0xc4, 0x74, 0x1f, 0x94, 0x2a, 0x52, 0x5f, 0xe9, + 0x66, 0xab, 0xe3, 0x1c, 0xfc, 0xb1, 0xaa, 0xae, 0x20, 0xd1, 0x6e, 0x79, 0x34, 0xa6, 0xd1, 0xb1, 0x2f, 0x0e, 0xd9, + 0xb1, 0xc7, 0xf3, 0x0e, 0x45, 0xc8, 0xfd, 0xd9, 0x37, 0xa6, 0xf8, 0x24, 0x23, 0x69, 0x04, 0xfa, 0x0a, 0x84, 0xab, + 0x7e, 0xee, 0xae, 0xa8, 0xb0, 0xd5, 0xc8, 0x66, 0x41, 0x19, 0x86, 0xa8, 0xa6, 0xa7, 0x68, 0x1c, 0x78, 0x56, 0x90, + 0x88, 0x09, 0x01, 0x4a, 0xd8, 0xb5, 0x44, 0x0f, 0xfd, 0x1f, 0x66, 0x56, 0xbf, 0xf2, 0x86, 0xad, 0x4c, 0xeb, 0x00, + 0x52, 0x04, 0x84, 0x54, 0xca, 0xd5, 0xfd, 0x83, 0xb9, 0x70, 0x3c, 0x4a, 0x4c, 0x26, 0x3f, 0xcf, 0x3e, 0x80, 0x37, + 0x33, 0xbd, 0x3c, 0xf2, 0x13, 0x69, 0x62, 0x93, 0x7a, 0x4c, 0x6b, 0xa4, 0x76, 0xbb, 0x03, 0x5c, 0xad, 0xd2, 0x0b, + 0x53, 0xff, 0xa2, 0x08, 0x46, 0xff, 0x4a, 0x07, 0x69, 0xdd, 0xcb, 0x9c, 0x4b, 0xb0, 0x29, 0x7a, 0xdb, 0x06, 0x30, + 0xed, 0xdb, 0x52, 0x75, 0x23, 0x41, 0x8a, 0x6d, 0x53, 0xf8, 0xee, 0xf0, 0x12, 0x11, 0x8b, 0x33, 0x16, 0xab, 0xd5, + 0x1d, 0x2d, 0xe6, 0xc1, 0xf7, 0x53, 0x47, 0x10, 0xf6, 0xaf, 0xb0, 0x09, 0x6c, 0x3c, 0x40, 0x16, 0x7b, 0x90, 0x8e, + 0x58, 0xa9, 0xa6, 0x39, 0x8f, 0x56, 0x81, 0x95, 0xaa, 0x2c, 0xde, 0xc7, 0x95, 0xb4, 0xfb, 0x5a, 0x26, 0x0e, 0xa8, + 0xce, 0x21, 0xfc, 0xd6, 0xa2, 0x6f, 0x25, 0x64, 0x5e, 0xd7, 0x38, 0x02, 0xd4, 0x95, 0xb8, 0x12, 0x37, 0x0a, 0x92, + 0x91, 0x1f, 0x34, 0x93, 0x13, 0x74, 0x34, 0xf9, 0xf8, 0x81, 0x06, 0x1e, 0xba, 0xe7, 0x6f, 0xd4, 0x50, 0xec, 0xdb, + 0x55, 0x74, 0x28, 0xb4, 0x26, 0xd9, 0x7f, 0xf6, 0x9d, 0x69, 0xcd, 0x69, 0x46, 0x3d, 0x35, 0xc1, 0x9d, 0x7a, 0x5b, + 0x17, 0x5b, 0xa6, 0x71, 0xe4, 0x2e, 0xcc, 0x9c, 0xf1, 0xb5, 0xbd, 0x81, 0x38, 0xdf, 0x0b, 0x89, 0x9b, 0xe9, 0x88, + 0x29, 0xfd, 0xa4, 0x31, 0x02, 0x6a, 0x14, 0x1d, 0x6c, 0x64, 0xda, 0xb7, 0x02, 0x39, 0x9b, 0xa0, 0xa3, 0x2a, 0xa8, + 0xb6, 0x98, 0x99, 0xa5, 0x71, 0x6a, 0xa4, 0x05, 0x05, 0x2b, 0x8d, 0x41, 0x61, 0xa5, 0x2a, 0xc9, 0x5e, 0x94, 0x58, + 0x7a, 0x9e, 0xb3, 0xd0, 0xa1, 0x6c, 0x3a, 0x7c, 0x5a, 0x0b, 0x97, 0x84, 0xd1, 0xd6, 0xc2, 0x30, 0x6d, 0xb6, 0xd2, + 0xb6, 0xb2, 0xa2, 0x12, 0x2a, 0xb9, 0xbe, 0xa8, 0x24, 0x69, 0x1e, 0x61, 0x1c, 0x4f, 0x65, 0x76, 0x43, 0xf9, 0x0a, + 0x5b, 0xb7, 0xf1, 0xa1, 0xf0, 0x6f, 0x42, 0xc9, 0x6c, 0xc8, 0x80, 0x0c, 0x54, 0x12, 0xac, 0xe2, 0xf4, 0xf3, 0xe5, + 0x35, 0x67, 0x11, 0x97, 0x39, 0xf0, 0x6a, 0xea, 0xb5, 0x76, 0x1c, 0x4a, 0x7c, 0xed, 0xe4, 0x3f, 0xd3, 0xe4, 0xcf, + 0x12, 0x0e, 0xd7, 0xb9, 0xb2, 0xe2, 0x74, 0x58, 0xd0, 0x8f, 0xd8, 0xab, 0xcf, 0xd7, 0x4b, 0x62, 0xcb, 0xa3, 0x48, + 0xdd, 0x2b, 0x6d, 0xef, 0x3d, 0x1b, 0xa9, 0xd0, 0xac, 0xdd, 0x7d, 0xdf, 0x49, 0x5a, 0x65, 0x6a, 0xb5, 0x8b, 0x7b, + 0xd8, 0x40, 0x68, 0x6b, 0x52, 0x22, 0xee, 0xdd, 0xa4, 0x0c, 0x2f, 0x6d, 0x16, 0x40, 0xb5, 0x26, 0x14, 0xdf, 0x8d, + 0xeb, 0x44, 0xee, 0xc3, 0x33, 0x99, 0xbf, 0xdd, 0x7d, 0x30, 0xda, 0x0d, 0xec, 0x8a, 0xd0, 0x0f, 0xa2, 0x2d, 0x58, + 0x75, 0xe9, 0x8d, 0xba, 0xc0, 0x64, 0x51, 0xea, 0x60, 0xa4, 0x82, 0x2c, 0x5e, 0xb9, 0x03, 0xbb, 0x8e, 0x47, 0x10, + 0x40, 0x7f, 0xe3, 0xb8, 0xc5, 0x6d, 0x22, 0x15, 0xc1, 0x5d, 0x76, 0x9c, 0x54, 0x69, 0xbd, 0xcd, 0x8e, 0x63, 0xc1, + 0xd8, 0x52, 0xc8, 0xcc, 0x2a, 0x08, 0x5a, 0x09, 0xb4, 0xbe, 0x4a, 0x76, 0xba, 0x0c, 0xb3, 0x56, 0x14, 0xb0, 0x0f, + 0x2a, 0x39, 0xeb, 0x0f, 0x4a, 0x51, 0x5d, 0xc1, 0xf7, 0x71, 0x78, 0xfa, 0xdd, 0xc0, 0x01, 0x8b, 0xa1, 0x15, 0x82, + 0x23, 0xf6, 0x48, 0x87, 0x2d, 0xbd, 0xa9, 0x77, 0x7c, 0xae, 0xc2, 0x79, 0xf3, 0x58, 0xff, 0x07, 0xa9, 0x3e, 0xef, + 0xeb, 0x17, 0x38, 0xc1, 0x2f, 0x5e, 0x54, 0x8f, 0x77, 0xfc, 0xff, 0x06, 0x43, 0x54, 0x1d, 0xa6, 0xb6, 0xf8, 0x73, + 0x82, 0x74, 0x26, 0x0d, 0x7b, 0xb8, 0xbe, 0x92, 0x76, 0xbe, 0xa0, 0x1a, 0x7a, 0x64, 0x63, 0xb5, 0x1e, 0x94, 0x20, + 0x52, 0xde, 0xbb, 0x7d, 0x36, 0x2f, 0x25, 0xa5, 0x1a, 0xd1, 0x42, 0x4d, 0x7c, 0xb3, 0xe6, 0x4d, 0xb2, 0x16, 0x24, + 0xb1, 0xed, 0x59, 0x3b, 0xb2, 0x85, 0xf8, 0xfd, 0x5b, 0x8c, 0x26, 0x07, 0xf1, 0xde, 0xec, 0xba, 0x0c, 0xba, 0xd5, + 0xb3, 0xb4, 0x84, 0x55, 0x1b, 0xa8, 0x6a, 0xaa, 0xd2, 0x6c, 0x58, 0x85, 0x7c, 0x0e, 0xf5, 0xeb, 0x4a, 0x3a, 0xa7, + 0xb4, 0x10, 0x6a, 0x19, 0xf7, 0x44, 0xb2, 0x88, 0xf8, 0x58, 0x05, 0x3f, 0x29, 0xcc, 0xa9, 0xbb, 0x68, 0x44, 0x16, + 0xa3, 0x57, 0x6e, 0xc3, 0x69, 0xab, 0xa5, 0x4a, 0x40, 0xac, 0xdf, 0xb5, 0x1a, 0x67, 0xb3, 0xc2, 0x89, 0xa1, 0xef, + 0xff, 0xc4, 0x55, 0xe1, 0x4b, 0x10, 0xc6, 0xf1, 0x99, 0x24, 0x4b, 0xf1, 0x19, 0xaf, 0x3c, 0xf0, 0x0e, 0xac, 0xe8, + 0x6e, 0x5f, 0xf1, 0xfb, 0x4f, 0x57, 0x61, 0x85, 0x66, 0x59, 0x51, 0x6e, 0x5d, 0x63, 0x49, 0xdd, 0x23, 0xc7, 0x79, + 0x71, 0x0f, 0x70, 0x26, 0x34, 0xa3, 0x22, 0x4c, 0x69, 0x24, 0x2d, 0x3f, 0x53, 0x5b, 0xb1, 0xf4, 0x09, 0xc5, 0x12, + 0x01, 0x32, 0xf8, 0xfe, 0x93, 0x44, 0x57, 0x1e, 0xeb, 0x00, 0xff, 0xa8, 0x58, 0xb9, 0x2c, 0x66, 0x85, 0x86, 0xba, + 0x00, 0xc9, 0xfa, 0xea, 0x4a, 0xd6, 0xec, 0x6c, 0x43, 0x04, 0x95, 0xba, 0xeb, 0x20, 0x40, 0x6c, 0xd7, 0x08, 0x7c, + 0xf9, 0xd7, 0x68, 0x58, 0x6f, 0x65, 0x41, 0x1d, 0x36, 0xd9, 0x05, 0x01, 0xd1, 0xbd, 0xe8, 0x97, 0x9e, 0x1b, 0xff, + 0xd8, 0xf8, 0x64, 0x63, 0xf9, 0xf0, 0x33, 0x72, 0x2d, 0xaa, 0x87, 0xcc, 0x16, 0x80, 0x98, 0x8d, 0x34, 0x1b, 0x27, + 0xba, 0x6a, 0xef, 0x7b, 0x8d, 0xb2, 0x4d, 0x86, 0xed, 0x12, 0xb3, 0x78, 0xb0, 0xa8, 0x31, 0x65, 0x64, 0x63, 0x8f, + 0x7b, 0xe5, 0xc1, 0x5d, 0xf6, 0x41, 0x04, 0x9d, 0xcb, 0x76, 0xcc, 0xb4, 0x76, 0x38, 0xaf, 0x1a, 0xbb, 0x42, 0x66, + 0x05, 0x9b, 0xc4, 0x41, 0x00, 0xd9, 0x65, 0xdd, 0x05, 0x53, 0xce, 0x69, 0x71, 0xc3, 0x62, 0x0f, 0x36, 0x50, 0x16, + 0x3a, 0xb0, 0x25, 0xd4, 0x50, 0x0a, 0xd3, 0x58, 0x7a, 0xe0, 0x6c, 0x05, 0xe6, 0x5a, 0x8f, 0x63, 0x5d, 0xb3, 0x4e, + 0xd1, 0xa5, 0x02, 0xd2, 0xe2, 0xe8, 0xf9, 0x4d, 0x1f, 0xd2, 0xbe, 0xdb, 0xda, 0xf0, 0xbd, 0x6e, 0xbc, 0x26, 0xc3, + 0x4a, 0x79, 0x12, 0xed, 0x55, 0xfd, 0xf6, 0x02, 0xa3, 0x5a, 0xf8, 0xcc, 0xe5, 0x4b, 0x25, 0xff, 0x6e, 0x0d, 0x03, + 0xcd, 0x17, 0x0a, 0x5f, 0xf5, 0x04, 0x32, 0x2d, 0x69, 0x51, 0xf0, 0xce, 0xf8, 0x69, 0xb3, 0x05, 0xe3, 0xfe, 0xcd, + 0x4d, 0xc5, 0xb8, 0xfe, 0xed, 0x4d, 0xd3, 0xaf, 0x86, 0xc0, 0x6f, 0x14, 0x24, 0xdd, 0x87, 0xed, 0x11, 0x04, 0x88, + 0x7b, 0xab, 0x5c, 0x36, 0xb9, 0x7e, 0xf3, 0xb8, 0xa1, 0xaf, 0x6e, 0xf9, 0xc7, 0x1d, 0xe0, 0x59, 0x92, 0x93, 0xad, + 0x2d, 0x8b, 0x47, 0xce, 0xec, 0xee, 0x65, 0x1c, 0xff, 0x00, 0x38, 0x85, 0xd5, 0xad, 0xfc, 0xe9, 0xfd, 0xcc, 0x9e, + 0x52, 0x73, 0xbd, 0xf5, 0xe7, 0xab, 0x5f, 0xb9, 0x6d, 0x1e, 0xab, 0x53, 0xc3, 0xc6, 0x4d, 0x63, 0x49, 0x66, 0x4b, + 0x30, 0x33, 0x07, 0x29, 0x9c, 0xaf, 0xd5, 0xe7, 0x8c, 0xa3, 0xb8, 0xce, 0x09, 0x23, 0x6c, 0x63, 0x90, 0x1f, 0xbf, + 0x24, 0x96, 0x92, 0xf9, 0xc7, 0xed, 0xca, 0x18, 0x26, 0x91, 0x6e, 0x4f, 0xbd, 0x97, 0xa9, 0xce, 0x29, 0xdb, 0x63, + 0x1e, 0x9b, 0xe0, 0x67, 0xd5, 0x23, 0xd0, 0x0a, 0xfc, 0x0b, 0x02, 0xb6, 0xbb, 0x2c, 0xb3, 0x07, 0x9a, 0x37, 0xff, + 0x03, 0x78, 0x23, 0x3a, 0x65, 0x61, 0x27, 0xbb, 0xbe, 0xf9, 0x5d, 0x87, 0xc3, 0x95, 0x61, 0x89, 0x1b, 0xc6, 0x30, + 0x60, 0x1c, 0xba, 0xb5, 0xb5, 0x27, 0xb5, 0x1b, 0x1c, 0xa4, 0x8a, 0xf7, 0x50, 0x8a, 0x75, 0x34, 0x2f, 0x2c, 0xff, + 0x28, 0x07, 0xca, 0x0a, 0x03, 0xf2, 0x60, 0xd8, 0xf9, 0x98, 0x35, 0x52, 0x0d, 0x5d, 0xba, 0x8e, 0x2b, 0xad, 0xb1, + 0x21, 0x1f, 0x33, 0xec, 0x7e, 0xef, 0x1c, 0x05, 0xed, 0xe9, 0x7a, 0xcb, 0x81, 0x33, 0xac, 0xbd, 0x2f, 0xe3, 0x3c, + 0xf5, 0x72, 0xc1, 0xce, 0xd4, 0xd0, 0x9f, 0xf7, 0x9b, 0xac, 0xa6, 0x60, 0xa3, 0x23, 0xa8, 0xd3, 0x4f, 0x2e, 0x4a, + 0x5c, 0x65, 0x46, 0xd6, 0xfd, 0x96, 0x54, 0x67, 0x82, 0x83, 0xac, 0x2b, 0x94, 0xdf, 0xc5, 0x99, 0xd0, 0x87, 0x26, + 0x35, 0x8b, 0x64, 0xe3, 0x7d, 0x94, 0x1e, 0x18, 0x22, 0x0b, 0x3d, 0x6e, 0xd6, 0x9e, 0xaf, 0x19, 0x27, 0xb1, 0xfc, + 0xd7, 0x85, 0xd3, 0x76, 0xab, 0xf6, 0x08, 0x06, 0x81, 0xe7, 0x5f, 0x45, 0xcc, 0xb6, 0x1a, 0xd6, 0x9d, 0x99, 0xa9, + 0xaa, 0x97, 0xeb, 0xd5, 0xdc, 0x5a, 0x8f, 0x09, 0x15, 0x54, 0x5e, 0xaa, 0xae, 0x32, 0x26, 0x32, 0xf2, 0x63, 0x41, + 0x39, 0xba, 0xba, 0xcd, 0x73, 0xde, 0xa3, 0x3d, 0x8b, 0xdc, 0x0c, 0x80, 0x91, 0x4e, 0xc8, 0x30, 0xe1, 0x16, 0x66, + 0x3a, 0xb2, 0x5a, 0x55, 0x16, 0xf0, 0x51, 0xc3, 0x17, 0x1d, 0xb4, 0xc0, 0xe4, 0xd5, 0x13, 0x87, 0xb3, 0x42, 0x8c, + 0x14, 0xf7, 0xb1, 0x9f, 0x10, 0xf3, 0xc7, 0x69, 0x26, 0xa6, 0x6a, 0xd6, 0x3e, 0xef, 0x7e, 0x07, 0x42, 0x13, 0x43, + 0x74, 0x58, 0x44, 0xaf, 0x43, 0x01, 0x9b, 0xe4, 0xb5, 0x55, 0xb5, 0xc8, 0xf0, 0xeb, 0x81, 0xc6, 0x32, 0x06, 0x21, + 0xcc, 0x25, 0x30, 0xab, 0xfd, 0x74, 0xdb, 0x05, 0x65, 0xa3, 0x48, 0x2b, 0x9c, 0xac, 0x57, 0xac, 0x35, 0xb1, 0x16, + 0x96, 0xe3, 0xa2, 0x43, 0x71, 0x15, 0x1a, 0xb1, 0x8a, 0xa8, 0x75, 0x89, 0x9f, 0xec, 0x14, 0x8d, 0x82, 0xb8, 0x6c, + 0x09, 0x22, 0x6a, 0x72, 0x72, 0xd7, 0x43, 0xea, 0x13, 0x2b, 0xa4, 0x29, 0x41, 0xf8, 0xce, 0x13, 0x94, 0x31, 0x02, + 0xb7, 0x55, 0x6a, 0x8c, 0x0d, 0x25, 0x99, 0x83, 0xc1, 0xf0, 0xcd, 0x04, 0x27, 0x7a, 0x09, 0x65, 0x46, 0xab, 0xe4, + 0x3e, 0x66, 0x4c, 0x63, 0x29, 0x27, 0x33, 0xa3, 0x6f, 0x58, 0xf8, 0xb3, 0x74, 0x21, 0xe7, 0xce, 0x5d, 0x5d, 0x9e, + 0xa9, 0xaf, 0xc8, 0xf3, 0xb9, 0x2d, 0x5c, 0x4b, 0xc6, 0x50, 0x7b, 0xd4, 0x94, 0xad, 0x78, 0xc3, 0x48, 0xaa, 0x71, + 0xfc, 0xaa, 0x97, 0x22, 0xac, 0xbb, 0x62, 0x78, 0xbd, 0xdd, 0x65, 0xe6, 0xda, 0x16, 0xd3, 0x5f, 0xcb, 0xfb, 0x19, + 0x5a, 0x0f, 0x7c, 0x35, 0x74, 0x73, 0x58, 0xf3, 0xfb, 0xa2, 0xdc, 0x23, 0x2c, 0xb7, 0x7f, 0x27, 0xc6, 0xed, 0xeb, + 0x5b, 0x30, 0x58, 0xc8, 0xe7, 0x66, 0x29, 0x6e, 0xb0, 0x7a, 0x90, 0x2e, 0x28, 0x1c, 0x89, 0xa9, 0x5c, 0xbd, 0x6c, + 0xc5, 0x4d, 0xed, 0x76, 0x9b, 0xb1, 0x4e, 0xa4, 0x56, 0xbe, 0x41, 0xb1, 0x6f, 0x7c, 0x81, 0xed, 0x8f, 0x30, 0xb4, + 0xeb, 0x15, 0xe7, 0xb6, 0xfa, 0xb7, 0xbc, 0xe3, 0xf7, 0xfd, 0x61, 0x13, 0x3a, 0xfe, 0x74, 0x7b, 0xe8, 0x86, 0x07, + 0xd2, 0x77, 0x69, 0x5f, 0x76, 0xa5, 0xa8, 0xbf, 0xe4, 0xc0, 0xa9, 0xf3, 0x63, 0x74, 0x5b, 0xf5, 0xa6, 0xde, 0xc7, + 0x11, 0x5e, 0x2a, 0xff, 0xc3, 0xda, 0xe2, 0x3e, 0xcd, 0x47, 0x7b, 0xde, 0x7a, 0xf2, 0xab, 0xdb, 0x74, 0x17, 0x56, + 0x35, 0x7f, 0x2b, 0x53, 0x1a, 0x2f, 0xce, 0x39, 0x60, 0xf6, 0x4f, 0xd4, 0x64, 0x0f, 0x91, 0xa9, 0xe4, 0x38, 0xae, + 0x62, 0x51, 0xeb, 0x49, 0xa1, 0x11, 0x79, 0xc3, 0xd5, 0x9e, 0x47, 0x83, 0x90, 0xd8, 0x01, 0x22, 0x3f, 0x16, 0x85, + 0xa1, 0x23, 0x16, 0x91, 0x76, 0x8d, 0xcf, 0x8b, 0xfa, 0x08, 0x85, 0x58, 0x4d, 0x84, 0x87, 0x05, 0x79, 0x1f, 0x01, + 0x54, 0xda, 0x4b, 0x5a, 0x5b, 0xe9, 0x20, 0xdb, 0x57, 0x82, 0x64, 0x72, 0x60, 0x24, 0xbd, 0x83, 0xd8, 0xce, 0x79, + 0x15, 0x2e, 0xbf, 0x98, 0x9b, 0x42, 0xee, 0xba, 0xca, 0x97, 0x3e, 0x69, 0x6c, 0x72, 0x80, 0xa3, 0xc2, 0xda, 0x57, + 0x4e, 0xc7, 0x41, 0x1f, 0xc4, 0x5e, 0xfe, 0x77, 0x16, 0xb8, 0x64, 0xdd, 0x05, 0xac, 0x97, 0xbe, 0xcf, 0xc3, 0x84, + 0x12, 0x6a, 0xd2, 0xb2, 0x44, 0x17, 0x36, 0x28, 0x55, 0xda, 0x6f, 0x21, 0xe2, 0xb0, 0xc5, 0x97, 0xdc, 0xa6, 0x51, + 0xb7, 0x52, 0xae, 0x6f, 0xe7, 0x94, 0x43, 0xeb, 0x8d, 0x1d, 0xc3, 0xd6, 0x62, 0xbc, 0x70, 0x18, 0x14, 0xa2, 0xa1, + 0xc6, 0x25, 0xcd, 0x57, 0x50, 0x6b, 0xe4, 0x8e, 0x45, 0x4b, 0x32, 0x9c, 0x3e, 0x6e, 0x39, 0x58, 0xa6, 0x81, 0x18, + 0xce, 0xa7, 0x9e, 0xbc, 0x26, 0xf9, 0x40, 0xc1, 0x0d, 0x9a, 0x65, 0x55, 0xd8, 0x1d, 0xd0, 0xbc, 0x0e, 0x1a, 0xad, + 0xa4, 0xc9, 0xa8, 0x4a, 0xba, 0x9f, 0xa6, 0xf8, 0x5d, 0xc6, 0xba, 0x57, 0x94, 0x12, 0xc6, 0xa8, 0xfe, 0xd0, 0x28, + 0x25, 0x07, 0x37, 0xd9, 0xb2, 0x27, 0xd4, 0x25, 0x62, 0xa2, 0x3c, 0x4f, 0xa1, 0x2b, 0xb4, 0x32, 0x72, 0xa8, 0xae, + 0x78, 0x83, 0x2c, 0x0e, 0x76, 0x96, 0x22, 0x99, 0x0f, 0x3a, 0x52, 0xef, 0x13, 0x4d, 0x21, 0x9c, 0xab, 0x64, 0x74, + 0xe3, 0xee, 0x94, 0x1e, 0x24, 0x70, 0xe2, 0x42, 0x47, 0xdb, 0xa1, 0xd7, 0x02, 0x76, 0xa3, 0x12, 0x7a, 0x8a, 0xdf, + 0xe9, 0xf3, 0x2c, 0x78, 0x3b, 0x12, 0xdb, 0x46, 0x31, 0xe6, 0xa8, 0x3a, 0xf5, 0x07, 0x6b, 0xdb, 0x71, 0xdf, 0x64, + 0xc3, 0x2f, 0x26, 0x7f, 0xd4, 0x41, 0x70, 0xcc, 0x3b, 0x59, 0x0e, 0x04, 0x32, 0x80, 0x4a, 0x27, 0x86, 0xf7, 0xc5, + 0x2e, 0x07, 0x85, 0x5f, 0xf5, 0x32, 0x57, 0xda, 0x96, 0x88, 0x8b, 0x8a, 0x83, 0x6f, 0x70, 0x3d, 0xa6, 0x7a, 0x2f, + 0x1d, 0x02, 0xe3, 0x1b, 0xa9, 0x70, 0x73, 0xdf, 0x0a, 0x03, 0x1d, 0x08, 0xca, 0xd9, 0xa8, 0x51, 0xa7, 0x3e, 0x5f, + 0x2d, 0xc8, 0x0b, 0x3c, 0x56, 0x8a, 0x63, 0xd7, 0x75, 0x2f, 0x3c, 0x96, 0x62, 0x3f, 0xa8, 0x50, 0xfe, 0xe7, 0x08, + 0x50, 0x89, 0x00, 0x46, 0xad, 0xd8, 0xca, 0xee, 0x7f, 0x31, 0x5d, 0xa6, 0xba, 0xa4, 0x48, 0xfd, 0x95, 0xe5, 0x24, + 0x7f, 0xe4, 0x61, 0x8f, 0xca, 0xc6, 0x83, 0x2d, 0x46, 0x81, 0x03, 0x78, 0x98, 0xa4, 0xf0, 0x56, 0xc6, 0x78, 0x5d, + 0xc5, 0x5a, 0x23, 0x15, 0x82, 0x64, 0x66, 0xb7, 0x8d, 0x7c, 0x91, 0x9f, 0x26, 0x41, 0x13, 0x3f, 0xa7, 0xde, 0x2b, + 0x4c, 0x3b, 0x76, 0xd6, 0x12, 0x05, 0xf4, 0xf2, 0x0e, 0xa1, 0x43, 0x56, 0xf1, 0xe5, 0xd4, 0x9a, 0x45, 0x40, 0x62, + 0x71, 0x6d, 0x7c, 0x4d, 0xb3, 0x7d, 0x9e, 0xc5, 0x08, 0xcb, 0x2f, 0xa8, 0x82, 0xcb, 0x14, 0xa8, 0x95, 0xda, 0xb3, + 0xee, 0x30, 0xd8, 0xa1, 0x2c, 0x63, 0x7a, 0x11, 0xb2, 0x28, 0xd2, 0xc4, 0x5a, 0xed, 0x62, 0x34, 0x20, 0xc1, 0x25, + 0x4c, 0x54, 0x28, 0x23, 0xcb, 0x18, 0x90, 0xe6, 0x96, 0xb5, 0x7d, 0x91, 0x51, 0x41, 0xbd, 0xfd, 0xcf, 0xac, 0xf6, + 0x3d, 0x2c, 0xd2, 0xf6, 0x4a, 0xba, 0x7e, 0xff, 0xdb, 0x4d, 0xe8, 0xf2, 0x45, 0xdf, 0x3d, 0x7c, 0xc5, 0x9a, 0xed, + 0x0d, 0x7c, 0xe9, 0xc3, 0xa0, 0x49, 0x99, 0x1c, 0x0a, 0x03, 0xcd, 0x32, 0x6e, 0x44, 0x6b, 0x07, 0x3c, 0xb2, 0xc3, + 0xb2, 0x89, 0xbc, 0xce, 0x6b, 0xaa, 0x67, 0x57, 0xa4, 0x61, 0x96, 0x26, 0xc5, 0x05, 0xa0, 0xb7, 0xbe, 0xd2, 0x35, + 0x55, 0x23, 0x4b, 0x60, 0x82, 0x62, 0x10, 0x6f, 0x4e, 0x65, 0x97, 0x36, 0xba, 0xf0, 0x28, 0x6f, 0x62, 0xac, 0x1f, + 0xb1, 0xdd, 0x01, 0x81, 0x4a, 0xd5, 0x02, 0x75, 0x2f, 0x0c, 0xe6, 0xe4, 0xaa, 0xa3, 0xda, 0xca, 0x48, 0x90, 0x4d, + 0xc3, 0x36, 0xbf, 0xd0, 0x70, 0x47, 0xc9, 0x26, 0x41, 0x52, 0xc8, 0x26, 0x63, 0xce, 0x8b, 0xda, 0xbd, 0x22, 0x66, + 0xa2, 0x4f, 0x1e, 0xdb, 0x39, 0xc8, 0x74, 0xb7, 0xcf, 0xe9, 0x63, 0x95, 0xc0, 0xe1, 0x9e, 0x46, 0x31, 0x3b, 0x5a, + 0xe1, 0xcf, 0x0b, 0xda, 0x9a, 0x61, 0xec, 0x21, 0x5c, 0xbd, 0x95, 0x12, 0x48, 0xdc, 0x8b, 0x2a, 0x38, 0xdb, 0x90, + 0xf4, 0xdb, 0xd1, 0x67, 0x4a, 0x8e, 0xe4, 0xca, 0x7e, 0x41, 0x5b, 0x27, 0x4e, 0x7c, 0x04, 0xe7, 0xed, 0xd6, 0x0b, + 0x43, 0x4f, 0x5b, 0xba, 0x0b, 0x5f, 0x16, 0xf7, 0x72, 0x75, 0x46, 0x3d, 0xb8, 0x8e, 0x4b, 0xb5, 0x20, 0x11, 0x2c, + 0x5a, 0xe7, 0x22, 0x5d, 0xe0, 0xe5, 0x78, 0xe4, 0xfc, 0x54, 0xc4, 0xae, 0xa0, 0x85, 0xf8, 0x90, 0x89, 0x8a, 0xf5, + 0xd6, 0xd1, 0x9f, 0xb8, 0x27, 0xd2, 0x20, 0xb7, 0xeb, 0xd1, 0x8e, 0xec, 0xe1, 0x47, 0xb5, 0xe4, 0x8a, 0xc2, 0x5e, + 0x25, 0x3b, 0xdf, 0xf5, 0x1a, 0x33, 0xeb, 0x9b, 0x65, 0x1f, 0x42, 0xb0, 0x80, 0xec, 0x14, 0xdf, 0xcb, 0x0b, 0xc8, + 0xbf, 0xc8, 0x58, 0x66, 0x31, 0x30, 0x93, 0x61, 0xc3, 0xe0, 0x1f, 0xb4, 0xa8, 0xd4, 0xcb, 0xe9, 0x38, 0xb8, 0x23, + 0x8e, 0x86, 0x43, 0x32, 0x55, 0x25, 0xdd, 0x3f, 0x18, 0x65, 0x5d, 0x0a, 0x27, 0x98, 0x64, 0xda, 0xfe, 0x15, 0xb4, + 0xda, 0x35, 0xef, 0x48, 0x72, 0x22, 0x3b, 0x53, 0xbb, 0xa6, 0x71, 0x43, 0xeb, 0x96, 0xce, 0x1d, 0xbd, 0x7b, 0x06, + 0x0f, 0xac, 0xbc, 0xe2, 0x6d, 0x49, 0xe2, 0x9d, 0x40, 0x85, 0x77, 0x03, 0x55, 0xde, 0x0b, 0xb4, 0xf1, 0xbe, 0xa4, + 0x9d, 0x0f, 0x02, 0x19, 0x5b, 0x88, 0xb9, 0xd5, 0x5c, 0x37, 0xb7, 0x9e, 0x8b, 0xb5, 0x7e, 0x30, 0x48, 0xb5, 0x1b, + 0xff, 0x9c, 0x3c, 0xfb, 0x52, 0xb7, 0xdd, 0x8e, 0xfb, 0xf9, 0xfe, 0x69, 0xb4, 0xb7, 0x3f, 0x99, 0x42, 0xe7, 0x45, + 0x12, 0x69, 0x7c, 0xee, 0xf5, 0x30, 0x04, 0xeb, 0xdc, 0x18, 0x7d, 0xdd, 0x05, 0x0d, 0xe5, 0x2e, 0x6c, 0x97, 0x7f, + 0xee, 0xfd, 0xc7, 0x93, 0x5f, 0x15, 0xf5, 0xd8, 0xfa, 0x50, 0x9a, 0xc5, 0x65, 0x00, 0xae, 0x3b, 0xd1, 0x78, 0xe5, + 0x82, 0x37, 0x86, 0xfe, 0xcc, 0x92, 0x96, 0x98, 0x47, 0x44, 0x3d, 0xd1, 0x12, 0xd7, 0x94, 0x49, 0x9f, 0x87, 0x2e, + 0xb1, 0xe4, 0xc8, 0x0d, 0xfb, 0x5b, 0xff, 0x85, 0x86, 0x3b, 0xad, 0xc6, 0x54, 0x8e, 0xfd, 0xfd, 0xb5, 0x81, 0xea, + 0x72, 0x28, 0xcd, 0xa6, 0x0f, 0x09, 0x13, 0xf5, 0x71, 0x0c, 0x77, 0x6e, 0x0c, 0x17, 0x78, 0x79, 0xb5, 0xa0, 0x5b, + 0x6d, 0xc0, 0x00, 0xcf, 0x79, 0x03, 0x50, 0xc9, 0x08, 0xfc, 0x0b, 0xde, 0xaf, 0x5a, 0x94, 0xe1, 0x8b, 0xd1, 0xef, + 0xce, 0xaf, 0xb6, 0x1f, 0x88, 0x13, 0x1e, 0x2d, 0x56, 0xe8, 0x9a, 0x99, 0xff, 0xb0, 0xc2, 0x7a, 0x8e, 0xbd, 0x9b, + 0xaf, 0x72, 0xde, 0xda, 0x0b, 0xe8, 0xed, 0xae, 0x40, 0x88, 0x40, 0xa3, 0xab, 0xc3, 0x59, 0xdf, 0xe6, 0x8f, 0x1f, + 0x52, 0x36, 0x13, 0x06, 0xe0, 0xd3, 0xca, 0x87, 0x7f, 0x37, 0x7f, 0x53, 0xbc, 0x48, 0xe1, 0x7e, 0xfd, 0xbe, 0x2a, + 0xc2, 0x7f, 0x61, 0x60, 0x7c, 0xc7, 0xc9, 0x05, 0x79, 0x6c, 0xde, 0xae, 0x2c, 0xef, 0xd0, 0xba, 0x68, 0xb1, 0xaf, + 0xcd, 0x13, 0x75, 0xf3, 0xf9, 0x27, 0xaa, 0x39, 0xb7, 0xab, 0xc7, 0xeb, 0xe6, 0xf7, 0xbb, 0xa9, 0x39, 0x7f, 0xc8, + 0x5f, 0xdd, 0x3f, 0x3a, 0xba, 0x6a, 0x38, 0x18, 0x5d, 0x7f, 0x9d, 0x65, 0xbb, 0x61, 0xfe, 0x7e, 0xd1, 0xca, 0x51, + 0x62, 0x95, 0x9a, 0xe5, 0x0f, 0x7b, 0x1f, 0xf3, 0x69, 0x5a, 0xd7, 0xbb, 0x5f, 0xbf, 0xc0, 0xfc, 0x0f, 0x71, 0x23, + 0xda, 0xc3, 0xe3, 0x3f, 0x1b, 0xff, 0xb4, 0x59, 0x73, 0x12, 0x7a, 0x32, 0xd6, 0x2a, 0x88, 0x1a, 0xe3, 0xe9, 0xf9, + 0xc8, 0x90, 0xc6, 0x7f, 0x7a, 0x52, 0x4e, 0x98, 0xe5, 0xc4, 0xd2, 0x7d, 0x4b, 0x78, 0x28, 0x15, 0xe5, 0x46, 0x71, + 0x3c, 0x26, 0xfc, 0xaf, 0x3d, 0xb9, 0x2d, 0x56, 0x29, 0x33, 0x80, 0xfb, 0xa1, 0xe6, 0xfb, 0xc5, 0x75, 0x32, 0x08, + 0xd2, 0x84, 0x89, 0x19, 0x83, 0x31, 0x51, 0x4e, 0xdd, 0x50, 0x08, 0xbe, 0x91, 0x53, 0x8a, 0x9c, 0x5a, 0xba, 0x3f, + 0x11, 0x1e, 0x0e, 0xcf, 0xee, 0x86, 0xa3, 0xdd, 0xcf, 0x1f, 0xb8, 0x9d, 0xe4, 0xd4, 0x98, 0x2f, 0x4f, 0x8d, 0xc6, + 0x9e, 0x01, 0x73, 0xba, 0x40, 0xa7, 0xd5, 0x33, 0xa4, 0xfd, 0x62, 0x20, 0x18, 0xba, 0xf2, 0xd0, 0x76, 0xf1, 0x6d, + 0x8b, 0xcb, 0x8f, 0x0d, 0x7a, 0xcd, 0xb0, 0x1a, 0xfc, 0x53, 0x03, 0xd6, 0x18, 0x13, 0x71, 0x8c, 0x09, 0x4c, 0xf9, + 0x96, 0x66, 0xdd, 0x92, 0x1d, 0x6c, 0xec, 0x29, 0xe5, 0x31, 0x52, 0x32, 0x87, 0xbc, 0x6c, 0xda, 0x98, 0x1b, 0x3c, + 0x2b, 0x9f, 0xe7, 0x76, 0xd2, 0x8e, 0xd0, 0x48, 0xc8, 0xf7, 0xac, 0xd8, 0xa4, 0xe8, 0xcc, 0x21, 0xee, 0x6c, 0x9d, + 0xcd, 0x31, 0x3e, 0x71, 0x44, 0x94, 0xdd, 0x7b, 0xd1, 0xd1, 0xbe, 0xd2, 0x17, 0xe4, 0x6c, 0x2e, 0xbf, 0xcd, 0x31, + 0xcf, 0xf2, 0xe8, 0x91, 0xf4, 0x42, 0xdf, 0x4b, 0x33, 0x8e, 0xc7, 0xbc, 0x6a, 0x69, 0x9e, 0xdd, 0x83, 0x78, 0x46, + 0x21, 0x68, 0x33, 0x4c, 0x7f, 0x7c, 0x33, 0x9f, 0x22, 0x75, 0x2f, 0xe3, 0x5e, 0x36, 0x0d, 0xe8, 0xb0, 0xa1, 0x03, + 0xaa, 0x42, 0x82, 0xa9, 0x55, 0xe8, 0x77, 0x2b, 0x2e, 0xb3, 0x55, 0x5a, 0xbc, 0x45, 0x73, 0x77, 0x65, 0x12, 0x97, + 0x11, 0xfa, 0xdd, 0xf5, 0x45, 0xb2, 0x3e, 0x03, 0xc6, 0x2d, 0x36, 0x14, 0xb3, 0xff, 0x58, 0xea, 0xf1, 0x89, 0x96, + 0x91, 0x81, 0x7d, 0x7d, 0x79, 0xee, 0xae, 0xb5, 0x67, 0x1b, 0x15, 0xb1, 0x31, 0xc5, 0xdc, 0xdc, 0xfa, 0x79, 0xb6, + 0x22, 0x99, 0xdc, 0x36, 0xe1, 0x0c, 0x98, 0xa3, 0x6b, 0xb8, 0x2b, 0x88, 0x71, 0x16, 0x40, 0x43, 0xe1, 0x6c, 0xdf, + 0x84, 0xcb, 0x0b, 0x49, 0x6c, 0x8c, 0x12, 0x7d, 0xe9, 0x7f, 0x77, 0x7e, 0x6a, 0xd0, 0x0f, 0x92, 0xd0, 0x73, 0xef, + 0xd1, 0xe9, 0xf6, 0xa7, 0xf9, 0x54, 0xfd, 0xac, 0xb5, 0x8d, 0x2f, 0xa0, 0x4f, 0x7d, 0x68, 0x79, 0xfb, 0x98, 0x51, + 0x80, 0x95, 0x94, 0xe2, 0x6b, 0x47, 0x75, 0x4c, 0xfd, 0x2d, 0x62, 0xea, 0xf8, 0x8d, 0x91, 0x47, 0xdd, 0xce, 0xa5, + 0x8f, 0x79, 0x33, 0xed, 0xb4, 0x67, 0x09, 0x38, 0xc7, 0x7b, 0xb1, 0xa5, 0x27, 0xbd, 0xee, 0x0b, 0x0e, 0x6c, 0x76, + 0x15, 0xf3, 0x36, 0xd7, 0xd0, 0x66, 0xed, 0xe6, 0xef, 0x6a, 0xec, 0x95, 0xf5, 0x56, 0x0f, 0x92, 0xad, 0xbe, 0xcc, + 0xf3, 0xf3, 0x6b, 0x7e, 0x5b, 0x2a, 0x95, 0xb8, 0x53, 0xc6, 0x77, 0xde, 0xff, 0xbe, 0x86, 0xea, 0xd4, 0x53, 0x46, + 0x29, 0xcc, 0x08, 0xcb, 0x27, 0xcf, 0xd3, 0xf2, 0x97, 0x5d, 0xd6, 0x67, 0x3b, 0x6f, 0xc1, 0xd1, 0xe5, 0xc0, 0x71, + 0x62, 0x16, 0x81, 0xef, 0xf1, 0x15, 0x84, 0xf2, 0xe5, 0x14, 0xb0, 0x25, 0xff, 0xc6, 0x84, 0xe4, 0x96, 0x67, 0x2d, + 0x49, 0x6d, 0x24, 0x16, 0x62, 0x38, 0x71, 0xda, 0xf5, 0x01, 0x20, 0xde, 0x22, 0x02, 0xf2, 0x49, 0xe6, 0x7e, 0xb0, + 0xa0, 0x17, 0xc3, 0x02, 0x7b, 0xbe, 0x14, 0x15, 0xbd, 0xe0, 0x1f, 0x32, 0x68, 0xd5, 0x4a, 0x66, 0x0a, 0x0f, 0x52, + 0x50, 0x72, 0xe2, 0xb1, 0xf8, 0x44, 0x08, 0x6d, 0x74, 0x16, 0xca, 0x30, 0x27, 0x6e, 0x79, 0x9a, 0x83, 0xab, 0xcb, + 0xac, 0xf5, 0x62, 0xec, 0xdd, 0x61, 0xe7, 0x11, 0x32, 0xdc, 0x1f, 0xae, 0xcb, 0xda, 0x92, 0xb6, 0x04, 0xb4, 0xd6, + 0x4e, 0x80, 0x3e, 0xea, 0xd2, 0x2d, 0x77, 0x5d, 0x02, 0x0b, 0xa7, 0xec, 0xee, 0x02, 0xec, 0x82, 0x64, 0xc6, 0xf9, + 0x19, 0xec, 0xdc, 0xe3, 0x0f, 0xf0, 0xfd, 0x0c, 0xda, 0x02, 0x7c, 0x3b, 0x83, 0xf5, 0xeb, 0x08, 0x7c, 0x3d, 0x03, + 0x73, 0x00, 0x67, 0x67, 0xf0, 0x57, 0xf1, 0x7b, 0xe9, 0xe9, 0x19, 0xf8, 0x97, 0x0a, 0x5f, 0xd8, 0x8d, 0x35, 0x84, + 0x13, 0xd6, 0xbc, 0x16, 0x8e, 0xe1, 0x80, 0xd7, 0xac, 0x5d, 0x61, 0x0f, 0xbf, 0x23, 0x63, 0xb0, 0x8f, 0xd8, 0x23, + 0x6f, 0x70, 0xc4, 0xec, 0x0e, 0x87, 0x97, 0x86, 0x77, 0xfb, 0xff, 0xb1, 0xb1, 0x3c, 0x4c, 0xd8, 0x7e, 0x8b, 0xbf, + 0x54, 0x42, 0x85, 0xcf, 0xff, 0x53, 0xbd, 0x80, 0xe9, 0x19, 0xd4, 0x05, 0xf8, 0x74, 0x06, 0xdb, 0x02, 0x7c, 0x3c, + 0x83, 0xdb, 0x52, 0xd7, 0x0d, 0x70, 0x70, 0x06, 0x7a, 0x07, 0x3e, 0x9c, 0xc1, 0xe6, 0x1b, 0x78, 0x73, 0x06, 0xea, + 0xf8, 0xed, 0x81, 0xb7, 0x67, 0xa0, 0x57, 0xe0, 0x9d, 0x67, 0x60, 0xe0, 0xfd, 0xdf, 0x38, 0x7f, 0x83, 0x91, 0x53, + 0x76, 0x9f, 0xe5, 0x2b, 0x46, 0xd3, 0x0f, 0xc9, 0x63, 0x27, 0xce, 0x2c, 0xc0, 0x67, 0xfb, 0x6f, 0xa4, 0xe9, 0x26, + 0x5b, 0x6c, 0x02, 0x29, 0x5c, 0x55, 0x66, 0x0c, 0x8c, 0xff, 0x23, 0x7e, 0x66, 0x0e, 0x86, 0x46, 0x12, 0x1b, 0xd9, + 0xc0, 0xaa, 0xed, 0xf9, 0x3f, 0xb6, 0x09, 0xbb, 0x5b, 0x45, 0xb6, 0x73, 0xef, 0xf1, 0xe3, 0xa3, 0xfb, 0xca, 0xa6, + 0xb1, 0x48, 0x03, 0xaa, 0xba, 0x80, 0x8e, 0x94, 0x52, 0x0b, 0xe6, 0x6e, 0xf5, 0x4f, 0x84, 0x6f, 0x2e, 0x78, 0x84, + 0xd9, 0xad, 0x34, 0x52, 0x80, 0x94, 0x99, 0xfe, 0x27, 0x57, 0x7b, 0xa4, 0x2c, 0x3d, 0xcb, 0xa2, 0xf2, 0xa9, 0xf7, + 0xc9, 0xcb, 0xe4, 0x98, 0x65, 0x2e, 0xd4, 0x38, 0xf4, 0xf3, 0x14, 0x02, 0xca, 0x21, 0x25, 0xdc, 0x9e, 0x86, 0x97, + 0x8c, 0xe1, 0x5b, 0x72, 0xeb, 0x05, 0xf6, 0x9e, 0x60, 0xc8, 0xed, 0x98, 0x02, 0xab, 0x98, 0xa9, 0x0d, 0xfa, 0x08, + 0xc4, 0x71, 0x53, 0x6a, 0xfc, 0x35, 0xfa, 0xf0, 0x76, 0xb1, 0xab, 0xe3, 0x60, 0x50, 0xf5, 0x3b, 0x0d, 0x8f, 0x88, + 0x4a, 0xca, 0x21, 0x8b, 0x16, 0x59, 0xb2, 0xfd, 0x85, 0x03, 0x4a, 0xd0, 0x44, 0xbb, 0xd2, 0xf2, 0x9a, 0x14, 0xbc, + 0x1c, 0x5d, 0x30, 0x19, 0x8e, 0x67, 0xf0, 0x0c, 0x52, 0x7f, 0xce, 0x1b, 0x5e, 0x01, 0xda, 0xe0, 0x93, 0xee, 0xd7, + 0x75, 0xc7, 0x17, 0x7a, 0x47, 0x69, 0xc6, 0x15, 0x3e, 0x8b, 0xdf, 0x30, 0xcb, 0x5c, 0xff, 0x46, 0x90, 0x66, 0x7b, + 0xeb, 0x69, 0x0b, 0x60, 0xfe, 0x01, 0xdb, 0xb3, 0x97, 0x33, 0x5c, 0x6c, 0xed, 0x51, 0x54, 0x2b, 0x2d, 0x38, 0xe8, + 0x6e, 0x33, 0xe0, 0x6e, 0x31, 0xb8, 0x67, 0x47, 0x7b, 0x25, 0x14, 0x4e, 0x44, 0xab, 0x0c, 0x45, 0x76, 0x00, 0xdf, + 0xb1, 0xb1, 0x25, 0x1a, 0xe9, 0xf4, 0xa0, 0x6f, 0xd0, 0xb6, 0x44, 0x10, 0xb6, 0x6e, 0xb7, 0x88, 0x81, 0xec, 0x55, + 0xe2, 0x7f, 0x5f, 0xee, 0x65, 0xd4, 0xd2, 0x4c, 0xdf, 0xe3, 0x3b, 0xe6, 0xa7, 0xb4, 0x50, 0x9f, 0x24, 0x65, 0xec, + 0x34, 0xfe, 0xe9, 0xcf, 0x90, 0xe7, 0x78, 0xd5, 0x55, 0x00, 0x14, 0xdc, 0xd0, 0x28, 0xfe, 0x90, 0xcf, 0x9a, 0xb0, + 0x70, 0x19, 0x79, 0x5c, 0x30, 0xc0, 0x2c, 0x73, 0xdc, 0xc4, 0xd8, 0x70, 0x71, 0x58, 0x70, 0x98, 0x99, 0x74, 0x99, + 0xd1, 0xeb, 0x62, 0x5c, 0x8a, 0xd6, 0x7d, 0x35, 0x35, 0x7e, 0x93, 0x19, 0xa1, 0x0f, 0x4f, 0xc3, 0x80, 0x5d, 0xc4, + 0xd8, 0xbf, 0x6b, 0x70, 0x55, 0x30, 0xb6, 0x55, 0x83, 0x15, 0xa5, 0x66, 0x95, 0xbf, 0x7c, 0x76, 0xd4, 0x1f, 0xde, + 0xe4, 0xd6, 0x33, 0x06, 0x0c, 0xfb, 0x82, 0x09, 0x6d, 0xca, 0xdf, 0x1b, 0x93, 0x88, 0x5e, 0x70, 0x6e, 0x7c, 0x4a, + 0x16, 0x6e, 0x9a, 0x61, 0x2a, 0x76, 0xd0, 0x24, 0x75, 0x08, 0xb1, 0x89, 0xbf, 0x7d, 0xf2, 0xe4, 0x39, 0xa4, 0x7c, + 0x4e, 0x44, 0x92, 0x68, 0x75, 0x47, 0xf1, 0x6c, 0xe2, 0x8a, 0x67, 0x41, 0x54, 0x72, 0x03, 0xc0, 0x11, 0xe8, 0xd2, + 0x74, 0xf8, 0xdd, 0x7e, 0xdc, 0x6b, 0x03, 0xcc, 0x36, 0xd6, 0xdd, 0xc7, 0xa1, 0x31, 0xe5, 0x19, 0xdd, 0x0f, 0x7a, + 0xe5, 0x39, 0x78, 0x9a, 0x5f, 0xa6, 0x24, 0x43, 0x86, 0xd5, 0xcc, 0xa1, 0xc3, 0x75, 0x54, 0xe5, 0x1a, 0xb4, 0xe0, + 0x63, 0xaa, 0xd4, 0xc8, 0x9b, 0xf7, 0x87, 0xeb, 0x82, 0xe4, 0x68, 0x07, 0xb4, 0xf6, 0x7a, 0x98, 0x5d, 0x08, 0x0c, + 0x52, 0x48, 0xb8, 0x63, 0x5b, 0x7b, 0x7f, 0xa7, 0x87, 0xeb, 0xed, 0x4b, 0x94, 0x5b, 0x6f, 0x7d, 0x60, 0x96, 0xfe, + 0xa4, 0xb3, 0x2b, 0xc3, 0x8e, 0xbf, 0x5a, 0x93, 0x0f, 0x6f, 0x6a, 0x6d, 0xa4, 0x64, 0xfa, 0xea, 0x82, 0x1f, 0x27, + 0x8c, 0x09, 0x9e, 0x81, 0x98, 0x10, 0xd5, 0xe9, 0x7a, 0x49, 0x22, 0x8a, 0xad, 0x09, 0x91, 0x52, 0x24, 0x11, 0xc9, + 0x49, 0xba, 0xab, 0x9b, 0xe2, 0x93, 0xd3, 0x13, 0x69, 0xe6, 0x0e, 0x0c, 0x60, 0xa9, 0x4f, 0xcf, 0xe6, 0x2c, 0x7f, + 0x23, 0x10, 0x7e, 0x94, 0x02, 0x96, 0xd6, 0x60, 0xf2, 0x80, 0xbd, 0xa4, 0x7e, 0xa6, 0xea, 0x2e, 0x9a, 0x14, 0xc9, + 0xa3, 0x67, 0x80, 0xad, 0x9c, 0x72, 0x84, 0x2a, 0xd3, 0x4c, 0x48, 0x8e, 0x73, 0x6b, 0x32, 0x27, 0x21, 0x94, 0x9c, + 0x99, 0x7b, 0xc4, 0xf2, 0x29, 0xfc, 0xb2, 0x29, 0x6f, 0x7a, 0x27, 0x5b, 0x8a, 0x75, 0x35, 0x84, 0xe1, 0xc4, 0x80, + 0xdf, 0x42, 0xec, 0xf5, 0xd6, 0x91, 0x34, 0xc8, 0x79, 0xf2, 0x8c, 0x23, 0x6c, 0x5c, 0x62, 0xa3, 0x6f, 0x36, 0x4a, + 0x12, 0x72, 0x40, 0x26, 0xbb, 0x50, 0x72, 0x72, 0x07, 0xef, 0xc1, 0xc7, 0x36, 0xdd, 0x9f, 0xff, 0xca, 0x13, 0xad, + 0x6a, 0x63, 0x60, 0xd5, 0xd7, 0x03, 0x59, 0x99, 0x7c, 0x25, 0x40, 0x5b, 0xc0, 0x85, 0xe4, 0x6f, 0x7f, 0xc5, 0x71, + 0x88, 0xaf, 0x32, 0xc8, 0x60, 0xd4, 0xe2, 0x8b, 0xc8, 0x3e, 0xb5, 0x62, 0xe3, 0xef, 0x94, 0xe6, 0x0a, 0x56, 0xb5, + 0x2f, 0x41, 0x64, 0x71, 0x68, 0xba, 0x4f, 0x73, 0xb0, 0x46, 0xad, 0x3f, 0x52, 0xe4, 0x6c, 0x8a, 0xd6, 0x1f, 0x4a, + 0x68, 0x24, 0x2b, 0xde, 0xea, 0x8b, 0x4b, 0xea, 0x2c, 0xaa, 0xa7, 0xa7, 0xc4, 0xa2, 0x62, 0x37, 0x6f, 0x6f, 0x71, + 0x4d, 0xd6, 0x2d, 0xb8, 0x1c, 0x59, 0x19, 0xbe, 0x5d, 0xe9, 0x6c, 0xca, 0xf3, 0x7b, 0x7a, 0x36, 0xb6, 0x60, 0x1f, + 0x7c, 0x6b, 0x53, 0x49, 0x73, 0x61, 0xc5, 0xaf, 0xf2, 0x70, 0x85, 0xdf, 0x90, 0xa0, 0x50, 0x85, 0x3f, 0xbd, 0x48, + 0x8e, 0x8b, 0xef, 0x88, 0x3d, 0x68, 0x5b, 0x83, 0x86, 0xb3, 0x08, 0x33, 0x21, 0x21, 0xe1, 0x00, 0x64, 0xf2, 0x61, + 0xdc, 0x2a, 0xa9, 0x25, 0xb6, 0xa4, 0x97, 0x23, 0x31, 0xe0, 0xf2, 0x5b, 0xb7, 0xc9, 0x4a, 0x57, 0x4f, 0xc1, 0x24, + 0x6e, 0x60, 0x05, 0x53, 0x8f, 0xe3, 0x1b, 0xa5, 0xb3, 0xd2, 0x12, 0x89, 0x04, 0x63, 0x21, 0x44, 0x7d, 0x3f, 0xf5, + 0x26, 0x33, 0x64, 0x45, 0x23, 0x8d, 0x48, 0xd3, 0x4d, 0x30, 0x03, 0x31, 0x81, 0xc2, 0x3c, 0xe6, 0xd6, 0x08, 0x11, + 0x66, 0x98, 0x6e, 0x22, 0x5d, 0xeb, 0x06, 0xcb, 0xf1, 0xfe, 0xa9, 0x1e, 0x8d, 0x79, 0xec, 0x06, 0xb5, 0xb6, 0x91, + 0x86, 0x31, 0x9e, 0xae, 0x10, 0x42, 0xb7, 0x10, 0xc5, 0xc3, 0x9a, 0xb5, 0x9a, 0xc6, 0x7e, 0x74, 0x6d, 0xf7, 0x20, + 0x00, 0xce, 0x63, 0x98, 0x5e, 0x06, 0x51, 0xd2, 0x2b, 0x13, 0x32, 0x1e, 0x91, 0x66, 0xe4, 0xc9, 0x15, 0xad, 0x22, + 0xad, 0xc1, 0xc2, 0xaa, 0x12, 0xc9, 0x9f, 0xa0, 0x52, 0x08, 0x49, 0xfa, 0x9e, 0xe6, 0xc3, 0xa3, 0xa5, 0x32, 0x56, + 0xbc, 0xa7, 0xef, 0xf3, 0xdb, 0xd5, 0x7c, 0x8d, 0x48, 0x98, 0x27, 0x40, 0x7c, 0x5d, 0xc0, 0x71, 0x5e, 0x95, 0x7c, + 0x0a, 0x12, 0x03, 0x03, 0xa1, 0x10, 0x6a, 0xbf, 0xc7, 0x99, 0xbb, 0x2a, 0x2b, 0x85, 0x20, 0x79, 0xb8, 0x15, 0xc5, + 0xcd, 0x48, 0xa5, 0xb1, 0x22, 0x48, 0xc6, 0x77, 0xf3, 0xa5, 0xaf, 0x25, 0x7b, 0xeb, 0x41, 0x26, 0x13, 0xc4, 0x59, + 0xfc, 0x7f, 0x96, 0x37, 0xcd, 0x7e, 0xbf, 0xf8, 0x52, 0x13, 0x23, 0x45, 0xb2, 0x97, 0x6a, 0xf2, 0xf4, 0x2f, 0x53, + 0x96, 0x01, 0x87, 0x44, 0x4b, 0x5f, 0xa1, 0x09, 0x0e, 0xb4, 0x21, 0xb3, 0x59, 0x80, 0x50, 0x48, 0x69, 0x31, 0xda, + 0x5d, 0xa4, 0x3a, 0xcb, 0xea, 0x98, 0x9d, 0x35, 0x33, 0x2c, 0x2a, 0xfd, 0x10, 0xb3, 0xa7, 0x61, 0xa6, 0x37, 0x59, + 0xf1, 0x8f, 0xd9, 0x4d, 0xca, 0xaf, 0xd9, 0x4d, 0x51, 0x06, 0x45, 0x1e, 0x1c, 0xe4, 0x90, 0x0b, 0xee, 0x26, 0x36, + 0x12, 0x75, 0x0f, 0x96, 0x0d, 0x93, 0x8b, 0x13, 0xbb, 0x24, 0x90, 0xb4, 0xc1, 0xe3, 0xbd, 0xde, 0x47, 0xf8, 0x90, + 0x0a, 0xf3, 0x7c, 0xfc, 0x21, 0x93, 0x93, 0xc9, 0x85, 0xcb, 0xea, 0x07, 0x66, 0x77, 0x41, 0xa2, 0x07, 0xe6, 0xc7, + 0xee, 0xd8, 0x49, 0x94, 0x82, 0x4c, 0xe6, 0x7a, 0x8b, 0xb4, 0xc7, 0xf4, 0xb1, 0x59, 0x75, 0xbf, 0x8c, 0xea, 0xfe, + 0xa0, 0xe8, 0x15, 0x8e, 0xb0, 0xf7, 0xa3, 0x72, 0x12, 0x68, 0xea, 0x29, 0x77, 0x6d, 0xe0, 0xde, 0xab, 0x58, 0x98, + 0xbc, 0x99, 0xe5, 0x1b, 0xcf, 0xb6, 0x2f, 0x52, 0xe7, 0xd9, 0x3a, 0x6a, 0x76, 0x1f, 0x97, 0x95, 0x64, 0x89, 0x73, + 0x81, 0x32, 0x46, 0xa0, 0x9a, 0x86, 0xe7, 0xae, 0x0b, 0x9b, 0x49, 0x69, 0x38, 0x8d, 0x9e, 0x50, 0x35, 0x48, 0x9d, + 0x53, 0x8a, 0x46, 0xb3, 0xf8, 0x2e, 0xe5, 0x9c, 0xe7, 0x4c, 0x38, 0x00, 0xa5, 0x94, 0x4b, 0x25, 0xcb, 0xf6, 0xf1, + 0x98, 0x0a, 0x7e, 0x67, 0xa2, 0xf0, 0x47, 0x17, 0x78, 0xe4, 0xba, 0xee, 0x6e, 0x08, 0x63, 0xe5, 0x0a, 0x3a, 0x83, + 0xf3, 0x3a, 0xf6, 0x8b, 0xce, 0x30, 0x19, 0x9e, 0xc1, 0xa8, 0x9e, 0xe5, 0x0c, 0xef, 0x57, 0x71, 0x5b, 0x56, 0x9e, + 0xbf, 0x73, 0x5d, 0x6b, 0xf3, 0x03, 0xce, 0x50, 0x53, 0x0f, 0x73, 0xa5, 0xc6, 0xf9, 0x9a, 0x01, 0x77, 0xaf, 0xe5, + 0x39, 0x58, 0x51, 0x61, 0xc1, 0x16, 0xcc, 0xb0, 0x01, 0xaa, 0x8b, 0xfc, 0x28, 0xa9, 0x60, 0x85, 0x0b, 0xaf, 0x57, + 0xfb, 0xeb, 0xaa, 0x0f, 0xa8, 0xa1, 0xcc, 0x3d, 0xae, 0xf0, 0x90, 0xfa, 0x0a, 0xbb, 0x12, 0x23, 0xbb, 0x05, 0xd7, + 0x78, 0xdc, 0xf6, 0xe6, 0xb5, 0x78, 0x2c, 0x9a, 0x9d, 0xf6, 0x23, 0xc6, 0x26, 0x16, 0xcf, 0xc2, 0x42, 0x27, 0xc9, + 0x99, 0xef, 0xbc, 0x57, 0x8a, 0xf2, 0xfc, 0x81, 0xb1, 0x9f, 0x25, 0x91, 0x20, 0x94, 0xd4, 0xf6, 0x4f, 0x08, 0xad, + 0x61, 0xaa, 0xa5, 0x34, 0x17, 0xd1, 0xe7, 0x1a, 0x0c, 0x98, 0x12, 0x66, 0x39, 0x8d, 0xca, 0x6b, 0x5b, 0xb6, 0x63, + 0xde, 0xf9, 0x53, 0xa9, 0x05, 0x91, 0xcc, 0x8f, 0xd1, 0x88, 0x60, 0x43, 0x4c, 0x90, 0x79, 0x33, 0x9f, 0x96, 0xd3, + 0x92, 0xcf, 0xbb, 0xf9, 0xc3, 0xe2, 0x81, 0xca, 0x6d, 0xf5, 0xb9, 0xa6, 0x33, 0x94, 0x87, 0xba, 0x7a, 0x53, 0x5d, + 0xa3, 0xbb, 0x39, 0xf4, 0x5d, 0x59, 0xe9, 0xbd, 0xf9, 0xef, 0x77, 0x67, 0xc9, 0x7b, 0xc0, 0xf4, 0xa2, 0x6a, 0xb4, + 0x9f, 0x00, 0x9e, 0xe5, 0xd2, 0xc1, 0xff, 0x54, 0xa4, 0x26, 0x57, 0xd1, 0x04, 0xf4, 0xdd, 0xcc, 0x0d, 0x48, 0x5b, + 0xd9, 0x74, 0x06, 0x8d, 0xa1, 0xe6, 0xa8, 0x5e, 0x19, 0xf1, 0xe7, 0xf2, 0xaf, 0x57, 0x18, 0x18, 0xd6, 0x32, 0x33, + 0x16, 0x21, 0x83, 0x59, 0x9a, 0xd8, 0xe4, 0x9d, 0xe6, 0xf4, 0xe7, 0x76, 0xed, 0xe6, 0xab, 0xdd, 0x7b, 0x0b, 0xb2, + 0xc0, 0x09, 0x86, 0x93, 0x4f, 0x1c, 0x2a, 0x8a, 0xcb, 0xb7, 0x75, 0x3d, 0xfd, 0xd7, 0xed, 0xdb, 0xd0, 0xfb, 0xe6, + 0xac, 0x98, 0xd4, 0xb4, 0xec, 0x1e, 0x4d, 0x0b, 0x30, 0x7f, 0x2a, 0x6e, 0xbf, 0xec, 0xf9, 0x36, 0x8e, 0x16, 0x47, + 0x07, 0xe3, 0x67, 0xf7, 0xd7, 0x3b, 0x06, 0xc0, 0xe3, 0xcf, 0x29, 0xa9, 0xa6, 0x39, 0x5d, 0xfc, 0x70, 0xca, 0xa1, + 0xc6, 0x39, 0x39, 0x4f, 0x81, 0x3c, 0x6c, 0xb7, 0xcd, 0xc3, 0x71, 0x53, 0x45, 0x4c, 0x67, 0x8e, 0xa0, 0x37, 0xe9, + 0x2b, 0x8a, 0x30, 0x53, 0xe1, 0xc2, 0xf4, 0x53, 0x96, 0xb2, 0xd4, 0xe8, 0x74, 0x91, 0x55, 0x39, 0x60, 0xe9, 0xdb, + 0x89, 0x6f, 0x3c, 0x22, 0x4d, 0xb1, 0xc1, 0xd8, 0x3a, 0xe4, 0x04, 0xb1, 0x0c, 0x1f, 0x8e, 0xe5, 0xed, 0x25, 0x5e, + 0xe2, 0x39, 0x56, 0xd7, 0xc9, 0x37, 0x6f, 0x82, 0x13, 0x63, 0x7b, 0x1e, 0x6e, 0xb0, 0xd1, 0x46, 0x3e, 0xe4, 0x46, + 0x33, 0x14, 0xb8, 0xaa, 0xc4, 0xac, 0x02, 0x7d, 0x41, 0x97, 0x83, 0xe7, 0xe6, 0x5d, 0x5b, 0x05, 0x6d, 0x02, 0x37, + 0x39, 0x83, 0xa3, 0x76, 0xa7, 0x36, 0x98, 0x7e, 0x3c, 0x17, 0xa4, 0xa7, 0xbe, 0x73, 0x1f, 0x52, 0xbe, 0xb1, 0x40, + 0x35, 0x62, 0x44, 0x0d, 0x1c, 0xe0, 0x65, 0x9f, 0x87, 0xe6, 0x0d, 0x95, 0xbd, 0x57, 0x0b, 0x08, 0xa3, 0x29, 0xe4, + 0xae, 0x00, 0xec, 0x84, 0x21, 0x42, 0x83, 0xa3, 0x13, 0x00, 0x2b, 0xdc, 0xc5, 0xa7, 0xe8, 0x7f, 0x63, 0xbf, 0xbe, + 0x57, 0x71, 0xae, 0xdf, 0x22, 0x4a, 0xd3, 0x14, 0xf9, 0xa3, 0x66, 0x0d, 0x41, 0xa8, 0xb7, 0xe1, 0x66, 0xe9, 0xcf, + 0x7e, 0x80, 0x73, 0x03, 0x6b, 0x2c, 0x89, 0xe1, 0x03, 0x13, 0x4e, 0xd1, 0x86, 0x2b, 0xea, 0xdb, 0x69, 0xb1, 0x70, + 0xee, 0xdf, 0xa8, 0xa8, 0xf7, 0xea, 0xfb, 0xa9, 0xac, 0x7c, 0x22, 0x23, 0x40, 0x9a, 0x9b, 0xa1, 0x23, 0x73, 0x5f, + 0x37, 0x6b, 0xb7, 0x9e, 0xf0, 0x64, 0xb2, 0xf6, 0x20, 0xfd, 0x1e, 0x2f, 0xe5, 0xbf, 0xdf, 0x19, 0xcf, 0xa3, 0x7e, + 0xa3, 0x81, 0x15, 0x45, 0xab, 0x76, 0x34, 0xb1, 0xbe, 0x05, 0x0c, 0x41, 0x20, 0x95, 0x52, 0x3c, 0x21, 0xbf, 0x04, + 0xa3, 0xd2, 0xad, 0xc8, 0xac, 0xcd, 0x09, 0xc3, 0xc2, 0xd3, 0x2d, 0xd0, 0x2b, 0x6e, 0x28, 0xdc, 0x6b, 0x3d, 0xa2, + 0xee, 0x53, 0xe9, 0xbe, 0xce, 0xf8, 0x83, 0xd5, 0x17, 0xa9, 0xde, 0xbe, 0xe7, 0xb7, 0xe5, 0xda, 0xdd, 0xe9, 0x7f, + 0x7d, 0xc8, 0xe7, 0xf6, 0xb4, 0xef, 0xf6, 0x3e, 0x6e, 0xd7, 0x48, 0x3f, 0x5e, 0xb6, 0xf5, 0x29, 0xd6, 0xb5, 0x5b, + 0x54, 0xd9, 0x34, 0x69, 0xb5, 0x89, 0xa7, 0x6c, 0xfc, 0x1b, 0xa2, 0x62, 0x73, 0x88, 0x23, 0xf3, 0xc8, 0xa4, 0x72, + 0xce, 0xcf, 0x7f, 0x59, 0xd8, 0xc3, 0xa9, 0x07, 0x5b, 0x39, 0x97, 0xc6, 0x5a, 0xb1, 0x51, 0x21, 0x52, 0x70, 0xc9, + 0xd6, 0xb8, 0xbb, 0xb0, 0xa4, 0xb1, 0x06, 0x6c, 0xc0, 0x50, 0xb6, 0x83, 0x42, 0x6a, 0xc9, 0xde, 0xd9, 0xf1, 0x7a, + 0x3b, 0x46, 0x7f, 0xb0, 0x95, 0x51, 0xbd, 0xed, 0xc8, 0x52, 0xcb, 0x9a, 0xd7, 0x82, 0x96, 0x39, 0xcf, 0x05, 0x9e, + 0x85, 0x32, 0xfd, 0x42, 0x58, 0x17, 0x52, 0x46, 0xeb, 0x80, 0x14, 0xcc, 0x1a, 0x77, 0x5e, 0x94, 0xa5, 0xfd, 0x55, + 0xbd, 0xb8, 0xd3, 0x55, 0x05, 0xba, 0xad, 0x18, 0x95, 0x78, 0xcb, 0x4f, 0x59, 0x95, 0xd0, 0x26, 0x45, 0x2d, 0xdf, + 0x55, 0xe9, 0xf1, 0xda, 0x59, 0x60, 0xb1, 0x8c, 0x14, 0xb3, 0xf1, 0xc0, 0xe8, 0x67, 0x22, 0xfd, 0x40, 0x7f, 0xaa, + 0x4c, 0x41, 0xb0, 0x71, 0xea, 0x92, 0x7f, 0x8f, 0x04, 0x8a, 0x10, 0x4d, 0xce, 0x05, 0x5a, 0xfa, 0x97, 0x26, 0x1e, + 0x15, 0xe5, 0xe4, 0x0a, 0x46, 0x2b, 0xc3, 0xa3, 0xfc, 0x69, 0xae, 0x2b, 0xf4, 0x3c, 0x51, 0xfb, 0x25, 0xbb, 0xfd, + 0x62, 0xe3, 0x68, 0x5b, 0xcc, 0x6b, 0x1c, 0xd9, 0x3b, 0xf6, 0x58, 0x29, 0x45, 0x6e, 0x9f, 0x7a, 0x60, 0xad, 0x4b, + 0x39, 0xee, 0xea, 0xe1, 0x25, 0xdc, 0x9b, 0xbe, 0x91, 0x83, 0xb2, 0xb8, 0x98, 0xb7, 0xfa, 0x91, 0xfe, 0x10, 0xaf, + 0x28, 0x62, 0xd7, 0x12, 0xf6, 0xe9, 0x25, 0xcd, 0xd7, 0xb4, 0xc8, 0x90, 0xe8, 0xf8, 0x4d, 0x1b, 0xe5, 0x8d, 0x61, + 0xd5, 0x39, 0xb8, 0xdf, 0x29, 0x79, 0x82, 0x9a, 0xc3, 0xcc, 0xb0, 0xb1, 0xba, 0x9a, 0xb7, 0x71, 0x72, 0x4f, 0xab, + 0x0a, 0xf7, 0x5c, 0x40, 0x7b, 0x9b, 0xe5, 0x5b, 0x50, 0x1f, 0x73, 0xd4, 0xba, 0xfd, 0xb4, 0x49, 0xca, 0x99, 0x17, + 0xde, 0xd7, 0xa1, 0xa1, 0xfb, 0x2a, 0x9d, 0xdb, 0x18, 0x19, 0xa7, 0x83, 0xaa, 0x4b, 0x06, 0xe3, 0xfe, 0xd1, 0xba, + 0x61, 0xf1, 0x54, 0x6d, 0xad, 0x95, 0xb3, 0xba, 0x95, 0xfe, 0x79, 0x52, 0xc3, 0xda, 0xe5, 0xbc, 0x1e, 0x06, 0x17, + 0xa7, 0x6a, 0xfe, 0xa9, 0x09, 0x96, 0x19, 0x9c, 0xc3, 0xa6, 0x0c, 0xb9, 0x10, 0xc7, 0x87, 0x79, 0x4f, 0xbd, 0xd4, + 0xda, 0x38, 0x47, 0xc2, 0x22, 0xea, 0xb7, 0x82, 0xa7, 0xb9, 0x7c, 0x63, 0xae, 0x7b, 0xd0, 0xd8, 0x26, 0xc3, 0xbf, + 0x2f, 0xe8, 0x5f, 0x7e, 0x19, 0xbe, 0x42, 0x42, 0x94, 0xb4, 0x97, 0x1c, 0xcd, 0x73, 0xed, 0x51, 0xd8, 0x73, 0x60, + 0x21, 0x5e, 0x71, 0x6f, 0x20, 0x7f, 0xa4, 0x5f, 0xfa, 0xe4, 0x14, 0x87, 0x28, 0xa2, 0x81, 0x70, 0xfe, 0x6d, 0x50, + 0x97, 0xd8, 0xf3, 0x89, 0x74, 0x36, 0x68, 0x1c, 0xd8, 0x0d, 0x10, 0x7a, 0x89, 0x4d, 0xd8, 0x4f, 0x54, 0x1b, 0x0c, + 0xe6, 0xd4, 0x34, 0x3d, 0x36, 0x76, 0x6b, 0x08, 0x14, 0xdf, 0x8d, 0xd1, 0xe6, 0x4e, 0x81, 0xd6, 0xcb, 0xa7, 0x9c, + 0x55, 0x59, 0x75, 0x01, 0x2e, 0x0b, 0xaa, 0xe5, 0x90, 0xab, 0x9f, 0xe8, 0x4e, 0x05, 0xa4, 0x4d, 0xec, 0x7f, 0xc6, + 0xf9, 0xc0, 0xde, 0x1b, 0x3f, 0x38, 0xa2, 0xf5, 0xd9, 0x86, 0x2e, 0x84, 0x59, 0xab, 0x43, 0x4a, 0x59, 0x24, 0x72, + 0xdb, 0x6a, 0x7d, 0x1e, 0x2b, 0xa6, 0xf0, 0xe0, 0xdf, 0x9f, 0x44, 0x1a, 0xd6, 0xaa, 0xe8, 0x22, 0x1a, 0xce, 0xb6, + 0x28, 0xcc, 0xeb, 0xf5, 0x20, 0x7f, 0x35, 0xe5, 0x64, 0x03, 0x77, 0x93, 0x51, 0x3a, 0x7b, 0x91, 0x4a, 0x1c, 0x37, + 0x5d, 0x6e, 0x3b, 0xee, 0x48, 0xb7, 0xd8, 0xed, 0x21, 0xa9, 0x5c, 0x16, 0x6a, 0x6f, 0xda, 0xa0, 0x07, 0x8c, 0xa5, + 0xb7, 0x40, 0x8a, 0x6d, 0x19, 0x20, 0x7b, 0xf8, 0x85, 0xa2, 0x84, 0xa8, 0x8b, 0xb9, 0xc0, 0x78, 0x03, 0x01, 0x51, + 0xf6, 0x3c, 0x0b, 0xb6, 0xb5, 0x3c, 0x9a, 0x23, 0x53, 0x9a, 0xa6, 0xaa, 0x3d, 0x87, 0x5c, 0xd2, 0xc5, 0x94, 0x1b, + 0xed, 0x65, 0xfd, 0x70, 0x19, 0x41, 0xd0, 0xfd, 0x96, 0x67, 0xd5, 0xe8, 0x25, 0x10, 0x96, 0x2b, 0x28, 0x4f, 0xa6, + 0xfb, 0x45, 0x53, 0x9c, 0x79, 0x1a, 0xa4, 0x9a, 0x1b, 0x9e, 0x36, 0x13, 0x16, 0x4e, 0x7c, 0x9b, 0x24, 0xfd, 0x51, + 0x2d, 0xaf, 0xb5, 0xf0, 0x81, 0x7a, 0x70, 0xdb, 0xf0, 0x72, 0x34, 0xef, 0x57, 0x5b, 0x9a, 0x53, 0x7e, 0x99, 0x34, + 0xab, 0x9b, 0x72, 0xcc, 0x6d, 0xcc, 0x7a, 0x89, 0x34, 0xda, 0x94, 0xb7, 0xb1, 0x51, 0x32, 0xab, 0xa4, 0x25, 0x72, + 0xfa, 0x1b, 0x4b, 0xa4, 0x86, 0xc9, 0xa5, 0xc8, 0xc5, 0x5f, 0xc9, 0x42, 0xda, 0x7a, 0x6b, 0x74, 0x27, 0x06, 0x4a, + 0xd7, 0x79, 0x8f, 0x5b, 0xc2, 0xb3, 0x5f, 0x02, 0x40, 0xb3, 0x2a, 0x6f, 0x90, 0x72, 0xb1, 0x0a, 0x67, 0x7e, 0xb6, + 0x45, 0x6f, 0x7b, 0x0e, 0xab, 0x55, 0x42, 0xbf, 0x6f, 0x75, 0x05, 0xdc, 0xc0, 0x3e, 0x0f, 0xeb, 0x83, 0x5d, 0x18, + 0xd5, 0x6b, 0x25, 0x84, 0xd5, 0x04, 0x85, 0xb0, 0x72, 0x25, 0xd9, 0x83, 0x28, 0x42, 0x0f, 0xdc, 0x1d, 0x52, 0x9b, + 0x89, 0x78, 0xbe, 0x86, 0xf0, 0xcf, 0x31, 0x7b, 0xa8, 0xe8, 0x92, 0x01, 0x0a, 0xea, 0x47, 0x40, 0x29, 0x64, 0x04, + 0x10, 0x90, 0x90, 0x17, 0x21, 0x98, 0x7a, 0x42, 0xe9, 0x26, 0x38, 0xd2, 0xff, 0xd1, 0x0b, 0x95, 0x95, 0x30, 0x23, + 0x3e, 0xa3, 0x60, 0x14, 0x06, 0x1c, 0xdf, 0x9d, 0x50, 0x78, 0xd4, 0x3c, 0x55, 0x43, 0x7e, 0x7d, 0x78, 0x4f, 0x2f, + 0xb6, 0xd1, 0xbe, 0x50, 0x1f, 0x40, 0x97, 0xba, 0xca, 0x0b, 0x3a, 0x0d, 0x52, 0x45, 0x03, 0x14, 0x21, 0xbc, 0x74, + 0x43, 0x31, 0xc0, 0x0c, 0x8d, 0xcd, 0xc9, 0xc8, 0x8a, 0x2e, 0x6e, 0xba, 0x19, 0x88, 0xbc, 0x70, 0x8e, 0x8d, 0xea, + 0x78, 0xfa, 0x4f, 0xd5, 0xdc, 0x4c, 0x83, 0xb4, 0xc6, 0xd9, 0xd4, 0xa9, 0xcd, 0x10, 0x33, 0x11, 0x71, 0x51, 0xad, + 0x4b, 0xee, 0x25, 0x34, 0x87, 0x5d, 0x78, 0xef, 0x13, 0x6f, 0x29, 0xa5, 0x70, 0x66, 0x75, 0xb6, 0xdb, 0x02, 0x2d, + 0xe9, 0x3c, 0x7b, 0xea, 0x1d, 0x2e, 0x32, 0x15, 0x11, 0xfe, 0x6c, 0x63, 0x32, 0x51, 0x8a, 0xf4, 0x0c, 0xb5, 0x5c, + 0x70, 0x94, 0xec, 0x2a, 0x64, 0xfa, 0x2f, 0x9c, 0xb2, 0x03, 0xd0, 0xb6, 0x9b, 0xf0, 0x6e, 0x72, 0xc7, 0x37, 0xde, + 0xdf, 0xab, 0xbd, 0x2b, 0x36, 0xb5, 0x6b, 0x1c, 0x62, 0x7a, 0xf7, 0x14, 0x7b, 0x40, 0xa0, 0xa2, 0xc5, 0x12, 0x1a, + 0x7b, 0x6e, 0x5e, 0x1e, 0x8f, 0x93, 0x15, 0x25, 0x67, 0x9f, 0xeb, 0x8b, 0x85, 0x6d, 0x3a, 0xf1, 0xd2, 0xed, 0x95, + 0xc3, 0x02, 0x3d, 0x8c, 0x40, 0x38, 0x85, 0x19, 0x4d, 0x80, 0x4e, 0x8f, 0x3b, 0x23, 0x41, 0x39, 0xf5, 0x15, 0x03, + 0x26, 0x90, 0xc7, 0x6a, 0xdc, 0x14, 0x2e, 0x21, 0x67, 0x7b, 0xac, 0x88, 0x91, 0x06, 0x63, 0xfb, 0x3a, 0x28, 0x1c, + 0xa0, 0x13, 0x67, 0x7b, 0xa0, 0xae, 0x13, 0xef, 0x15, 0x79, 0x4d, 0xac, 0x35, 0xde, 0xb7, 0xa6, 0x38, 0x9d, 0x94, + 0xaf, 0x25, 0x9a, 0x23, 0x9a, 0xd3, 0x16, 0xf9, 0x03, 0x58, 0x79, 0xac, 0xbf, 0xb2, 0xba, 0x2f, 0x67, 0xac, 0xbf, + 0x30, 0xb2, 0xe4, 0x36, 0x42, 0x37, 0x1c, 0xac, 0x55, 0x10, 0xed, 0xc1, 0xd6, 0x62, 0xed, 0x08, 0xcb, 0x66, 0x6f, + 0x6b, 0x01, 0xee, 0xb4, 0xe7, 0x22, 0x0a, 0x17, 0x2b, 0xf0, 0x9e, 0xae, 0x21, 0xaf, 0xc4, 0x19, 0xfd, 0x23, 0x9c, + 0x98, 0x83, 0x04, 0xb1, 0x81, 0x1e, 0x95, 0xf5, 0x1f, 0x86, 0xc8, 0xe8, 0x74, 0x33, 0x1a, 0xbb, 0x7c, 0x69, 0xb4, + 0x6a, 0x05, 0x30, 0xa8, 0x6f, 0xf3, 0x78, 0x58, 0xc6, 0x8b, 0x39, 0x0e, 0x6d, 0x6b, 0xdf, 0x78, 0x94, 0x59, 0x78, + 0xff, 0x09, 0x4c, 0x6b, 0xb8, 0x2d, 0xf2, 0x7f, 0x2f, 0xcb, 0x4b, 0x79, 0xb6, 0x2b, 0xe7, 0xe9, 0xbd, 0xdf, 0xab, + 0x3c, 0xbb, 0x3b, 0xbd, 0xb7, 0xd8, 0xe4, 0x81, 0x71, 0x31, 0x47, 0xc0, 0x69, 0xeb, 0xc3, 0xbb, 0xef, 0x5e, 0x1f, + 0xbc, 0xa7, 0x2f, 0x7a, 0xcb, 0x6f, 0x64, 0xd3, 0x87, 0xe1, 0x14, 0x4f, 0xf1, 0xf3, 0x25, 0xef, 0x5a, 0xff, 0x35, + 0xdb, 0x6c, 0xce, 0x6b, 0xef, 0x94, 0xeb, 0x16, 0x57, 0x0d, 0xed, 0xcf, 0xdb, 0x6a, 0x0a, 0x9c, 0x6c, 0x02, 0x45, + 0x7c, 0xdd, 0x15, 0xa7, 0xac, 0x34, 0x80, 0x40, 0xb8, 0x3e, 0xfd, 0x9b, 0x6f, 0x8c, 0x52, 0xd9, 0xe0, 0xed, 0xa7, + 0xe5, 0x27, 0xa3, 0xc3, 0x23, 0x82, 0x0b, 0xd9, 0x7a, 0xa4, 0x7f, 0xe7, 0xe9, 0xce, 0xd7, 0xf3, 0x3c, 0xaa, 0x41, + 0x05, 0x64, 0xa9, 0xc6, 0x99, 0x0d, 0xe2, 0x98, 0xbb, 0xcd, 0x55, 0x4b, 0xc2, 0x37, 0x0b, 0x16, 0xdd, 0x75, 0xc0, + 0x91, 0x17, 0xcc, 0x31, 0x44, 0x70, 0xf8, 0xde, 0x32, 0xca, 0x77, 0xdc, 0x89, 0x1d, 0x0f, 0xc2, 0x22, 0x2c, 0xcf, + 0x5a, 0xd1, 0xb0, 0xfa, 0x81, 0x37, 0x7b, 0xf9, 0xff, 0xda, 0x50, 0xad, 0x17, 0x51, 0x95, 0x6c, 0x2d, 0x04, 0x58, + 0x17, 0xdb, 0x3c, 0x7e, 0x8a, 0x37, 0x76, 0xad, 0x91, 0xc4, 0xc3, 0x73, 0xf7, 0xd3, 0x3d, 0x41, 0x39, 0xce, 0xcf, + 0x3b, 0xbc, 0x46, 0x74, 0x06, 0xf1, 0x11, 0x78, 0x2f, 0xd7, 0x36, 0x28, 0xf8, 0xff, 0xfc, 0x57, 0x97, 0xb1, 0x5a, + 0xd6, 0x03, 0x56, 0x37, 0xfc, 0xb4, 0x0d, 0xde, 0xba, 0xbf, 0x9d, 0x6f, 0xe8, 0xd3, 0xb8, 0xde, 0x63, 0xad, 0xde, + 0x59, 0x97, 0xd6, 0x8c, 0x4e, 0xc2, 0x32, 0x8b, 0xb9, 0x12, 0xb2, 0x9e, 0x6b, 0xf3, 0x48, 0x86, 0x0f, 0x6b, 0xdb, + 0x96, 0x92, 0x83, 0x36, 0x0e, 0x4d, 0xb9, 0xa4, 0x42, 0xda, 0x54, 0x46, 0xff, 0x66, 0xaa, 0x28, 0x99, 0xf5, 0x74, + 0xf6, 0x2c, 0xba, 0x61, 0xa1, 0x4f, 0x81, 0x0c, 0x3c, 0xaf, 0x83, 0xaa, 0x25, 0x69, 0x2a, 0x44, 0x46, 0xa8, 0xa6, + 0x01, 0x6a, 0x9f, 0x54, 0x35, 0x14, 0x9e, 0xeb, 0x39, 0x1d, 0xfc, 0xc2, 0x47, 0x22, 0x48, 0x46, 0x12, 0x47, 0x0d, + 0x32, 0x65, 0x6f, 0xd7, 0xba, 0x57, 0xd7, 0xe9, 0xa9, 0xd3, 0x9c, 0x6d, 0x3e, 0xf2, 0xbf, 0x72, 0x73, 0x52, 0x2c, + 0xb6, 0x11, 0x88, 0x0b, 0x79, 0x8b, 0x29, 0xdb, 0x63, 0xc3, 0x30, 0xa8, 0xe3, 0xd7, 0x9b, 0x36, 0x6e, 0xa3, 0x04, + 0x11, 0xad, 0xe3, 0x93, 0x66, 0x8d, 0x8b, 0xaf, 0x5b, 0xe6, 0x9b, 0xcb, 0xaf, 0xd7, 0x38, 0xaa, 0xf5, 0xd9, 0x4e, + 0x95, 0x9b, 0x6b, 0x71, 0x54, 0x9b, 0xc6, 0x24, 0x3d, 0x21, 0x5b, 0x5e, 0xa0, 0x4d, 0xb9, 0xc9, 0x78, 0x83, 0xd2, + 0x40, 0xea, 0x05, 0xf3, 0x70, 0xb4, 0x33, 0x4f, 0xc7, 0xb7, 0x13, 0x2b, 0x67, 0x13, 0x5d, 0xda, 0x4d, 0xad, 0x85, + 0xf0, 0x78, 0x31, 0xe6, 0x35, 0x87, 0x7c, 0x56, 0x71, 0xe6, 0x7a, 0x1e, 0x4a, 0x8a, 0x30, 0xe1, 0xda, 0x2c, 0xd1, + 0x9b, 0x9b, 0x10, 0x16, 0x4b, 0x4e, 0x17, 0x08, 0x1d, 0x24, 0xcd, 0x77, 0xb4, 0xcf, 0x57, 0x7a, 0x78, 0x7e, 0x7f, + 0x4a, 0xb4, 0xdd, 0x3c, 0xe9, 0xec, 0xf3, 0xbb, 0x67, 0x1a, 0x75, 0xab, 0x54, 0xdb, 0x2a, 0x8a, 0x59, 0x23, 0x01, + 0x57, 0xeb, 0x08, 0xf4, 0xaa, 0x96, 0x3d, 0xd4, 0xd2, 0x47, 0xdd, 0xd5, 0x51, 0xab, 0xe7, 0xa2, 0xd6, 0x3e, 0x95, + 0x2e, 0xcf, 0x37, 0x8d, 0xc2, 0x90, 0x8b, 0xa2, 0x18, 0xa5, 0xa7, 0xdd, 0xb5, 0xf9, 0x32, 0x37, 0xa0, 0x99, 0x7b, + 0x07, 0xb5, 0x3c, 0x0f, 0xa8, 0xe7, 0xc6, 0xff, 0xa9, 0xd8, 0xf2, 0x18, 0x11, 0xaa, 0x3c, 0x9b, 0xa7, 0x15, 0x88, + 0xea, 0xda, 0x92, 0x6f, 0x8c, 0x64, 0xff, 0xf0, 0x8f, 0x7a, 0x3f, 0x4e, 0x4e, 0xe5, 0xef, 0xc0, 0x1d, 0x7d, 0x10, + 0xf7, 0x90, 0xc2, 0x55, 0xbc, 0xe6, 0x2e, 0xb7, 0xc8, 0x34, 0xf8, 0xff, 0xf8, 0xa5, 0x1b, 0x60, 0xe7, 0x78, 0x39, + 0x89, 0xe8, 0x5d, 0x09, 0x4a, 0xda, 0x94, 0xe1, 0x9a, 0x13, 0x0c, 0xf0, 0x7b, 0xdd, 0x31, 0xe5, 0xbc, 0xb1, 0x1c, + 0xc5, 0x52, 0x03, 0x3e, 0x54, 0xf9, 0x84, 0xf6, 0x8f, 0xed, 0x25, 0xbb, 0xba, 0x3c, 0xb6, 0x8d, 0xb3, 0xd4, 0xa6, + 0x65, 0xfb, 0x48, 0x34, 0x82, 0xea, 0x25, 0x81, 0x25, 0xb5, 0xcb, 0xaf, 0x6c, 0xab, 0x50, 0x36, 0x9b, 0xe0, 0xb2, + 0xf6, 0xe6, 0xae, 0x2c, 0x46, 0x9a, 0x65, 0xec, 0x3b, 0xcc, 0x76, 0x63, 0xed, 0xf4, 0x08, 0x09, 0xd5, 0xd3, 0x8a, + 0x65, 0xe9, 0x0b, 0xb1, 0xa7, 0x9f, 0x8f, 0xfa, 0xa9, 0x3d, 0x05, 0x00, 0x30, 0x5a, 0xef, 0x51, 0xc9, 0x0e, 0xae, + 0xdb, 0x20, 0xc0, 0x07, 0x7a, 0x7d, 0x03, 0xa0, 0x39, 0x9e, 0xdd, 0x16, 0x2c, 0xc5, 0x4f, 0xe2, 0x23, 0xf3, 0xb0, + 0xb3, 0x0c, 0x26, 0x70, 0x6b, 0xd6, 0x01, 0xa7, 0x6b, 0x37, 0x36, 0x6d, 0x9c, 0xac, 0xb3, 0xe9, 0xeb, 0x72, 0x0a, + 0x88, 0xe7, 0x6e, 0xdc, 0x1e, 0xa6, 0x8d, 0xab, 0x3b, 0x86, 0xf4, 0x29, 0x5a, 0xd6, 0x1d, 0x3f, 0x23, 0x0a, 0xab, + 0x22, 0xcb, 0x72, 0x86, 0xbd, 0x62, 0x9a, 0xa8, 0x79, 0xcb, 0xad, 0x82, 0x9c, 0x15, 0x60, 0xed, 0xf5, 0x7a, 0x40, + 0x38, 0xb5, 0x1e, 0x7c, 0xfb, 0x3f, 0x75, 0xd4, 0x77, 0xeb, 0xb8, 0x0b, 0xe7, 0x46, 0x0b, 0xcf, 0x53, 0x88, 0xb2, + 0x3c, 0x20, 0xb3, 0xe5, 0xaf, 0xff, 0xf2, 0x94, 0x7c, 0xd2, 0xee, 0xf6, 0x6f, 0xe8, 0xd6, 0x4f, 0x00, 0xc7, 0x36, + 0x3b, 0x40, 0x37, 0x48, 0x75, 0x83, 0x08, 0xda, 0xef, 0x19, 0xe8, 0x66, 0xa0, 0x8c, 0x8a, 0x89, 0xcf, 0x1b, 0xdd, + 0x55, 0x00, 0xfa, 0xfe, 0x9c, 0x6d, 0x71, 0xee, 0xb3, 0x20, 0x6f, 0xc1, 0xaa, 0xd9, 0x1f, 0x05, 0x86, 0x6b, 0xfb, + 0x81, 0x33, 0x08, 0xea, 0x5a, 0x07, 0x4a, 0xbb, 0xdc, 0x3e, 0x5a, 0x01, 0x88, 0x58, 0x58, 0x13, 0x36, 0x51, 0xfd, + 0x97, 0x48, 0x97, 0x34, 0x24, 0x61, 0xa6, 0xfa, 0x89, 0xd8, 0xb3, 0x96, 0x18, 0x56, 0x7a, 0x80, 0x4d, 0x9c, 0x07, + 0x74, 0x83, 0x28, 0x5e, 0x87, 0x06, 0xff, 0x4e, 0x58, 0x90, 0xf7, 0x94, 0xfa, 0xcc, 0x50, 0xd5, 0xa5, 0xb0, 0xe6, + 0x51, 0xaa, 0xc1, 0xf0, 0x1c, 0xda, 0x00, 0x6b, 0x29, 0x6a, 0x6e, 0xbb, 0x24, 0x95, 0x20, 0xf1, 0xc6, 0xd4, 0x77, + 0x33, 0xe0, 0xdd, 0xac, 0x0c, 0xa0, 0x40, 0x52, 0x5f, 0x50, 0xf4, 0xe6, 0xd1, 0x77, 0x6b, 0xc2, 0xb3, 0xd3, 0xdd, + 0x88, 0x49, 0xb2, 0xae, 0xb6, 0x10, 0x11, 0x8b, 0x68, 0xdb, 0x56, 0x85, 0xa1, 0x03, 0x99, 0xe8, 0x51, 0x95, 0xa4, + 0x04, 0xff, 0xcd, 0xd8, 0x2e, 0x46, 0xd4, 0x30, 0xa0, 0xa0, 0x60, 0x6f, 0x8f, 0xdb, 0x85, 0x2f, 0x6f, 0x69, 0xcc, + 0x2d, 0x05, 0x5e, 0x85, 0xc6, 0xc3, 0x3a, 0x90, 0x2b, 0xa2, 0x41, 0xe7, 0x5c, 0x3b, 0xe3, 0xe4, 0xb8, 0x99, 0xf9, + 0x12, 0xcf, 0x9f, 0x5b, 0xfb, 0x7d, 0xa5, 0x14, 0xf9, 0x37, 0x28, 0x24, 0x9c, 0x5a, 0x55, 0xa3, 0xaa, 0x0f, 0x60, + 0x4d, 0xd7, 0x04, 0x77, 0xc6, 0xb3, 0x4f, 0x08, 0x24, 0x3f, 0xe5, 0x52, 0x67, 0x93, 0x36, 0x02, 0xa3, 0x2f, 0xc1, + 0xe4, 0x85, 0xe9, 0x6a, 0x01, 0x71, 0x78, 0xa0, 0x3f, 0x26, 0x8a, 0xe4, 0xa9, 0x22, 0x0a, 0x6a, 0x78, 0xaa, 0x32, + 0x5f, 0x2f, 0xf5, 0x3a, 0xb9, 0xd1, 0xd9, 0x13, 0x8f, 0x69, 0x9b, 0x9a, 0x21, 0x9b, 0xc9, 0x8b, 0xfb, 0xe1, 0xba, + 0x7b, 0x75, 0x1f, 0x14, 0x12, 0x27, 0x37, 0x0d, 0x90, 0xb7, 0xea, 0x44, 0x9e, 0x16, 0x24, 0x80, 0xe5, 0xd2, 0x34, + 0x6b, 0xbf, 0x3c, 0x9a, 0x20, 0x84, 0xcf, 0x86, 0xbd, 0x71, 0x5c, 0x34, 0x4d, 0x27, 0x06, 0x0e, 0x97, 0x37, 0x39, + 0x9e, 0x19, 0x99, 0xad, 0x3b, 0xdf, 0x34, 0x26, 0x1a, 0xba, 0x6a, 0x57, 0x86, 0xc9, 0x1b, 0xa7, 0x5b, 0xee, 0xd4, + 0xee, 0x42, 0x19, 0x5c, 0xa4, 0xcb, 0xa6, 0x6c, 0x04, 0x20, 0xba, 0x76, 0x68, 0x94, 0x27, 0x47, 0x61, 0x42, 0x73, + 0x5b, 0x83, 0x17, 0x40, 0xdb, 0x3f, 0xea, 0x46, 0xa9, 0x17, 0x22, 0x2b, 0x3c, 0xfe, 0x68, 0x23, 0xd7, 0xcb, 0xad, + 0x53, 0xe5, 0xe5, 0x3a, 0x6a, 0xe7, 0xe5, 0xd6, 0xad, 0x66, 0xa5, 0xa9, 0x4d, 0xd8, 0x08, 0x6c, 0x2f, 0xbd, 0xe4, + 0x19, 0x7a, 0xfa, 0x19, 0x7a, 0xc6, 0x36, 0x2f, 0x44, 0x4c, 0xd9, 0xfa, 0x95, 0x57, 0xa4, 0x75, 0x60, 0xff, 0xb3, + 0x7f, 0xdf, 0x40, 0x64, 0x2a, 0x56, 0xbb, 0x4a, 0xc4, 0x14, 0xc8, 0x5a, 0xa1, 0x7e, 0x64, 0x1f, 0xb4, 0xff, 0xda, + 0xfb, 0xed, 0x7d, 0x6e, 0x2a, 0x9a, 0x0c, 0xf8, 0xfd, 0x09, 0x33, 0xcd, 0x31, 0xc4, 0x12, 0x33, 0xba, 0x1f, 0x5d, + 0x47, 0x0e, 0xa6, 0xdf, 0x95, 0x12, 0xff, 0xc9, 0x5d, 0x6d, 0xb7, 0x9c, 0x89, 0x38, 0x22, 0xee, 0x76, 0x40, 0x37, + 0x41, 0x7c, 0x7d, 0xfc, 0xb2, 0x06, 0x97, 0xb9, 0xda, 0x17, 0x59, 0xc8, 0xf0, 0xfa, 0x83, 0x87, 0xec, 0x33, 0xef, + 0xb2, 0x53, 0x73, 0x86, 0x6b, 0x0b, 0xd7, 0x83, 0xe2, 0xcd, 0xcc, 0xaf, 0x03, 0x2f, 0x2a, 0xee, 0x54, 0x67, 0x2f, + 0xaa, 0xf1, 0x10, 0x1a, 0xcf, 0x02, 0xf7, 0x68, 0x9d, 0xd2, 0xbf, 0x66, 0x21, 0x3e, 0x9b, 0xbc, 0xb1, 0x99, 0xc7, + 0xdc, 0xcd, 0x1c, 0x82, 0x94, 0x9f, 0xe4, 0x52, 0x85, 0x91, 0x85, 0x74, 0xd3, 0x92, 0x55, 0xc2, 0x5b, 0xbc, 0x33, + 0x8f, 0xbb, 0x3f, 0x97, 0x78, 0x07, 0xf5, 0x8a, 0xf0, 0x22, 0xb3, 0x27, 0xd7, 0x31, 0x0c, 0xcc, 0x12, 0x2e, 0x64, + 0x0e, 0x8d, 0xd4, 0xdb, 0x5b, 0x3c, 0x71, 0x41, 0xf5, 0x63, 0x78, 0xe7, 0x5d, 0x76, 0x59, 0xc9, 0xed, 0xe1, 0x63, + 0x3d, 0x39, 0x4b, 0xdc, 0x0b, 0x66, 0xce, 0xad, 0xc7, 0x7d, 0x75, 0x26, 0x69, 0x76, 0xe4, 0x22, 0x41, 0x63, 0x01, + 0x2b, 0x31, 0x94, 0x9e, 0xde, 0x71, 0x3c, 0xbb, 0x23, 0xc0, 0x0f, 0x8e, 0xf5, 0x6d, 0x4d, 0xd9, 0x78, 0x72, 0x76, + 0xbd, 0x0d, 0xdc, 0x97, 0x5d, 0xf7, 0x2e, 0x46, 0xa9, 0xba, 0xa1, 0x89, 0x6b, 0x33, 0x10, 0xd8, 0xdd, 0x54, 0x3c, + 0x85, 0xa3, 0xe9, 0xc6, 0xf7, 0x4d, 0x5d, 0x9b, 0x8e, 0x02, 0xd2, 0x2e, 0x01, 0xad, 0x6d, 0xbf, 0xb8, 0xa1, 0x7b, + 0x04, 0xc3, 0x26, 0xc4, 0xc8, 0xe6, 0x6a, 0x3f, 0xac, 0xc7, 0x76, 0xdc, 0xd0, 0x51, 0x67, 0x19, 0x3e, 0x73, 0xd9, + 0x01, 0x7d, 0x26, 0x2f, 0xf4, 0x5d, 0x6c, 0x88, 0x4e, 0xb0, 0xee, 0xd8, 0xe0, 0x93, 0x80, 0x38, 0xbf, 0xf1, 0x0e, + 0x17, 0x70, 0xb9, 0xc0, 0x12, 0xc0, 0xfa, 0xec, 0xc4, 0xe0, 0x99, 0xbe, 0x8b, 0x5d, 0xc7, 0xb9, 0x27, 0xdc, 0x55, + 0x28, 0x05, 0x2e, 0xcd, 0x4f, 0x21, 0x4f, 0x5e, 0xe8, 0x5e, 0x3f, 0x9f, 0x25, 0xa6, 0xee, 0x62, 0x6b, 0x7d, 0xd8, + 0x94, 0x0a, 0x98, 0xfb, 0x94, 0x4e, 0xad, 0x83, 0xe2, 0xed, 0x13, 0x5a, 0x20, 0x0c, 0x63, 0x7b, 0xff, 0xaa, 0x89, + 0x61, 0x3c, 0x8d, 0x67, 0x0e, 0x82, 0x81, 0x11, 0x61, 0x80, 0x2f, 0xf1, 0x5e, 0x4e, 0x48, 0x9e, 0xa0, 0x99, 0x7d, + 0x36, 0x52, 0xec, 0x5d, 0x9e, 0xf4, 0xf5, 0xee, 0x4c, 0x66, 0x8f, 0x0f, 0xef, 0x02, 0x5a, 0xef, 0xc6, 0x4b, 0x0d, + 0x8f, 0x65, 0xed, 0x72, 0x25, 0x96, 0x42, 0xbc, 0x76, 0xd1, 0xfa, 0x6f, 0xda, 0xe4, 0xb0, 0x45, 0x33, 0x18, 0xf9, + 0x4e, 0x5f, 0x9a, 0x63, 0x79, 0x47, 0x5e, 0xd8, 0x48, 0x35, 0x80, 0xe2, 0xca, 0x20, 0xc3, 0x90, 0xb5, 0x77, 0x58, + 0x44, 0x41, 0x61, 0xe8, 0x8a, 0x47, 0x7d, 0x4a, 0x44, 0x9d, 0x04, 0xd9, 0xc0, 0x5b, 0x60, 0x26, 0xae, 0x0c, 0x4e, + 0x01, 0xb5, 0xe2, 0x48, 0x78, 0xe6, 0x42, 0x5e, 0x1a, 0x76, 0x26, 0xee, 0x2a, 0xb0, 0x3c, 0xe5, 0x83, 0x5d, 0x5c, + 0x60, 0xe2, 0xbf, 0xee, 0xf5, 0x89, 0x81, 0x23, 0xdd, 0x8d, 0xb0, 0x05, 0x30, 0x0d, 0x76, 0xe5, 0x95, 0xbc, 0xe4, + 0xa9, 0xd7, 0x10, 0x9c, 0x5c, 0x6d, 0x5e, 0x9f, 0xcc, 0xfa, 0x12, 0x84, 0x52, 0xaa, 0x11, 0x76, 0x0b, 0xec, 0xda, + 0x72, 0xb6, 0xae, 0x4e, 0x26, 0x6a, 0xdb, 0xc8, 0xb8, 0x61, 0x3f, 0x63, 0xed, 0x22, 0x76, 0x7d, 0xbc, 0xd8, 0x85, + 0x6c, 0x2a, 0x76, 0x2e, 0x4c, 0x8a, 0x9c, 0x41, 0x07, 0x0e, 0xb5, 0xc3, 0x19, 0x31, 0xd3, 0xed, 0x74, 0xc9, 0x76, + 0xfa, 0xe0, 0x9d, 0x76, 0x4e, 0x05, 0x74, 0x06, 0xe3, 0xec, 0xd4, 0x4e, 0x1a, 0xa1, 0x0b, 0x8c, 0xef, 0x4a, 0xe8, + 0x17, 0xb3, 0x54, 0xc3, 0x6b, 0xd6, 0x80, 0xb4, 0x92, 0x0a, 0xe6, 0xd9, 0x8a, 0x2d, 0x29, 0xdc, 0xe6, 0xc5, 0xd6, + 0xe2, 0x7f, 0xe9, 0x47, 0xba, 0x2e, 0x0f, 0x88, 0x20, 0x1b, 0x88, 0xd2, 0x49, 0xf0, 0x9f, 0xc5, 0x0c, 0x7f, 0x6e, + 0x60, 0xf4, 0x32, 0xb3, 0x78, 0x9a, 0xe5, 0xa8, 0x19, 0xdf, 0x19, 0xfe, 0x54, 0x46, 0xf2, 0x93, 0x2c, 0x27, 0xf0, + 0xbe, 0x0e, 0x01, 0x8e, 0x4c, 0xed, 0xe7, 0x2c, 0x67, 0xbc, 0x82, 0x96, 0x8d, 0xa2, 0x43, 0x14, 0xf5, 0xba, 0x7e, + 0xee, 0xc3, 0xe2, 0x9c, 0x50, 0x82, 0x91, 0x4d, 0x2f, 0xa1, 0x10, 0xa2, 0xf7, 0xcd, 0xb1, 0x18, 0x99, 0xbf, 0x28, + 0xe1, 0x04, 0x0b, 0x39, 0xd0, 0xbd, 0x2a, 0x20, 0x1d, 0x9f, 0x08, 0x0d, 0x13, 0x73, 0xb6, 0x91, 0xa9, 0xce, 0x26, + 0x16, 0xd2, 0x59, 0x71, 0xcd, 0xb1, 0x94, 0x56, 0x73, 0xe5, 0x75, 0x19, 0x75, 0x27, 0xe5, 0x97, 0xd0, 0xf4, 0x7a, + 0xcb, 0x59, 0x8c, 0x2a, 0xfc, 0xa7, 0xc3, 0x84, 0x6f, 0xd0, 0x2e, 0x2c, 0x67, 0x1d, 0x22, 0x54, 0xc1, 0x03, 0x5d, + 0x93, 0xbe, 0x8e, 0x56, 0xc3, 0x08, 0xcd, 0xdd, 0x0a, 0x0a, 0x84, 0xb4, 0x3f, 0xb0, 0xa8, 0x6d, 0xac, 0x9a, 0xb3, + 0x77, 0x47, 0x3b, 0x58, 0x35, 0x98, 0x9f, 0x1c, 0x05, 0xac, 0xba, 0x91, 0x70, 0xb1, 0xfc, 0x95, 0xc3, 0x43, 0x56, + 0x70, 0xf3, 0x01, 0xdc, 0x7e, 0x00, 0xe3, 0x98, 0xf1, 0x91, 0x3d, 0x69, 0x01, 0xc3, 0x99, 0xeb, 0x01, 0xef, 0x8d, + 0x53, 0x6f, 0xa4, 0xd1, 0x81, 0xa3, 0xab, 0xe5, 0x25, 0x7e, 0x28, 0x22, 0xc3, 0xaf, 0x49, 0x54, 0xd7, 0x34, 0x83, + 0x3c, 0x95, 0xd2, 0xe4, 0xd8, 0x1d, 0x50, 0x00, 0x7b, 0x1f, 0xe8, 0x98, 0xb6, 0x31, 0x93, 0xeb, 0x29, 0xba, 0x24, + 0xcd, 0xeb, 0x79, 0x68, 0x3f, 0x62, 0x9d, 0x11, 0x50, 0x8e, 0xed, 0x01, 0xf3, 0x5d, 0x03, 0x54, 0x2d, 0xcc, 0xf9, + 0x6f, 0x6a, 0x6b, 0x73, 0xe8, 0x58, 0x3d, 0x20, 0x2c, 0xf9, 0xf5, 0x51, 0x0b, 0xf2, 0xfb, 0x58, 0xe1, 0xb0, 0x45, + 0xfb, 0x62, 0x47, 0xaa, 0x0e, 0x6a, 0x85, 0xc1, 0x45, 0xa5, 0xca, 0xd4, 0x11, 0xc3, 0x0b, 0xb2, 0x49, 0x28, 0x14, + 0x1a, 0xc7, 0x13, 0x1b, 0xd0, 0xec, 0x8f, 0x58, 0x4a, 0xbb, 0x4b, 0x7e, 0xcd, 0x04, 0x91, 0xe6, 0xa5, 0xbf, 0xdb, + 0xe1, 0xa2, 0x0e, 0x17, 0xaf, 0x79, 0xa3, 0xe7, 0xb4, 0x09, 0xcd, 0x16, 0x63, 0xbc, 0x52, 0x6d, 0x82, 0xec, 0x4e, + 0xf3, 0xe8, 0x68, 0xc7, 0x16, 0x85, 0x24, 0x64, 0x7f, 0x79, 0x6d, 0x26, 0x47, 0x15, 0xfd, 0xbb, 0xc8, 0x59, 0x96, + 0x12, 0xe2, 0x1d, 0xf8, 0x45, 0x4f, 0xb9, 0x92, 0x46, 0xa7, 0xba, 0x23, 0xba, 0xa0, 0xa8, 0x7f, 0x76, 0x34, 0x0c, + 0xb3, 0x3c, 0x19, 0x54, 0xc1, 0x01, 0x8c, 0xd9, 0xe8, 0xa3, 0xeb, 0x85, 0x4b, 0x31, 0x98, 0xf2, 0xb0, 0x6a, 0xb3, + 0xb6, 0x61, 0xea, 0xc6, 0xb8, 0x30, 0x5c, 0x33, 0xb6, 0x31, 0x01, 0x18, 0xdb, 0x34, 0xdb, 0x4c, 0x9b, 0xfe, 0xfe, + 0xa9, 0xb0, 0x7a, 0x48, 0x24, 0x22, 0x90, 0x0f, 0xa2, 0x0f, 0x06, 0xa4, 0x7f, 0x5d, 0xa4, 0x60, 0x74, 0xa9, 0x15, + 0x3f, 0x89, 0x15, 0x1d, 0xf0, 0x9b, 0x8d, 0x77, 0x68, 0x18, 0x73, 0xde, 0x29, 0xc6, 0x5c, 0x9e, 0x82, 0x1d, 0x76, + 0x0e, 0xc2, 0x93, 0x2a, 0x46, 0xdb, 0x93, 0x58, 0x41, 0xd3, 0xf3, 0x05, 0xac, 0x6f, 0x12, 0x56, 0x63, 0x98, 0x56, + 0x33, 0x37, 0xc5, 0x5d, 0x2e, 0x3c, 0x66, 0x4d, 0xd6, 0x82, 0xf8, 0x20, 0x10, 0x59, 0x26, 0x41, 0x04, 0x3d, 0x16, + 0xde, 0x93, 0x99, 0x57, 0xc8, 0xd2, 0x0d, 0xba, 0x8c, 0x8c, 0xfb, 0xc0, 0x86, 0xfa, 0x41, 0xfd, 0xa0, 0x85, 0x74, + 0xa1, 0xf9, 0x6b, 0x54, 0xd5, 0x32, 0xcf, 0x71, 0xa3, 0x1c, 0x5a, 0x72, 0x6e, 0x8e, 0x40, 0x0b, 0x81, 0x50, 0x7b, + 0x35, 0x37, 0xc0, 0x58, 0x7e, 0x08, 0x87, 0x9c, 0x03, 0x40, 0x67, 0x31, 0xe1, 0xf1, 0x6d, 0x1b, 0x20, 0xb5, 0x71, + 0xf4, 0xa4, 0x6e, 0xed, 0xa6, 0xc9, 0x10, 0x41, 0x24, 0xbe, 0x1d, 0x03, 0xeb, 0xe7, 0xf9, 0x77, 0x11, 0x9d, 0x60, + 0xe8, 0xc7, 0xe2, 0x2e, 0x7f, 0x34, 0xf4, 0x54, 0x7f, 0x3e, 0x55, 0x4f, 0x62, 0xc4, 0x52, 0xc4, 0x8f, 0x79, 0x6d, + 0x2e, 0xa0, 0x14, 0xa5, 0xd9, 0x04, 0x46, 0x39, 0xd2, 0xe3, 0x4a, 0x92, 0x47, 0xa2, 0x81, 0x18, 0x44, 0xa3, 0xe2, + 0x9b, 0x2b, 0x5d, 0x3b, 0x87, 0xab, 0x78, 0xe5, 0xb1, 0xae, 0x97, 0xc7, 0xeb, 0x99, 0x9d, 0x7a, 0x2f, 0x07, 0xeb, + 0xd4, 0x3b, 0x2f, 0x44, 0x4b, 0x57, 0xc4, 0xe8, 0xf4, 0x13, 0x51, 0xab, 0x76, 0xbd, 0x6b, 0x04, 0x22, 0x09, 0x97, + 0xff, 0x19, 0x02, 0xb5, 0xf0, 0xe1, 0xd2, 0xb3, 0xc3, 0x62, 0x6e, 0x74, 0xf9, 0xa6, 0x19, 0x94, 0x02, 0x1e, 0xd4, + 0xea, 0xb8, 0x86, 0xe8, 0xdc, 0x56, 0xf2, 0x82, 0xd4, 0x7f, 0x12, 0xab, 0xa0, 0xe0, 0xbd, 0x95, 0xee, 0x79, 0x91, + 0x6a, 0xb8, 0xa9, 0x72, 0x07, 0xe8, 0xb5, 0xa8, 0x62, 0x4d, 0x83, 0x17, 0x76, 0xa6, 0x24, 0xf2, 0x81, 0x97, 0x10, + 0xa1, 0xfb, 0x4e, 0xcc, 0x1a, 0xa6, 0xad, 0x66, 0x67, 0xec, 0x46, 0x0f, 0xa1, 0x47, 0x31, 0x59, 0x5e, 0x2d, 0x21, + 0xe0, 0xb3, 0xdf, 0x46, 0xb3, 0xf7, 0x36, 0x25, 0x19, 0x17, 0xfe, 0x6d, 0x02, 0x4b, 0x6e, 0x71, 0x87, 0x41, 0x0c, + 0xd6, 0x1f, 0x09, 0x1e, 0x98, 0x83, 0xed, 0xa1, 0xb0, 0xac, 0x9e, 0xcd, 0x33, 0x0f, 0xb4, 0x8f, 0x51, 0x94, 0xd1, + 0x04, 0xf0, 0xc9, 0x7a, 0x47, 0x02, 0x80, 0x6c, 0x5d, 0xa1, 0x46, 0x9f, 0x74, 0x65, 0xdd, 0x55, 0x77, 0xc3, 0x52, + 0x19, 0xba, 0x1b, 0x59, 0x4b, 0x01, 0x1d, 0x8a, 0x53, 0x96, 0x59, 0xf4, 0xc6, 0x07, 0x55, 0x14, 0xf9, 0x3e, 0xb1, + 0x2d, 0x61, 0xea, 0xdb, 0x9b, 0x44, 0x17, 0x85, 0x44, 0x01, 0x61, 0x92, 0x9c, 0x78, 0x5f, 0x20, 0x71, 0xb6, 0x5e, + 0x22, 0x6a, 0xc2, 0xe5, 0x67, 0xf7, 0xca, 0x04, 0x1e, 0xb4, 0xf5, 0xac, 0xec, 0x02, 0x97, 0xd7, 0x17, 0x43, 0x10, + 0x05, 0x72, 0x0a, 0x62, 0x72, 0x09, 0x8a, 0x0f, 0xe6, 0x7a, 0x02, 0x4e, 0x91, 0xef, 0xa5, 0xe6, 0x7d, 0xe3, 0x00, + 0x1c, 0x85, 0x10, 0x8b, 0x91, 0x08, 0x08, 0xc9, 0x26, 0xc0, 0xa5, 0x15, 0x68, 0xf7, 0x17, 0x7b, 0xed, 0x0b, 0xf7, + 0x8f, 0xd8, 0xad, 0x30, 0x17, 0xb2, 0x30, 0x5a, 0x11, 0xdd, 0x4b, 0x02, 0x67, 0x87, 0x3a, 0xbf, 0x65, 0x96, 0xc6, + 0xb7, 0xee, 0xe7, 0xa0, 0x94, 0x80, 0xb0, 0xff, 0xc9, 0x38, 0x10, 0x00, 0x73, 0x69, 0x25, 0x6d, 0x89, 0x78, 0xf6, + 0x42, 0x9a, 0x0d, 0xfd, 0xc6, 0x86, 0x37, 0x0f, 0x15, 0x60, 0x49, 0xad, 0x5e, 0x30, 0x97, 0x55, 0xce, 0x68, 0x0d, + 0x4a, 0x78, 0xdb, 0x42, 0x57, 0xcf, 0x56, 0xbf, 0x17, 0xd5, 0x8f, 0xfb, 0xe1, 0x5b, 0xd5, 0x6d, 0x4f, 0xaa, 0x33, + 0xc9, 0x60, 0xf2, 0x54, 0xad, 0xa7, 0x59, 0x70, 0xf7, 0x7e, 0xa7, 0x00, 0x64, 0xa1, 0xf1, 0x76, 0xd6, 0xd9, 0xdc, + 0x1e, 0xfa, 0xc0, 0xfe, 0x80, 0x0b, 0x8a, 0x3f, 0x24, 0x1d, 0xdf, 0x27, 0x11, 0x11, 0x59, 0xdb, 0xb1, 0xcb, 0x5b, + 0xdb, 0x36, 0xad, 0x99, 0x97, 0x3e, 0x56, 0xdf, 0xfc, 0xf6, 0x96, 0xbc, 0x17, 0x25, 0x27, 0xe9, 0x0b, 0x56, 0xb7, + 0x68, 0x7b, 0xc8, 0xd3, 0x81, 0x09, 0x74, 0xeb, 0xbc, 0xf1, 0x4e, 0x31, 0xd2, 0xd4, 0x11, 0x47, 0x99, 0x8d, 0x0c, + 0x9e, 0xe9, 0x59, 0x1f, 0x1d, 0x28, 0x9e, 0x61, 0xba, 0x26, 0x62, 0x9f, 0xb8, 0xec, 0x0a, 0x3c, 0x21, 0x3a, 0x3e, + 0x82, 0x69, 0x43, 0xde, 0xaf, 0xc2, 0xfb, 0xe2, 0x58, 0xfc, 0x60, 0x31, 0x89, 0x7c, 0x90, 0x03, 0xbc, 0x0f, 0x6c, + 0x59, 0x61, 0x64, 0xe0, 0x73, 0xb6, 0x3b, 0x8e, 0x17, 0x80, 0x11, 0x0f, 0xb9, 0x47, 0x77, 0x7c, 0x0f, 0x02, 0xc7, + 0x33, 0xd5, 0x62, 0xe7, 0xb0, 0x04, 0x01, 0x90, 0x19, 0x2c, 0x8e, 0x8b, 0xd1, 0x28, 0x4a, 0x09, 0x78, 0x26, 0xa7, + 0x6e, 0xd5, 0x10, 0xcb, 0xec, 0x9b, 0x80, 0x72, 0xe9, 0x5a, 0x48, 0xc1, 0xad, 0xfa, 0xc6, 0x98, 0x90, 0x6e, 0x1b, + 0x0d, 0xb6, 0xf0, 0xaf, 0x05, 0xb1, 0xa4, 0x41, 0x5d, 0x93, 0xf7, 0x61, 0xb6, 0x50, 0xda, 0x9b, 0xae, 0xe3, 0xd4, + 0x25, 0x92, 0xb8, 0xb6, 0xc4, 0x48, 0x20, 0x98, 0x59, 0x87, 0x22, 0x8b, 0x21, 0xf6, 0xb1, 0xda, 0x02, 0x80, 0x27, + 0x10, 0x1d, 0x39, 0x66, 0xef, 0x11, 0xc5, 0xfd, 0x0b, 0xf8, 0x65, 0xf9, 0x03, 0x19, 0x7f, 0x7b, 0x3e, 0xce, 0x8e, + 0x28, 0xfa, 0x77, 0x12, 0x4f, 0x55, 0x2c, 0x81, 0x34, 0xb6, 0x03, 0x2c, 0x5d, 0x81, 0x5c, 0x06, 0x8e, 0x11, 0xeb, + 0x67, 0xd6, 0x27, 0xe0, 0xc7, 0x01, 0x16, 0x1d, 0xbf, 0x0e, 0x95, 0x5d, 0x4a, 0xe5, 0x6f, 0x95, 0x5b, 0x99, 0x31, + 0x38, 0x17, 0xba, 0x3b, 0x49, 0x53, 0xaf, 0xee, 0x6d, 0x43, 0x7d, 0x93, 0xa4, 0x7d, 0x6b, 0x09, 0x03, 0xee, 0xeb, + 0x24, 0x8d, 0xa1, 0xd0, 0x27, 0xcb, 0xe3, 0x26, 0xa9, 0x89, 0xa1, 0x37, 0x45, 0xbc, 0x9b, 0x6a, 0x8f, 0xd4, 0xee, + 0x4b, 0xd3, 0x2e, 0x02, 0xb3, 0xa4, 0xb1, 0x68, 0x32, 0xfc, 0x08, 0x58, 0x1c, 0x8a, 0x14, 0x23, 0x72, 0x19, 0x80, + 0xb0, 0x61, 0x07, 0x77, 0x52, 0x3d, 0xa6, 0xd9, 0x13, 0xf0, 0xd2, 0xa6, 0x78, 0xef, 0xaa, 0xc5, 0x84, 0x32, 0x61, + 0xf2, 0xe0, 0xad, 0xdd, 0x5c, 0x30, 0x53, 0x2a, 0x49, 0x20, 0x9b, 0xb2, 0x90, 0x2b, 0xf8, 0xd9, 0x8b, 0xbf, 0x7f, + 0x50, 0x54, 0x3a, 0x02, 0xee, 0x78, 0xd9, 0xfd, 0xb9, 0x11, 0xff, 0x64, 0x88, 0x47, 0x46, 0x66, 0xf8, 0x2f, 0x49, + 0xd6, 0xf8, 0x04, 0x32, 0x09, 0x2f, 0x69, 0x0f, 0xd2, 0x25, 0x3c, 0x52, 0xeb, 0x60, 0x96, 0x47, 0x1b, 0x9a, 0xc8, + 0xdf, 0x84, 0xc2, 0xaa, 0xab, 0x0c, 0x2c, 0xce, 0x32, 0xe4, 0xe7, 0x52, 0x73, 0x0c, 0xc0, 0x6f, 0x02, 0xf0, 0x86, + 0x86, 0x49, 0xc8, 0x08, 0xa0, 0xfa, 0x70, 0x34, 0xe9, 0x3a, 0x55, 0x5d, 0x90, 0x1d, 0x65, 0xd0, 0x2e, 0x0c, 0x89, + 0x80, 0x9e, 0xef, 0x6d, 0xf1, 0xa0, 0x79, 0x7a, 0x93, 0x1f, 0x10, 0xf8, 0xd8, 0x4f, 0xa3, 0x55, 0x75, 0xa0, 0xd6, + 0xe8, 0x19, 0x41, 0x4f, 0x33, 0xe0, 0x36, 0x2e, 0x87, 0xb0, 0x8d, 0x63, 0x58, 0x98, 0xf1, 0x62, 0x18, 0x2f, 0x86, + 0x5c, 0x79, 0x7b, 0x0a, 0xb1, 0x80, 0xdd, 0x80, 0xd4, 0x2c, 0x27, 0x64, 0x27, 0x52, 0xf6, 0x20, 0x50, 0x10, 0xa1, + 0xfc, 0xe6, 0x65, 0xfc, 0x1b, 0x21, 0x28, 0x08, 0xb0, 0x82, 0x68, 0xd5, 0x81, 0x64, 0x6b, 0x13, 0x39, 0xad, 0x25, + 0x55, 0x71, 0x7e, 0x29, 0xc3, 0xe4, 0x77, 0xfd, 0xf9, 0x75, 0x1b, 0x6d, 0x18, 0xc3, 0x53, 0x73, 0xcd, 0x46, 0x93, + 0xe1, 0x53, 0x66, 0x7f, 0x21, 0xc4, 0xc1, 0x21, 0xbf, 0x15, 0xc3, 0x7a, 0x28, 0x6d, 0x18, 0x3c, 0xea, 0x5c, 0x5a, + 0x45, 0xa5, 0xbd, 0x61, 0x27, 0x1a, 0xc6, 0xde, 0xf2, 0xe7, 0x3b, 0x81, 0x8f, 0x81, 0x2b, 0x0c, 0x10, 0xf4, 0x7f, + 0xfb, 0x5b, 0xef, 0x00, 0xff, 0x31, 0xa2, 0xc8, 0x81, 0xdb, 0x0a, 0x83, 0x8c, 0x6d, 0xb3, 0xed, 0x36, 0xfa, 0x98, + 0x6b, 0x3d, 0xf1, 0x42, 0x27, 0x0b, 0xe3, 0xdd, 0x1f, 0x41, 0x19, 0xe6, 0x45, 0x12, 0xe7, 0x45, 0x54, 0xe9, 0xc3, + 0xa2, 0xaa, 0x22, 0xdd, 0x3f, 0x00, 0x30, 0x0a, 0xe7, 0xd3, 0xef, 0xdf, 0x10, 0xaf, 0xec, 0xc6, 0xf7, 0x6a, 0x24, + 0x9f, 0x13, 0xe2, 0xf5, 0xdc, 0x77, 0xb8, 0x03, 0xb3, 0x5f, 0x21, 0x98, 0xb9, 0x9c, 0x4c, 0x06, 0xd7, 0xf4, 0x1b, + 0x05, 0x72, 0xfc, 0xd8, 0x0d, 0x1d, 0xd8, 0x4c, 0xa7, 0xf6, 0x96, 0xef, 0x07, 0xf8, 0xcc, 0x19, 0xcd, 0xea, 0xcd, + 0x8f, 0xbe, 0x6c, 0x50, 0x16, 0xe1, 0x8f, 0x63, 0x72, 0x20, 0x75, 0x95, 0x2e, 0x21, 0xe0, 0x59, 0x9f, 0x34, 0xd0, + 0x10, 0x4e, 0x6d, 0x44, 0x7e, 0x7f, 0x1b, 0x22, 0x7c, 0x67, 0x4f, 0x39, 0xea, 0x44, 0x8f, 0xa8, 0x27, 0x64, 0xb6, + 0xdd, 0xd6, 0x07, 0x64, 0x59, 0xb0, 0xf9, 0x06, 0xce, 0x53, 0xd3, 0x92, 0xc3, 0x4f, 0xfd, 0xb9, 0x09, 0x55, 0xa4, + 0xec, 0x77, 0xf0, 0x26, 0xc9, 0xc8, 0xa8, 0x50, 0xfe, 0x37, 0x59, 0x6f, 0x51, 0x32, 0xe2, 0x4b, 0x62, 0x2a, 0x98, + 0x3e, 0xf2, 0x98, 0xcd, 0x48, 0x4a, 0x5d, 0x6b, 0x25, 0x0f, 0xbe, 0x87, 0x42, 0xe3, 0xf4, 0x06, 0x21, 0xd8, 0x60, + 0x5a, 0x41, 0x3c, 0x5a, 0x31, 0xca, 0x1a, 0x4f, 0x26, 0xef, 0xb7, 0x52, 0x13, 0x7a, 0x7c, 0x5b, 0x25, 0x8b, 0x27, + 0x73, 0x47, 0xca, 0x47, 0x12, 0x47, 0xda, 0x28, 0x68, 0xf1, 0xa6, 0xe3, 0xfd, 0xac, 0x33, 0x22, 0x05, 0xbd, 0x9c, + 0x1b, 0x91, 0xf2, 0xcb, 0x1b, 0x66, 0xdf, 0x1e, 0x5c, 0x1e, 0x3c, 0x3e, 0x16, 0x45, 0x4e, 0x81, 0xbb, 0x75, 0x0d, + 0x2f, 0xfb, 0xa2, 0x2b, 0x61, 0x28, 0xa7, 0x94, 0x07, 0x85, 0xc2, 0xc8, 0x16, 0x48, 0x43, 0x7e, 0x12, 0xf9, 0xfb, + 0x61, 0x3e, 0x63, 0xf8, 0xba, 0xf4, 0x49, 0xca, 0x4a, 0x62, 0x7f, 0x22, 0xd2, 0x64, 0xe2, 0x8d, 0x79, 0xbf, 0xff, + 0x63, 0xf6, 0x62, 0x3f, 0x54, 0x19, 0x33, 0x77, 0x93, 0xc8, 0x42, 0x1d, 0x94, 0xf2, 0x15, 0x4e, 0x3b, 0xfc, 0xff, + 0x46, 0xa2, 0xed, 0x42, 0x0f, 0x6f, 0x0f, 0x3c, 0x14, 0xad, 0x49, 0x62, 0xdb, 0x7b, 0xba, 0x04, 0x2b, 0x54, 0xdb, + 0xf8, 0x72, 0x1a, 0x79, 0x56, 0xf4, 0x6a, 0x77, 0xc5, 0x70, 0xef, 0xa0, 0xbe, 0xc9, 0xaa, 0xfd, 0xd4, 0x64, 0x1a, + 0x58, 0x81, 0xc0, 0x77, 0x41, 0x6e, 0x1d, 0x27, 0xae, 0xd7, 0xda, 0xdf, 0xad, 0x36, 0x61, 0xb5, 0xa1, 0x6e, 0x41, + 0x64, 0x5e, 0x30, 0xb0, 0x2c, 0x9a, 0x27, 0x11, 0x14, 0xc3, 0x32, 0x18, 0x72, 0xa6, 0x64, 0x81, 0xf3, 0x56, 0x40, + 0x1e, 0xdd, 0x2b, 0x38, 0x21, 0x61, 0xb4, 0x8e, 0x35, 0x1b, 0xf9, 0x22, 0x14, 0xf9, 0xaa, 0x6d, 0x36, 0x2e, 0xc5, + 0x3d, 0x4d, 0x6a, 0x15, 0xd0, 0x5c, 0x56, 0x30, 0x85, 0xf6, 0x8d, 0x42, 0xe2, 0x4c, 0x23, 0x79, 0xfe, 0x92, 0xee, + 0xc7, 0xb1, 0x41, 0x17, 0xf9, 0x2b, 0x0d, 0xdb, 0xce, 0xb5, 0xbe, 0xfb, 0xa0, 0x00, 0x5f, 0xa8, 0x23, 0xf3, 0x29, + 0xed, 0x2f, 0xab, 0xd6, 0x0d, 0x5f, 0xa8, 0xe5, 0x17, 0x13, 0xfd, 0xa5, 0x92, 0x66, 0x7f, 0x5f, 0x5a, 0xa0, 0x94, + 0xa7, 0x59, 0xae, 0x9a, 0x5a, 0x3e, 0xf2, 0x3f, 0xcd, 0x93, 0x09, 0x99, 0xd0, 0x39, 0x2c, 0x1f, 0x15, 0x69, 0x76, + 0x9f, 0x3f, 0x70, 0xff, 0x86, 0x57, 0xca, 0xde, 0x13, 0xe7, 0x31, 0xbd, 0xa7, 0xc4, 0xe6, 0xcc, 0x03, 0x0c, 0xb2, + 0x22, 0x4e, 0x6d, 0xab, 0xd9, 0x3b, 0x92, 0x39, 0x56, 0xdd, 0x65, 0x72, 0xc2, 0x64, 0xe8, 0xae, 0xe2, 0x83, 0x46, + 0xd8, 0xcd, 0xf7, 0x89, 0x0f, 0x2c, 0x82, 0x66, 0x94, 0xee, 0x6a, 0xb7, 0x7f, 0x2c, 0x57, 0xb1, 0x48, 0xa4, 0xc8, + 0x56, 0xd4, 0x6a, 0x9f, 0xe8, 0x21, 0x7f, 0x2b, 0xf9, 0xf9, 0x3f, 0x95, 0x88, 0x1b, 0x87, 0xd3, 0x7f, 0x4e, 0x54, + 0x10, 0x06, 0xb5, 0x43, 0x46, 0xb2, 0x84, 0x0a, 0x14, 0xc6, 0xce, 0x04, 0xdb, 0x5f, 0xb0, 0x1e, 0x60, 0x91, 0x48, + 0x22, 0x69, 0x28, 0x8f, 0x2c, 0x11, 0xa8, 0x12, 0xdf, 0x53, 0x97, 0xcd, 0x0f, 0x51, 0xb4, 0x71, 0x7d, 0x1e, 0x10, + 0x2a, 0xd3, 0x96, 0x2b, 0xe7, 0xb4, 0x72, 0xe5, 0x5e, 0x90, 0x18, 0x49, 0x24, 0x38, 0x82, 0xda, 0xa8, 0x21, 0x7c, + 0x48, 0x4a, 0x2f, 0x9d, 0x54, 0xde, 0xb5, 0xb8, 0x26, 0x8f, 0x02, 0x88, 0x4d, 0xc6, 0xf8, 0xf5, 0xe5, 0xaa, 0xdd, + 0x78, 0xba, 0x6c, 0xc4, 0xe2, 0x77, 0x0a, 0x38, 0x41, 0xba, 0xaf, 0x28, 0xa0, 0x37, 0x60, 0x55, 0x07, 0x7c, 0xfb, + 0x04, 0x8e, 0x4a, 0x07, 0x8a, 0xe9, 0xc8, 0xa1, 0x22, 0xbf, 0x03, 0xae, 0xdd, 0xad, 0x88, 0xf0, 0xed, 0xf2, 0x35, + 0x2b, 0x6a, 0x09, 0x41, 0xad, 0x35, 0x89, 0x66, 0xb6, 0x08, 0xc5, 0xc6, 0xbd, 0x80, 0xcb, 0x73, 0x28, 0xfa, 0xf0, + 0x12, 0x17, 0x71, 0xe0, 0xfd, 0xc5, 0x4b, 0x9b, 0x27, 0xd3, 0x29, 0xfd, 0xd6, 0x77, 0x74, 0xf0, 0xe8, 0xe5, 0xc3, + 0xb2, 0x48, 0x27, 0x29, 0x78, 0xc4, 0x20, 0x08, 0x93, 0xf4, 0x89, 0x47, 0x4c, 0x76, 0x9a, 0x77, 0x1e, 0xdc, 0xc5, + 0x05, 0xb2, 0x1f, 0x46, 0xf0, 0x10, 0xd9, 0x5e, 0x9a, 0x38, 0xa0, 0x3d, 0xb7, 0x75, 0x76, 0x32, 0x6e, 0x9e, 0x78, + 0x2f, 0x5a, 0xf0, 0x2a, 0xe6, 0x7d, 0xe2, 0x11, 0xc6, 0x0c, 0x7e, 0xee, 0x12, 0x92, 0x1c, 0x6b, 0xa9, 0xe0, 0x28, + 0xe8, 0x81, 0x7c, 0x31, 0x92, 0x51, 0x9a, 0xd1, 0xb7, 0x3f, 0x9b, 0xbb, 0x1d, 0xed, 0xaa, 0xdb, 0x6c, 0x83, 0x8b, + 0xae, 0x8c, 0x5f, 0xcf, 0x7d, 0xd9, 0xb1, 0xa3, 0x2b, 0x37, 0x0e, 0xd8, 0x96, 0xd1, 0x9b, 0x2e, 0xb1, 0x58, 0x69, + 0x0d, 0xa9, 0x20, 0xf6, 0x65, 0x4e, 0xe0, 0xd2, 0x4a, 0x73, 0x52, 0xb0, 0x06, 0xef, 0x32, 0x3d, 0x59, 0xad, 0xbe, + 0x7b, 0x29, 0xeb, 0xe1, 0x19, 0x7f, 0xdb, 0x0b, 0xd5, 0xc8, 0xc5, 0xef, 0xa7, 0x1c, 0x64, 0x1d, 0x5d, 0x64, 0xc8, + 0xa4, 0x2f, 0xd0, 0xde, 0x37, 0x59, 0x04, 0x36, 0xe1, 0x80, 0xdc, 0xbb, 0x95, 0xda, 0xc0, 0x65, 0xbc, 0xb1, 0x89, + 0xe8, 0x69, 0x2c, 0xc2, 0x69, 0x2f, 0xec, 0xc1, 0xdf, 0xd1, 0xcd, 0x34, 0xe7, 0xa9, 0xbc, 0x74, 0xdf, 0xde, 0xbb, + 0x74, 0xbf, 0x48, 0xbe, 0xc7, 0xee, 0xf3, 0xc5, 0xfb, 0x45, 0x1d, 0xde, 0x1c, 0xd8, 0x3f, 0x33, 0xbc, 0xb4, 0x47, + 0xcd, 0xcd, 0xdb, 0x9a, 0x7d, 0x2d, 0xf6, 0x5b, 0x87, 0x37, 0x5d, 0x44, 0x99, 0x9d, 0x33, 0x1a, 0x5b, 0xf3, 0x6b, + 0xbc, 0x7e, 0xfc, 0xf1, 0x67, 0xfe, 0x59, 0xce, 0x0b, 0x86, 0x61, 0x8d, 0x9f, 0x77, 0x59, 0x0f, 0xab, 0xb6, 0x7e, + 0x2c, 0xf0, 0xd1, 0xdc, 0xbc, 0xa3, 0xda, 0xb8, 0xb7, 0x48, 0x1f, 0xad, 0xdc, 0x35, 0x9f, 0x3c, 0x32, 0xb8, 0xf0, + 0x23, 0xf3, 0xf9, 0xa8, 0x1f, 0x41, 0xc8, 0xe5, 0x4f, 0xde, 0x45, 0x62, 0xfa, 0xc7, 0xd6, 0x2b, 0xb1, 0x88, 0x7d, + 0xaa, 0x81, 0x70, 0x37, 0x5e, 0xa4, 0x45, 0xd9, 0x77, 0xf4, 0x0b, 0x7b, 0x4c, 0x56, 0xef, 0x40, 0x04, 0x0a, 0x5a, + 0x9f, 0x9d, 0x24, 0x12, 0x3f, 0x44, 0xff, 0xe4, 0x8f, 0xa8, 0xd3, 0x4e, 0xb4, 0xff, 0xd8, 0x4e, 0xf8, 0xff, 0xa7, + 0x6b, 0x5b, 0xf5, 0x96, 0x0d, 0xf0, 0xd6, 0xff, 0x05, 0xa2, 0x0d, 0x6d, 0xaf, 0x04, 0xe9, 0xa1, 0x8b, 0x88, 0xfc, + 0xd9, 0xad, 0x66, 0x59, 0x61, 0xf5, 0x32, 0x57, 0x2c, 0xe3, 0x09, 0x9d, 0x93, 0x0b, 0x8d, 0x93, 0x34, 0x4d, 0x19, + 0xbc, 0x67, 0xd2, 0xec, 0x75, 0x2d, 0x43, 0xef, 0x36, 0x67, 0x8f, 0xd0, 0x49, 0x3a, 0xce, 0x92, 0x8a, 0x45, 0xb4, + 0x6e, 0x57, 0x6d, 0x9e, 0xad, 0x47, 0x70, 0x06, 0x07, 0x67, 0x59, 0x40, 0xef, 0xc1, 0x52, 0xdb, 0x9d, 0x1b, 0x3b, + 0x4c, 0xf7, 0x2f, 0x2d, 0x87, 0x23, 0x82, 0xc6, 0xde, 0x2c, 0xb7, 0xed, 0x8f, 0x4a, 0x0a, 0x15, 0xf1, 0xe6, 0xc0, + 0x40, 0x2f, 0x7e, 0x3d, 0x91, 0x65, 0xd0, 0x75, 0x2f, 0x5f, 0x0b, 0x61, 0x59, 0xab, 0xb9, 0x76, 0x18, 0x9f, 0x0b, + 0xab, 0x20, 0xb4, 0x0b, 0x21, 0xce, 0x5e, 0xe8, 0x3a, 0x01, 0x4d, 0x8c, 0x78, 0x8b, 0x0b, 0x09, 0x36, 0x39, 0x28, + 0x54, 0x50, 0x3a, 0x3e, 0xd2, 0xee, 0x41, 0x10, 0x7a, 0x2e, 0xc6, 0x0a, 0xc8, 0xb1, 0xdc, 0x4e, 0x65, 0x22, 0x9a, + 0x24, 0x04, 0xae, 0xf2, 0x43, 0xf8, 0x8c, 0x64, 0xbc, 0x9c, 0xda, 0x45, 0x58, 0x77, 0x9b, 0xaa, 0xcd, 0x41, 0xef, + 0xfe, 0x27, 0xac, 0x42, 0x17, 0x45, 0xd1, 0x7a, 0xd6, 0x59, 0x9e, 0x47, 0xcc, 0x03, 0xb3, 0x49, 0x81, 0x46, 0x11, + 0x8a, 0xd0, 0xbf, 0x27, 0xf6, 0xd0, 0x88, 0x2a, 0xa3, 0xba, 0x60, 0x31, 0xac, 0x30, 0x57, 0x4e, 0x4b, 0x69, 0xa7, + 0x2c, 0x77, 0xa3, 0x1d, 0xe0, 0xc7, 0x57, 0xff, 0x6c, 0x43, 0xff, 0x72, 0xe9, 0x63, 0xdb, 0x8b, 0x2d, 0x36, 0xbf, + 0x7e, 0x3e, 0x4b, 0xef, 0x0e, 0x4f, 0xa3, 0xd8, 0x82, 0x9a, 0x40, 0x25, 0x13, 0x85, 0x52, 0xf3, 0xba, 0xe0, 0x10, + 0xb4, 0x50, 0xfc, 0x68, 0x54, 0xd4, 0xfb, 0x79, 0x35, 0x01, 0x05, 0x09, 0x20, 0x1c, 0x4b, 0x1a, 0x69, 0x97, 0xd8, + 0x82, 0x48, 0xeb, 0xb3, 0xf2, 0x6e, 0x84, 0xd7, 0x36, 0x68, 0x8a, 0xe0, 0x90, 0x25, 0xd3, 0xc2, 0xb0, 0x22, 0x83, + 0x04, 0x18, 0xb7, 0x11, 0xd2, 0x8b, 0xe4, 0x1f, 0x50, 0x02, 0xf0, 0x2a, 0xa2, 0xbd, 0x51, 0x99, 0x88, 0xe4, 0xa5, + 0xac, 0x51, 0x0a, 0x4b, 0x00, 0x02, 0xff, 0x32, 0xbf, 0x22, 0x58, 0x22, 0xd5, 0x58, 0xa3, 0x35, 0x9d, 0x11, 0xa8, + 0xad, 0x7e, 0x07, 0x44, 0x98, 0xd7, 0xd8, 0xcd, 0x68, 0x7e, 0x43, 0xb3, 0x24, 0xc6, 0xb8, 0x6a, 0x6f, 0x3e, 0xe7, + 0x72, 0xbb, 0xe6, 0xb2, 0xdf, 0x5a, 0x0f, 0xec, 0x4d, 0xba, 0xcf, 0x3a, 0xf3, 0xc0, 0xc7, 0xa7, 0x15, 0xce, 0xeb, + 0x25, 0x99, 0x95, 0xc7, 0x47, 0x5f, 0xf7, 0xae, 0xb5, 0x50, 0x73, 0xf3, 0xbe, 0x3e, 0xdc, 0xbc, 0x77, 0x6d, 0x85, + 0x1f, 0xfc, 0x5f, 0xc6, 0xf1, 0xb4, 0x46, 0x56, 0xfe, 0x5a, 0x15, 0xd5, 0x36, 0x62, 0x98, 0x78, 0x3b, 0x65, 0x08, + 0xe4, 0xa9, 0xda, 0x83, 0xdd, 0x49, 0x54, 0xda, 0x6f, 0x2a, 0x52, 0xb7, 0x9d, 0x0b, 0x45, 0x67, 0xa2, 0x4d, 0x72, + 0xc2, 0x33, 0x9e, 0x6f, 0x8e, 0x30, 0x89, 0xa4, 0xe5, 0x3a, 0x53, 0x37, 0xdc, 0xfd, 0x5c, 0xae, 0x3f, 0x6e, 0x1a, + 0xd0, 0x21, 0x8b, 0x8f, 0xfb, 0x90, 0x51, 0x10, 0x34, 0xbc, 0x6c, 0x96, 0x09, 0x79, 0xac, 0x13, 0xeb, 0x6a, 0xf7, + 0x65, 0x8a, 0xba, 0x3f, 0xd7, 0x2f, 0xc4, 0xb1, 0xb7, 0xc3, 0x24, 0xb1, 0xb1, 0x64, 0x9a, 0xb5, 0x46, 0x1a, 0x79, + 0x70, 0x7a, 0x6a, 0x7b, 0xe6, 0x65, 0x67, 0xf5, 0xd6, 0xcc, 0x03, 0x1d, 0x70, 0x12, 0x69, 0x0b, 0xe6, 0x64, 0x5e, + 0xcc, 0xcf, 0x4f, 0x07, 0xe9, 0xc6, 0xfd, 0x5c, 0x8d, 0xed, 0x88, 0xc7, 0xa4, 0x27, 0x09, 0xfc, 0x44, 0x09, 0xbe, + 0x25, 0x91, 0x06, 0xe0, 0xe5, 0xeb, 0xcb, 0x11, 0x64, 0xb6, 0x0a, 0x48, 0x4c, 0xb6, 0xf5, 0xeb, 0x4f, 0x01, 0x83, + 0xce, 0x56, 0x20, 0x01, 0x43, 0xf1, 0x33, 0x68, 0xbd, 0xc6, 0x97, 0x1a, 0x24, 0x29, 0x31, 0x3e, 0x99, 0xe1, 0xe6, + 0x93, 0x28, 0xf0, 0xf0, 0x5b, 0x47, 0xb6, 0xd9, 0x0e, 0x65, 0x9c, 0x41, 0xf5, 0x98, 0xd6, 0xc3, 0x29, 0x33, 0xe3, + 0xf9, 0x4c, 0x7a, 0x1c, 0x7f, 0xab, 0x80, 0xbc, 0x0f, 0xcc, 0xdb, 0xa0, 0x6c, 0x82, 0x70, 0x02, 0x83, 0xcb, 0xb9, + 0x17, 0x9b, 0x40, 0x5c, 0xbf, 0x84, 0x35, 0xa6, 0x93, 0x3c, 0x9f, 0xab, 0xc3, 0x0f, 0xe3, 0xb7, 0x1e, 0xb9, 0x37, + 0xbd, 0x57, 0x8c, 0x4b, 0xd3, 0x7a, 0xc1, 0x30, 0xbb, 0x52, 0x6c, 0x80, 0x5a, 0x05, 0x33, 0x5b, 0x32, 0x6e, 0xa5, + 0x45, 0x16, 0x38, 0x6e, 0x7a, 0xef, 0xd4, 0xec, 0xae, 0xad, 0x07, 0xa4, 0x4f, 0x34, 0x44, 0x8c, 0x4f, 0x55, 0xe0, + 0x12, 0x75, 0xfc, 0x1e, 0x7f, 0x7a, 0xcf, 0x5f, 0xc3, 0x38, 0x15, 0x6f, 0xb6, 0xf1, 0x22, 0x63, 0x2b, 0xcb, 0xf3, + 0xf7, 0xf1, 0x9e, 0x43, 0x4b, 0x6b, 0x3f, 0x8c, 0x31, 0x58, 0xc9, 0x67, 0x0a, 0xf5, 0x84, 0x1d, 0xff, 0x22, 0x91, + 0xfe, 0xd9, 0x5a, 0xfa, 0x5b, 0xfc, 0x33, 0xc2, 0xff, 0x31, 0x0c, 0x95, 0xe6, 0xcb, 0x3f, 0xa2, 0xfe, 0x23, 0xbe, + 0x50, 0x56, 0x7a, 0x91, 0x73, 0x4f, 0xb5, 0xd9, 0x98, 0x8f, 0xf6, 0x26, 0x4e, 0x0a, 0x18, 0xc5, 0x99, 0x89, 0x82, + 0x38, 0xcf, 0xd5, 0xf9, 0x48, 0xf2, 0xb7, 0x9a, 0xb8, 0xf0, 0xcb, 0xf1, 0xd1, 0xf0, 0xf1, 0xdc, 0xa7, 0xe7, 0xfd, + 0x1e, 0x52, 0xb9, 0x2f, 0x12, 0xd5, 0xb6, 0xb2, 0x7d, 0xf2, 0xf7, 0x5d, 0xe7, 0x8e, 0x08, 0xac, 0x3f, 0x0f, 0x3e, + 0xb3, 0x8c, 0x3f, 0x19, 0x5e, 0xa6, 0x29, 0x5a, 0x33, 0x16, 0x5f, 0xe8, 0x33, 0xad, 0x8d, 0xdf, 0xb8, 0x55, 0x65, + 0x9b, 0x1a, 0x50, 0x9b, 0x6e, 0x82, 0xc4, 0xa4, 0x82, 0x06, 0x65, 0x9d, 0xfb, 0xf4, 0x07, 0x44, 0x1b, 0x12, 0x8a, + 0x7e, 0xfc, 0x43, 0x21, 0xe8, 0x2e, 0x41, 0x02, 0x90, 0xc4, 0x30, 0x33, 0x7e, 0x29, 0x48, 0x07, 0x34, 0x3c, 0xd4, + 0xdb, 0xcb, 0xc6, 0x56, 0x7d, 0x0e, 0x39, 0xfe, 0x6d, 0x74, 0x7f, 0x60, 0xf5, 0xd0, 0x80, 0x8a, 0xe3, 0x70, 0xf9, + 0x7f, 0xf4, 0x86, 0x30, 0x8a, 0x7a, 0x48, 0xa8, 0x2e, 0x16, 0x8d, 0x7f, 0x3a, 0x4a, 0xca, 0x19, 0x4a, 0x96, 0x72, + 0x1e, 0x15, 0x03, 0xcf, 0x82, 0xa0, 0xba, 0xa2, 0xc7, 0x26, 0xcf, 0xc5, 0xb3, 0x82, 0x43, 0xfe, 0x4f, 0xb0, 0xec, + 0x32, 0xc4, 0x64, 0x8c, 0xf8, 0xce, 0x4f, 0x10, 0x96, 0x64, 0x35, 0x6c, 0xcc, 0x1e, 0xc2, 0xed, 0x47, 0x6f, 0xa0, + 0x21, 0xdc, 0x7c, 0x7c, 0x80, 0x04, 0x7c, 0xe3, 0xcd, 0xea, 0x6a, 0x54, 0xbe, 0x30, 0xa7, 0x5d, 0x41, 0xdc, 0x18, + 0x02, 0x10, 0x89, 0xe0, 0x54, 0x52, 0x80, 0xfa, 0x26, 0x69, 0x8b, 0x80, 0xc5, 0x84, 0x6b, 0x43, 0x72, 0xd0, 0x8e, + 0x8b, 0xc9, 0x99, 0x72, 0x28, 0x73, 0xea, 0x28, 0x05, 0xe4, 0x24, 0x8f, 0x0e, 0x64, 0xd6, 0xed, 0xa5, 0x64, 0x7c, + 0x95, 0x8d, 0x76, 0xd9, 0x42, 0xf5, 0x49, 0x84, 0x99, 0x44, 0x8c, 0x54, 0xf0, 0x24, 0x67, 0x65, 0x82, 0x7e, 0xd1, + 0x2e, 0xa6, 0x36, 0xe2, 0xe1, 0xde, 0x66, 0x46, 0xec, 0x22, 0xd0, 0xe0, 0xca, 0x21, 0x79, 0xc5, 0xab, 0x90, 0x41, + 0x90, 0x09, 0x0b, 0x84, 0x05, 0x5c, 0x68, 0xf7, 0x7d, 0xe2, 0x78, 0xd8, 0xef, 0x47, 0xc1, 0x25, 0xe7, 0xf5, 0x86, + 0x5d, 0x82, 0x3b, 0xaf, 0x7a, 0x7d, 0x5d, 0x5b, 0x87, 0x8f, 0x9f, 0x33, 0x22, 0x05, 0x96, 0x41, 0xde, 0xe0, 0xe5, + 0x41, 0x18, 0xf8, 0x81, 0x3d, 0x82, 0x97, 0xa9, 0xb3, 0x2f, 0xc2, 0x90, 0x60, 0xd6, 0x93, 0x1a, 0xd2, 0x96, 0x05, + 0x57, 0xa7, 0xd3, 0x36, 0xdb, 0x51, 0xa0, 0x46, 0xa9, 0xde, 0x17, 0x86, 0x32, 0x25, 0x5a, 0x65, 0x13, 0xd8, 0x21, + 0x10, 0x2d, 0x5b, 0x11, 0xbe, 0xc5, 0xdc, 0x84, 0x05, 0x3a, 0xe9, 0xb4, 0xcd, 0x76, 0xb4, 0x08, 0xdf, 0x80, 0x4e, + 0x2e, 0x26, 0x5c, 0x4c, 0xb9, 0xf4, 0x84, 0xbc, 0x3a, 0xa9, 0x40, 0x89, 0x09, 0x78, 0x28, 0x23, 0xc3, 0x7c, 0x9a, + 0xf2, 0xa5, 0x27, 0xbf, 0x89, 0x59, 0xb4, 0x5f, 0xe6, 0x3c, 0x0d, 0xd2, 0x06, 0x13, 0x1b, 0xa4, 0xa6, 0xd5, 0x49, + 0x12, 0xdf, 0x1f, 0x5a, 0xc0, 0x0c, 0x4a, 0x73, 0x7c, 0x6e, 0x63, 0x52, 0x4a, 0x45, 0xc8, 0xb8, 0x5e, 0x1e, 0xf5, + 0xec, 0x7c, 0x18, 0x09, 0x6a, 0x76, 0x13, 0x7e, 0xb6, 0x17, 0x8d, 0x96, 0x8a, 0x97, 0x93, 0x50, 0xc2, 0x08, 0x89, + 0x95, 0x80, 0x04, 0x79, 0x93, 0xd9, 0x00, 0xe5, 0x4f, 0xca, 0x95, 0xfb, 0x4a, 0xc6, 0x63, 0x87, 0x70, 0xc0, 0xcf, + 0x1c, 0x03, 0x47, 0x19, 0x35, 0xfa, 0x47, 0xf3, 0xc1, 0x61, 0x74, 0xea, 0x12, 0x86, 0x09, 0xc8, 0x72, 0x89, 0x35, + 0xca, 0xf8, 0x30, 0x40, 0xf5, 0xba, 0x1f, 0x26, 0x1b, 0xfc, 0x11, 0x4d, 0x78, 0x24, 0x1b, 0xe6, 0xb0, 0xab, 0x16, + 0x9a, 0x18, 0xe4, 0x01, 0xe4, 0x16, 0x55, 0x46, 0x8e, 0xcc, 0xe9, 0x3d, 0xaa, 0x1c, 0xad, 0xd0, 0xf7, 0x11, 0xb0, + 0x94, 0xea, 0xd9, 0xb5, 0x34, 0x55, 0x00, 0x4b, 0x68, 0x25, 0x50, 0xb6, 0x31, 0x9b, 0x7c, 0x6f, 0xd9, 0xde, 0xb8, + 0xcc, 0x46, 0xfe, 0x22, 0x8d, 0x58, 0xc3, 0x96, 0x80, 0x73, 0xf5, 0x81, 0xd0, 0x76, 0xb2, 0x07, 0x10, 0xa2, 0xa4, + 0x17, 0x99, 0xbb, 0x32, 0x41, 0x6e, 0x5e, 0xf3, 0x89, 0x36, 0xcb, 0x00, 0x5b, 0x0d, 0xc1, 0x9d, 0xe7, 0xc2, 0x37, + 0xa9, 0x39, 0x29, 0xb8, 0xe0, 0xe7, 0xfb, 0x2b, 0x44, 0x15, 0x5e, 0xe4, 0xba, 0x1b, 0xae, 0x45, 0x3c, 0x37, 0xe6, + 0x5f, 0xab, 0xc6, 0x8b, 0x45, 0x19, 0x96, 0xca, 0xbf, 0xa1, 0xc9, 0x16, 0xb2, 0x6f, 0xa9, 0xa4, 0xf1, 0x6f, 0x09, + 0x7b, 0xe2, 0x83, 0xd1, 0x88, 0x32, 0xdc, 0xc0, 0x9a, 0xf8, 0xc8, 0xbc, 0x8a, 0x5e, 0x1c, 0x0f, 0x08, 0x0e, 0x02, + 0x94, 0x81, 0x93, 0x10, 0x06, 0xe2, 0x73, 0x8c, 0x35, 0x5f, 0xb3, 0x1e, 0x73, 0xde, 0xf4, 0x26, 0xcf, 0x35, 0xbf, + 0xe0, 0x35, 0xa0, 0x02, 0xda, 0xc3, 0x8e, 0x1e, 0xcb, 0x60, 0x42, 0x33, 0x1e, 0x42, 0x3e, 0x7d, 0xf0, 0xdb, 0xfc, + 0x8c, 0xc1, 0x2c, 0x0a, 0x09, 0x32, 0x9c, 0xae, 0xba, 0x99, 0xa1, 0xd5, 0x26, 0xf0, 0x7f, 0xbf, 0xbb, 0x19, 0x35, + 0x44, 0xde, 0x8b, 0x90, 0xe9, 0x06, 0x32, 0xda, 0x62, 0x1a, 0x36, 0xcd, 0x34, 0x5b, 0x0d, 0x86, 0xea, 0xa3, 0xa6, + 0xaf, 0xf1, 0xda, 0x6b, 0x1d, 0x95, 0x43, 0xa7, 0x36, 0x30, 0xbc, 0xe7, 0xbf, 0x77, 0x0f, 0x05, 0x79, 0x91, 0x14, + 0x72, 0x3b, 0x1d, 0x22, 0xc3, 0x4d, 0xec, 0x58, 0xf7, 0xfa, 0x9f, 0x31, 0xe3, 0xe4, 0x33, 0x93, 0x39, 0x81, 0x4d, + 0x53, 0x53, 0xb4, 0xa0, 0x9f, 0x36, 0x4b, 0x73, 0xa7, 0xac, 0xf5, 0x5a, 0xf0, 0x4b, 0xab, 0xd4, 0x38, 0x64, 0x55, + 0xc3, 0xcb, 0x0b, 0x5d, 0x3c, 0xf1, 0x82, 0xbf, 0x0c, 0x4c, 0xb8, 0xf1, 0x53, 0x2b, 0xea, 0xea, 0xe2, 0x85, 0xbe, + 0xef, 0x3e, 0x4b, 0x79, 0x48, 0xb4, 0x45, 0x16, 0x1a, 0xb2, 0xb9, 0xc9, 0x8b, 0x99, 0x2f, 0x36, 0xa3, 0x82, 0xcf, + 0x57, 0x68, 0x20, 0x1b, 0x1c, 0xb6, 0xd5, 0x35, 0xbe, 0xf0, 0xbc, 0x4b, 0xa4, 0x32, 0x72, 0xb1, 0x17, 0x1c, 0xc2, + 0x61, 0xb9, 0xb4, 0x98, 0xb5, 0x7a, 0xfe, 0x0e, 0x9d, 0xf1, 0x14, 0xa7, 0x90, 0xa8, 0x94, 0x8b, 0x4f, 0xcc, 0xfe, + 0xac, 0xef, 0xf7, 0x39, 0xc3, 0xfb, 0x03, 0xc9, 0x67, 0xfd, 0x63, 0x5f, 0x6d, 0x82, 0xbd, 0x93, 0xb7, 0x4a, 0xfb, + 0xda, 0x86, 0x65, 0xec, 0x23, 0x05, 0x04, 0x7f, 0x53, 0x38, 0x35, 0xf4, 0x08, 0x53, 0xd2, 0x72, 0x2f, 0xf2, 0xdb, + 0x8a, 0x68, 0x89, 0x06, 0xde, 0xe2, 0xb8, 0x48, 0x9f, 0xb6, 0xe5, 0x5d, 0xb6, 0xd4, 0xf1, 0xd0, 0xad, 0x8c, 0x25, + 0xd1, 0xa8, 0xd2, 0xf4, 0x41, 0xf4, 0xdc, 0xd9, 0x92, 0xe8, 0xed, 0xce, 0x68, 0xf6, 0x24, 0x1f, 0x13, 0xea, 0x1a, + 0x71, 0xab, 0x9e, 0xb7, 0xd8, 0xd7, 0xd4, 0xec, 0x86, 0x5e, 0xa8, 0x19, 0x2a, 0x21, 0xab, 0xd1, 0x17, 0x6a, 0xfd, + 0x28, 0x42, 0x92, 0xec, 0xf1, 0xeb, 0x9a, 0x5f, 0x3e, 0xdf, 0x48, 0x85, 0x72, 0x75, 0x51, 0x51, 0xa0, 0xf9, 0x79, + 0x9a, 0x78, 0x82, 0x72, 0x5e, 0x4b, 0x5f, 0x7d, 0x6a, 0x00, 0x2a, 0x42, 0x72, 0x6b, 0x87, 0x86, 0x34, 0xb4, 0xcc, + 0xcd, 0xb3, 0x79, 0x16, 0x8a, 0x00, 0xdd, 0x33, 0x4f, 0x3c, 0x75, 0x31, 0x6e, 0xf6, 0x82, 0x41, 0xc5, 0xce, 0x13, + 0x93, 0x01, 0x70, 0x92, 0x3a, 0x88, 0x46, 0xb0, 0xb7, 0x3b, 0xcd, 0x3e, 0xe6, 0xe2, 0x19, 0xb8, 0x10, 0xd6, 0xd3, + 0xf2, 0xda, 0xb3, 0x68, 0xd7, 0x33, 0xa4, 0x49, 0xb7, 0x6f, 0x57, 0xe3, 0xd1, 0x05, 0x77, 0x64, 0xd2, 0x48, 0x68, + 0xa9, 0x86, 0x42, 0xae, 0x52, 0xb9, 0xa3, 0xee, 0xac, 0x99, 0x52, 0x6e, 0xa2, 0x70, 0x2b, 0x73, 0xd9, 0xba, 0x8c, + 0xe5, 0x1c, 0x61, 0x85, 0xad, 0xcc, 0x12, 0xcf, 0x02, 0xfc, 0x08, 0x0c, 0xa2, 0x52, 0x95, 0x67, 0xa2, 0x08, 0x49, + 0xdd, 0x60, 0x81, 0x89, 0xec, 0x7d, 0xbf, 0x85, 0x02, 0x0f, 0xbe, 0xf2, 0x11, 0xa3, 0x48, 0x14, 0x02, 0x02, 0x68, + 0x30, 0xd0, 0x02, 0xaa, 0x59, 0x3a, 0xa8, 0x86, 0x0f, 0xa1, 0xf3, 0x22, 0x9e, 0x98, 0x24, 0x19, 0xf4, 0x6f, 0xff, + 0x43, 0x81, 0x98, 0xf4, 0x01, 0x92, 0x2a, 0x13, 0x80, 0x1b, 0x16, 0x8f, 0xd3, 0x68, 0x2e, 0x5b, 0x91, 0x2b, 0x3d, + 0x8e, 0x7c, 0x6e, 0x8b, 0x7a, 0xc1, 0xca, 0x4b, 0x48, 0x69, 0xc7, 0xf0, 0xb2, 0xd7, 0xa5, 0xc2, 0xcf, 0x5e, 0xf3, + 0x0b, 0x26, 0x17, 0xc6, 0x21, 0x29, 0x17, 0xca, 0x10, 0xd6, 0x03, 0x00, 0x99, 0x77, 0x03, 0xd5, 0x9b, 0x84, 0x87, + 0xad, 0xb2, 0x39, 0x14, 0x0c, 0xc1, 0xc1, 0xbd, 0xf3, 0x39, 0x21, 0x89, 0x79, 0x0c, 0x43, 0x00, 0x27, 0x71, 0x4a, + 0x68, 0x33, 0x97, 0x71, 0xa6, 0x4e, 0x4f, 0xb2, 0xeb, 0x40, 0xdc, 0xda, 0x12, 0x42, 0xb1, 0x97, 0x75, 0x62, 0xc8, + 0x92, 0xaa, 0xc7, 0xa4, 0xdc, 0x8c, 0x60, 0xd7, 0xfe, 0x14, 0x4f, 0x75, 0x18, 0x8a, 0x9b, 0x19, 0x18, 0x89, 0x99, + 0x90, 0x9c, 0x0d, 0x92, 0xe4, 0x99, 0x74, 0x59, 0x5b, 0x93, 0xba, 0xce, 0xdf, 0x32, 0x84, 0x47, 0x24, 0xe3, 0xfc, + 0x2a, 0x0f, 0x75, 0xc7, 0x95, 0x4d, 0xaa, 0x2c, 0x4f, 0x4f, 0xbe, 0xeb, 0x5e, 0xd7, 0x98, 0x1a, 0xde, 0x03, 0x6a, + 0x64, 0x71, 0xe8, 0x36, 0xe7, 0x63, 0x67, 0x82, 0x9f, 0xbb, 0x3c, 0x50, 0x17, 0x0f, 0x3b, 0x92, 0xd0, 0xcf, 0x37, + 0x78, 0x5d, 0x68, 0x74, 0xc6, 0x80, 0x9c, 0x24, 0xe7, 0xe2, 0x52, 0x0b, 0xd4, 0x58, 0xf0, 0xd5, 0x8e, 0xa4, 0x6e, + 0x23, 0x0f, 0x7c, 0x23, 0x2e, 0x84, 0x2e, 0x72, 0xdb, 0x74, 0x8d, 0xfc, 0x9c, 0xde, 0xad, 0x5a, 0xe0, 0x49, 0x7e, + 0xfd, 0x7b, 0x55, 0x7a, 0x42, 0xaf, 0x2a, 0x71, 0x16, 0x9f, 0xb8, 0x44, 0x37, 0xd3, 0x3c, 0x82, 0x93, 0xba, 0x6a, + 0xca, 0x00, 0xbd, 0x2e, 0xbc, 0x1d, 0x68, 0x12, 0x09, 0xbc, 0x30, 0xdd, 0x25, 0xae, 0xa4, 0x03, 0xe1, 0x41, 0xb1, + 0x87, 0x09, 0x26, 0x42, 0xa3, 0xcc, 0x86, 0x03, 0xc0, 0xcf, 0x21, 0xde, 0x8d, 0xf9, 0xab, 0x61, 0x59, 0x55, 0x0b, + 0x82, 0x3b, 0x65, 0x01, 0xd9, 0xcb, 0xc8, 0x80, 0x02, 0xea, 0x84, 0x2c, 0x28, 0x6d, 0xd4, 0xd8, 0x21, 0x67, 0x83, + 0x15, 0xaa, 0xe6, 0x01, 0xc7, 0x26, 0xdd, 0xda, 0xa7, 0x16, 0x62, 0x44, 0x83, 0x6a, 0x72, 0xfe, 0x3a, 0x40, 0x42, + 0xf9, 0x0c, 0x29, 0x70, 0xa4, 0x5f, 0x32, 0xff, 0x06, 0x4c, 0x7a, 0xa7, 0x20, 0xe8, 0x97, 0x21, 0xe3, 0x7e, 0xa9, + 0x23, 0x50, 0x5a, 0xc6, 0xf6, 0x0f, 0xc5, 0xf1, 0x0d, 0x67, 0x4c, 0xcf, 0xc9, 0xd7, 0x36, 0x9a, 0x3f, 0xaf, 0xd4, + 0x22, 0xc4, 0x4b, 0x42, 0x2a, 0x0c, 0x00, 0xbf, 0xb7, 0x92, 0xce, 0x63, 0xf0, 0xee, 0x01, 0xc7, 0x59, 0xad, 0x09, + 0xa5, 0x67, 0x40, 0xbe, 0xc5, 0xbf, 0x31, 0x4d, 0x07, 0x05, 0x70, 0x6a, 0x45, 0xde, 0xbb, 0xbb, 0xbb, 0x75, 0x28, + 0x18, 0xfa, 0x3a, 0x4c, 0x59, 0xbf, 0xe0, 0x28, 0x1b, 0xc8, 0x6d, 0xbb, 0xdd, 0x6d, 0x55, 0xd2, 0xce, 0x24, 0xc3, + 0x23, 0x89, 0x41, 0x2a, 0x8d, 0xfc, 0xac, 0x2b, 0xab, 0xcb, 0x2c, 0x8e, 0x14, 0x17, 0x80, 0xee, 0x78, 0x86, 0xcd, + 0x1b, 0x5b, 0xf5, 0x81, 0x77, 0x20, 0xcd, 0x75, 0x02, 0x00, 0xbc, 0xf0, 0x54, 0x31, 0xe1, 0x8e, 0xb9, 0xca, 0x4e, + 0xa2, 0x9e, 0x4c, 0x34, 0x07, 0xe7, 0xf9, 0xa8, 0x42, 0x3e, 0xe9, 0x0e, 0x2b, 0x3e, 0x2f, 0x02, 0xe2, 0x71, 0x9c, + 0x54, 0x06, 0x43, 0xa2, 0xe0, 0xa7, 0x22, 0xec, 0x78, 0xba, 0x70, 0x9e, 0xdc, 0x55, 0xe9, 0xce, 0x01, 0xaa, 0x21, + 0x01, 0xab, 0x82, 0x6d, 0xd8, 0xbc, 0xcc, 0x49, 0x5c, 0xb6, 0x33, 0x86, 0x64, 0x1d, 0x0e, 0x6a, 0xe1, 0x63, 0xaf, + 0xf4, 0x43, 0x52, 0x28, 0xc4, 0xb9, 0x08, 0xe7, 0x59, 0x48, 0x9e, 0x0e, 0x90, 0x19, 0x79, 0x39, 0x79, 0xaf, 0xdd, + 0xd9, 0xae, 0x5b, 0x82, 0x48, 0xb7, 0x78, 0x6b, 0xac, 0xa7, 0xe3, 0x95, 0x4d, 0xc7, 0x54, 0x05, 0x92, 0x4d, 0xa4, + 0x82, 0x2a, 0xa5, 0xc1, 0xca, 0xd3, 0x01, 0x50, 0x30, 0x37, 0xfc, 0xad, 0x71, 0x4f, 0xcb, 0x84, 0x61, 0x73, 0x34, + 0xd8, 0x24, 0x0e, 0xee, 0x07, 0x83, 0x4e, 0x21, 0x6e, 0xd4, 0x2e, 0x70, 0x0d, 0x36, 0xd1, 0xcc, 0xc4, 0x1e, 0xff, + 0x5e, 0x7e, 0x10, 0x59, 0x65, 0x57, 0x25, 0xcd, 0x44, 0xa2, 0x5c, 0xba, 0x08, 0xc9, 0x5e, 0xfd, 0x3b, 0xfd, 0x56, + 0xe8, 0x74, 0xa0, 0x00, 0x74, 0x1c, 0x29, 0x24, 0xc4, 0x4c, 0x93, 0xee, 0x89, 0xe7, 0xf7, 0x5f, 0x7f, 0xfd, 0xdd, + 0xd6, 0xd6, 0x7c, 0x15, 0xbc, 0xf3, 0x79, 0xd1, 0x84, 0xed, 0xce, 0x52, 0xea, 0xfa, 0x1d, 0x58, 0x00, 0x3b, 0xdb, + 0x78, 0x26, 0x86, 0xb7, 0x4d, 0xf4, 0xa0, 0x0b, 0xf2, 0xc2, 0xf1, 0xa3, 0xf6, 0x87, 0x8f, 0xb8, 0x55, 0x16, 0xe8, + 0xbd, 0xba, 0x33, 0x8b, 0x40, 0xcc, 0x00, 0x2a, 0x20, 0xaf, 0xa0, 0xab, 0x18, 0x82, 0xe0, 0x97, 0x06, 0x49, 0x87, + 0x13, 0xce, 0x04, 0x7c, 0x36, 0x08, 0xba, 0xf3, 0xb7, 0xc3, 0x8e, 0xee, 0x44, 0xbc, 0x77, 0xe8, 0xe2, 0xd7, 0x76, + 0xea, 0x90, 0x29, 0x4f, 0x2f, 0x8d, 0xae, 0xec, 0x42, 0x73, 0xbd, 0xd3, 0xa7, 0x12, 0xe2, 0x63, 0x36, 0x46, 0x2e, + 0x28, 0x5e, 0xc1, 0x40, 0xf3, 0x60, 0x93, 0x7c, 0xb1, 0xf5, 0x3a, 0xb8, 0x1f, 0x37, 0x8f, 0x14, 0xfb, 0x05, 0xd5, + 0x0f, 0xb8, 0x61, 0x17, 0x52, 0xcb, 0xc7, 0x05, 0xc6, 0xca, 0x38, 0x28, 0x7f, 0x4e, 0xbb, 0xd3, 0x0b, 0xbf, 0x58, + 0x14, 0x15, 0x53, 0x12, 0xbf, 0x4d, 0xc2, 0xa4, 0x71, 0xaf, 0x5b, 0xd3, 0xe3, 0xf4, 0xbc, 0x20, 0x9c, 0x3b, 0xb9, + 0x7b, 0xfe, 0x4b, 0xb4, 0x3e, 0x9b, 0xe3, 0x76, 0xd7, 0xad, 0xe9, 0x77, 0x83, 0xa5, 0x4a, 0x93, 0x6e, 0x69, 0xec, + 0x5c, 0xbd, 0x09, 0x97, 0x75, 0x11, 0x89, 0xee, 0xcf, 0x7a, 0x2c, 0xa8, 0xd4, 0x33, 0x33, 0x7f, 0x0a, 0xa2, 0x86, + 0xb8, 0xde, 0xea, 0xe2, 0xbd, 0x5e, 0x7c, 0x4b, 0x8e, 0xbd, 0x74, 0x95, 0x64, 0x30, 0xa8, 0x0e, 0x5d, 0x0d, 0x8b, + 0xe4, 0x88, 0xa8, 0x5f, 0x31, 0x09, 0x98, 0xf5, 0x9c, 0x8f, 0xae, 0xd7, 0xa2, 0x79, 0xfa, 0xd6, 0x13, 0xa1, 0x7f, + 0xae, 0xc3, 0xed, 0xfb, 0x04, 0xae, 0xb6, 0xad, 0x63, 0x19, 0x8d, 0xe2, 0xcb, 0x46, 0x22, 0x66, 0x61, 0x47, 0xa2, + 0x4f, 0xff, 0x80, 0x48, 0xa2, 0xfc, 0xa4, 0xa5, 0x07, 0x49, 0x25, 0xdb, 0x6f, 0xf8, 0x70, 0x1f, 0xb5, 0x10, 0x68, + 0x6f, 0xff, 0x28, 0x52, 0x35, 0xbd, 0x4c, 0x24, 0xb1, 0xea, 0x40, 0xbd, 0xa6, 0xd4, 0xe7, 0x3e, 0xff, 0x7c, 0xfb, + 0x1d, 0x25, 0x64, 0x9a, 0x28, 0x59, 0xce, 0x18, 0x40, 0xae, 0xb1, 0xbb, 0x92, 0x90, 0x35, 0xbc, 0x4c, 0x4a, 0xef, + 0xc3, 0xcf, 0x67, 0xeb, 0xd0, 0x77, 0xff, 0x94, 0xc6, 0x65, 0xc1, 0x39, 0xf3, 0x66, 0xfe, 0x98, 0x9e, 0x06, 0x82, + 0x35, 0xaf, 0xb1, 0x77, 0x97, 0xeb, 0xcb, 0xd2, 0xe9, 0xa2, 0x5d, 0x3a, 0x5d, 0xb4, 0xda, 0x1b, 0xe6, 0xdf, 0xac, + 0x63, 0x0e, 0xbc, 0x5a, 0xb6, 0x0d, 0xa6, 0x12, 0x3c, 0xb5, 0xe5, 0x3f, 0x3a, 0x03, 0x57, 0x1e, 0x90, 0x1a, 0x6d, + 0xa0, 0x4f, 0x65, 0x10, 0x74, 0x73, 0xc3, 0x82, 0xa6, 0xab, 0x32, 0x23, 0xcd, 0x7c, 0x24, 0x5d, 0xf0, 0x39, 0x8d, + 0x39, 0xd8, 0xa7, 0xa9, 0xf2, 0x32, 0x14, 0x33, 0x7e, 0x56, 0xda, 0x82, 0xa6, 0x43, 0xe1, 0x47, 0x5c, 0xa6, 0x06, + 0xf4, 0xa2, 0xf3, 0x6b, 0x98, 0xc7, 0x59, 0xfa, 0x9b, 0xa7, 0x18, 0xe3, 0xf3, 0x86, 0x4c, 0xe1, 0xb8, 0xeb, 0x5e, + 0xa2, 0x80, 0x9f, 0x13, 0x1a, 0xcb, 0xf8, 0xbd, 0x18, 0xb4, 0x2f, 0xd2, 0x6d, 0x2e, 0x02, 0xa7, 0x02, 0xe4, 0x0f, + 0x09, 0xe3, 0x40, 0xd1, 0xd1, 0x5e, 0x63, 0xdf, 0x29, 0x55, 0xa6, 0xcf, 0x3d, 0xad, 0xd1, 0x13, 0xa5, 0x4c, 0x3f, + 0x19, 0x63, 0xcc, 0xba, 0xc8, 0xb1, 0xa5, 0xf9, 0xc0, 0x20, 0x93, 0x7c, 0xe1, 0x22, 0xa7, 0xc7, 0x9c, 0x3a, 0x0b, + 0x74, 0xab, 0x50, 0x6b, 0x0f, 0x96, 0x3f, 0xa0, 0x72, 0x60, 0xa8, 0x28, 0xfb, 0x71, 0x8a, 0x2d, 0xe2, 0x43, 0xfb, + 0x0d, 0xb7, 0x90, 0xb8, 0xed, 0x45, 0x26, 0x82, 0x54, 0x83, 0xa2, 0x58, 0xdb, 0x24, 0x23, 0xb9, 0xa1, 0x8a, 0xc1, + 0x46, 0x2d, 0x9f, 0x3e, 0x83, 0xd3, 0xe5, 0xd3, 0xd3, 0x9c, 0x5a, 0xb4, 0x25, 0x33, 0xf5, 0x8c, 0xc4, 0xd2, 0x15, + 0x76, 0xf1, 0xf2, 0x6b, 0xbc, 0xa1, 0x7d, 0xcc, 0x40, 0x22, 0x29, 0xbd, 0x6a, 0x1a, 0xc4, 0x0c, 0x36, 0x90, 0x46, + 0x75, 0x20, 0xf2, 0x12, 0x5f, 0x4e, 0x40, 0x00, 0x46, 0x0f, 0x3f, 0x7a, 0x43, 0xe9, 0xb4, 0xd9, 0xed, 0x76, 0x56, + 0x26, 0x50, 0x74, 0xcd, 0x27, 0x63, 0x92, 0x37, 0x84, 0x9d, 0xc5, 0x2d, 0x79, 0x2a, 0x84, 0x31, 0x78, 0x79, 0x66, + 0x6c, 0x31, 0x7f, 0x7e, 0x3d, 0xd6, 0x2f, 0x0c, 0x25, 0x51, 0x52, 0xc8, 0xbe, 0xd4, 0x2d, 0x33, 0x1c, 0x59, 0x9c, + 0xe6, 0xe4, 0xd2, 0x83, 0x33, 0xf5, 0x18, 0x78, 0x0e, 0x04, 0xf2, 0xfa, 0x0e, 0xfb, 0xed, 0xc0, 0x09, 0x47, 0xfc, + 0x14, 0xf3, 0xf1, 0x4f, 0xd5, 0x42, 0xf6, 0xcc, 0xea, 0xbc, 0x53, 0x16, 0xbb, 0x2a, 0x54, 0x51, 0x67, 0x0a, 0x2a, + 0xf8, 0xad, 0x43, 0x04, 0x6d, 0xfb, 0x49, 0x92, 0x21, 0x12, 0x55, 0x81, 0xfa, 0x6c, 0x26, 0x92, 0x60, 0x2e, 0xc0, + 0x92, 0xe5, 0x0d, 0xe7, 0xbc, 0xf6, 0xb7, 0xae, 0x49, 0xe6, 0x0d, 0x70, 0xd1, 0x7c, 0xda, 0xe9, 0xe9, 0x3a, 0xf2, + 0xad, 0x87, 0xa9, 0x75, 0xa8, 0x05, 0xb3, 0x84, 0x8b, 0x59, 0xb9, 0x89, 0x7d, 0x79, 0x1b, 0xa8, 0x99, 0x1c, 0x84, + 0xca, 0x9f, 0x98, 0x9c, 0x52, 0x1b, 0xa9, 0x90, 0xb5, 0x87, 0xcc, 0x49, 0x07, 0x21, 0xdc, 0x86, 0xf4, 0xdb, 0xf9, + 0x65, 0x87, 0x4c, 0xf6, 0xa3, 0x2d, 0x0c, 0xe9, 0xff, 0x7a, 0x85, 0x49, 0x68, 0x30, 0x42, 0xb8, 0x9f, 0x04, 0x08, + 0xf7, 0xa2, 0x53, 0x16, 0xe1, 0xc2, 0x9d, 0x47, 0x61, 0xbf, 0x77, 0x36, 0x54, 0x86, 0x05, 0xe7, 0x07, 0xcd, 0xcf, + 0x71, 0x10, 0x8e, 0xf5, 0x9a, 0x3c, 0x30, 0x7e, 0xfc, 0x91, 0xbd, 0x40, 0xc0, 0x7c, 0x37, 0x11, 0xb4, 0xea, 0x14, + 0x28, 0x0b, 0xd6, 0x38, 0x18, 0x48, 0x0a, 0x16, 0xfb, 0x46, 0xaa, 0x7a, 0x9b, 0xb2, 0x2d, 0x9f, 0xe5, 0x49, 0xc7, + 0xe9, 0x5f, 0xd6, 0x7a, 0x9b, 0x10, 0x62, 0x5f, 0xf4, 0xb9, 0xf2, 0x01, 0x4a, 0xb4, 0xda, 0xe7, 0xff, 0x25, 0xb8, + 0xf5, 0xf5, 0xdf, 0x79, 0x35, 0xd3, 0x46, 0x8a, 0x59, 0x14, 0x5e, 0x7b, 0xa9, 0xac, 0x19, 0x9f, 0x90, 0xad, 0x66, + 0x4d, 0x36, 0x4e, 0x05, 0xc5, 0x4d, 0x5d, 0x0b, 0xb6, 0x98, 0x96, 0x6c, 0xde, 0x16, 0x93, 0x78, 0x63, 0x7e, 0x49, + 0xcb, 0xb1, 0x39, 0x17, 0xda, 0x8a, 0x41, 0xa3, 0x8e, 0x87, 0x8d, 0x9e, 0x13, 0x9d, 0x32, 0x5d, 0xaf, 0x1c, 0x37, + 0x55, 0xb5, 0xbf, 0x04, 0x0e, 0x9d, 0xda, 0x0a, 0x91, 0x56, 0xcb, 0x51, 0x43, 0x9e, 0x61, 0x59, 0x2b, 0x03, 0xe1, + 0x3a, 0x90, 0x76, 0xe7, 0xaf, 0xb3, 0xe4, 0x59, 0x70, 0xcb, 0x12, 0x8f, 0xf0, 0xa5, 0x66, 0x72, 0x8b, 0xe4, 0x15, + 0x93, 0x28, 0x0f, 0xe5, 0xc1, 0xee, 0xfc, 0x7c, 0xa2, 0xbd, 0x92, 0x2c, 0xe7, 0x33, 0xcd, 0x0b, 0x10, 0x42, 0xa6, + 0x6b, 0x09, 0x6d, 0xd9, 0x0b, 0xf6, 0xc4, 0xb8, 0x7a, 0x4a, 0x92, 0xf9, 0x25, 0x38, 0xf8, 0xeb, 0x7e, 0x9b, 0xa5, + 0x35, 0xf8, 0xdb, 0xbb, 0xc5, 0x4c, 0x6c, 0x2f, 0xb4, 0x19, 0xa9, 0x90, 0x7d, 0xff, 0xd7, 0x81, 0x78, 0x13, 0x98, + 0x1f, 0xea, 0xa8, 0x71, 0x74, 0x4f, 0x35, 0xfe, 0x2f, 0x1c, 0x36, 0x5a, 0x7a, 0xed, 0x68, 0xae, 0x91, 0x80, 0xc9, + 0x91, 0x7b, 0x55, 0xdf, 0x8b, 0x82, 0xbd, 0xe1, 0x81, 0x40, 0x59, 0xcd, 0xfe, 0x7e, 0xfd, 0x20, 0x00, 0x70, 0xa5, + 0x67, 0x1d, 0xaf, 0xe5, 0x67, 0xdb, 0x6d, 0x6c, 0xc0, 0xe5, 0x5a, 0xc1, 0x7f, 0x15, 0x47, 0xe8, 0xaf, 0xcd, 0x24, + 0x87, 0xed, 0xba, 0x1e, 0x0a, 0x3a, 0x64, 0xcc, 0x29, 0x06, 0x71, 0x3d, 0xf9, 0x92, 0xf5, 0x3a, 0x30, 0x6f, 0x82, + 0xda, 0xfc, 0x62, 0xef, 0xc5, 0x5e, 0x65, 0xd2, 0xa0, 0x29, 0x82, 0xff, 0x02, 0xf5, 0x01, 0xfe, 0xf4, 0x82, 0xb0, + 0x28, 0x7e, 0x50, 0x1c, 0xce, 0xb0, 0xc0, 0x66, 0x56, 0x1a, 0x5a, 0x07, 0xc6, 0x8f, 0x19, 0x3d, 0xf5, 0x09, 0xc6, + 0xa1, 0xc8, 0xd9, 0x39, 0x38, 0xc9, 0x51, 0xaa, 0x95, 0xfb, 0xfb, 0x4d, 0x9e, 0x84, 0x49, 0x4b, 0x3b, 0xf7, 0x27, + 0x68, 0x1f, 0xab, 0x3f, 0xff, 0xc7, 0xb1, 0x9b, 0x31, 0x2c, 0xa3, 0x76, 0x13, 0xbf, 0x3f, 0x81, 0x1b, 0x35, 0x4f, + 0xa9, 0xdb, 0xbd, 0x33, 0x7f, 0xd7, 0xb7, 0xf6, 0xf8, 0x69, 0xa0, 0x34, 0x86, 0xb1, 0x00, 0xb1, 0x98, 0xc6, 0x4b, + 0x63, 0x79, 0x07, 0x33, 0x37, 0x6c, 0xa3, 0x6f, 0x66, 0x7c, 0xeb, 0xe7, 0x0c, 0x41, 0x03, 0x62, 0xd4, 0x74, 0x69, + 0x45, 0xa5, 0xdf, 0xa5, 0xb8, 0xf3, 0x26, 0x14, 0x68, 0x9e, 0xfb, 0x1c, 0x0a, 0xa7, 0xa3, 0x48, 0x25, 0x25, 0xc0, + 0x3a, 0x59, 0x7e, 0xd6, 0x2e, 0xe2, 0xfd, 0x85, 0xd0, 0x25, 0xbc, 0xae, 0x53, 0xc4, 0xaf, 0xc5, 0x70, 0x33, 0x4d, + 0x37, 0x1b, 0xa8, 0x2f, 0xcb, 0x2e, 0x9d, 0x83, 0x23, 0xf8, 0x6a, 0x8d, 0x54, 0x44, 0xce, 0x50, 0x5f, 0x24, 0xd6, + 0x70, 0xe8, 0x23, 0x0e, 0xd6, 0xa5, 0xea, 0x80, 0x26, 0xdf, 0xac, 0x76, 0xd9, 0x69, 0xa3, 0x39, 0x4d, 0x4d, 0x31, + 0x83, 0x18, 0x0e, 0x3e, 0x89, 0xd0, 0xd9, 0xb4, 0x8f, 0x9b, 0xac, 0x89, 0x33, 0x34, 0x0d, 0xd7, 0x31, 0x5a, 0x55, + 0xc2, 0xac, 0xb2, 0x8d, 0xc7, 0x53, 0xda, 0x55, 0xeb, 0x9e, 0x08, 0x3b, 0xe7, 0xd2, 0x51, 0xab, 0x09, 0xda, 0x26, + 0x22, 0x85, 0xe2, 0xb0, 0xd5, 0x84, 0xbe, 0x3b, 0xac, 0x58, 0x61, 0xc5, 0xdb, 0x25, 0xf5, 0x3a, 0x66, 0x1c, 0xae, + 0x84, 0xc5, 0x1c, 0x60, 0xe0, 0x97, 0xb1, 0xf2, 0x81, 0x9a, 0xe4, 0x54, 0x7a, 0x48, 0x79, 0x97, 0x52, 0x9d, 0xcc, + 0x63, 0xff, 0xe2, 0xee, 0xf5, 0xa5, 0xf9, 0xe2, 0x6e, 0x32, 0xde, 0x42, 0x98, 0x3a, 0x6d, 0xa0, 0x2e, 0x6b, 0x3b, + 0x22, 0x74, 0xb9, 0x4f, 0xb4, 0x18, 0xef, 0x29, 0x74, 0x97, 0x93, 0xce, 0x09, 0xd5, 0x7f, 0x0a, 0x44, 0xf9, 0x88, + 0x32, 0xc8, 0xdd, 0x9d, 0x8a, 0x5d, 0xc9, 0xd3, 0x9d, 0x24, 0x3e, 0x56, 0xdf, 0x30, 0x32, 0x68, 0x6d, 0x9d, 0xa8, + 0xf6, 0x9d, 0xf5, 0x3e, 0x41, 0x8c, 0x75, 0x4b, 0x2c, 0xcb, 0xb7, 0xcb, 0x1d, 0xd2, 0x80, 0x38, 0xb7, 0x97, 0x61, + 0x5d, 0xce, 0xd1, 0x08, 0xf3, 0x65, 0x2c, 0x25, 0x24, 0x20, 0x92, 0x3e, 0x4e, 0x48, 0x97, 0xe2, 0xef, 0xba, 0xc3, + 0x65, 0x79, 0x12, 0xc2, 0x7c, 0xf4, 0x32, 0x66, 0x52, 0x97, 0xe0, 0x6b, 0xbc, 0xc9, 0x2f, 0x09, 0xb8, 0x24, 0x9a, + 0x5e, 0x5f, 0x71, 0xaa, 0x4b, 0xd5, 0xdb, 0x16, 0x14, 0xa7, 0xe9, 0x57, 0x2d, 0xc9, 0x2d, 0xf1, 0x99, 0xb1, 0x60, + 0x10, 0xa8, 0x43, 0x45, 0x2f, 0x03, 0x15, 0x63, 0x2c, 0x22, 0x4e, 0x97, 0x5f, 0x32, 0xa9, 0xae, 0x74, 0xa8, 0xda, + 0xb3, 0xf7, 0x17, 0x4f, 0x76, 0x78, 0x34, 0x7d, 0xfa, 0xe3, 0xeb, 0x41, 0x0f, 0xaa, 0xa0, 0x53, 0xb8, 0xdd, 0xd9, + 0xc0, 0x50, 0x28, 0x40, 0x56, 0x76, 0x2e, 0xcb, 0x00, 0xea, 0x4c, 0x4d, 0x49, 0x77, 0x7d, 0xdd, 0x9b, 0x44, 0x7a, + 0xd9, 0x30, 0xe3, 0xe7, 0xd0, 0x92, 0x9f, 0x4d, 0xf3, 0xcb, 0x1d, 0x6d, 0x63, 0x39, 0xe2, 0x29, 0xb0, 0xb1, 0x30, + 0x78, 0x0f, 0x29, 0x6e, 0xc2, 0x20, 0x43, 0x0e, 0x92, 0xbc, 0xd2, 0x96, 0xe5, 0xb9, 0x96, 0x92, 0x0d, 0x33, 0x7e, + 0x4f, 0x8a, 0x02, 0xe5, 0x77, 0x89, 0xb7, 0x71, 0x43, 0x00, 0x4e, 0x50, 0xda, 0x1c, 0x39, 0x56, 0x71, 0x2b, 0xbf, + 0x31, 0x78, 0x11, 0x81, 0x9e, 0x29, 0x1c, 0xe3, 0xf9, 0xc3, 0x7e, 0x1c, 0x21, 0x48, 0x05, 0x3f, 0xac, 0xd4, 0x66, + 0x47, 0x2f, 0xfd, 0xd7, 0xac, 0xa6, 0x47, 0x46, 0xba, 0xdb, 0x24, 0x6a, 0xcb, 0x4e, 0x54, 0x80, 0x19, 0x44, 0x63, + 0xe0, 0x82, 0x3b, 0xc6, 0x34, 0x1f, 0xfe, 0xdb, 0x4f, 0xac, 0x3d, 0xcc, 0xdf, 0xce, 0x78, 0xe5, 0xc9, 0x4b, 0x64, + 0x81, 0x9a, 0x8f, 0x5d, 0x5f, 0x5e, 0xc6, 0x77, 0xeb, 0x3e, 0x9e, 0xba, 0x05, 0x59, 0x40, 0x80, 0x81, 0xf8, 0x99, + 0x33, 0xd1, 0x1b, 0xc2, 0x9d, 0xd4, 0xc4, 0xd3, 0x5a, 0xcd, 0x6f, 0xf2, 0xf6, 0xda, 0x45, 0x0d, 0xc9, 0x5b, 0x67, + 0xed, 0x66, 0x55, 0x7a, 0x6c, 0x4d, 0xf2, 0xfd, 0x9a, 0x49, 0x96, 0xfa, 0x5f, 0xc3, 0xad, 0xb1, 0x7d, 0xbb, 0x0a, + 0xcb, 0x3a, 0xcc, 0x5f, 0x5e, 0x5f, 0x72, 0x1c, 0xe7, 0xbc, 0xf8, 0x8d, 0x35, 0xb6, 0xf0, 0xe6, 0x64, 0x4b, 0xc2, + 0x65, 0x6a, 0x35, 0xf7, 0xac, 0x56, 0xb5, 0x67, 0x49, 0xc8, 0xcd, 0x5e, 0xf6, 0x00, 0x9d, 0xbf, 0x37, 0xe9, 0x73, + 0xfc, 0x5e, 0x67, 0xcd, 0xe9, 0x7b, 0x83, 0x34, 0xd7, 0x9f, 0x22, 0xb2, 0x78, 0x66, 0x9d, 0x3c, 0xaa, 0xec, 0x15, + 0x93, 0x69, 0x3e, 0x26, 0xe4, 0x0a, 0x61, 0x58, 0x55, 0xbb, 0x3e, 0x3d, 0xa1, 0xe2, 0x86, 0x03, 0xc8, 0x6d, 0x7c, + 0x3e, 0xc8, 0x0d, 0xfe, 0x5e, 0xd9, 0x59, 0x0e, 0x3a, 0x0b, 0x6d, 0x7e, 0xec, 0xa1, 0xee, 0xc7, 0xe1, 0x61, 0x08, + 0xae, 0xcc, 0xde, 0x9c, 0xaa, 0x5f, 0x03, 0xd2, 0xea, 0x9c, 0xdb, 0xae, 0x20, 0x17, 0x7b, 0xfd, 0x4a, 0xb9, 0x37, + 0x0a, 0x44, 0x63, 0x43, 0x49, 0xea, 0x2c, 0xf2, 0xdd, 0x90, 0x3a, 0xb9, 0xdb, 0xce, 0xe8, 0x68, 0x7d, 0xe2, 0x23, + 0xfe, 0x54, 0x0d, 0x55, 0x98, 0xaf, 0xe7, 0x36, 0xcb, 0x7a, 0x80, 0xc6, 0x11, 0x69, 0x56, 0xd7, 0x9b, 0x92, 0x5e, + 0xad, 0x88, 0x4c, 0x68, 0x0c, 0xbe, 0xc9, 0xe0, 0x20, 0x1f, 0x57, 0x42, 0x2f, 0x92, 0x6e, 0xca, 0x27, 0xfb, 0x5f, + 0x45, 0x7b, 0x31, 0x07, 0x10, 0xfb, 0x16, 0x5d, 0x60, 0xb6, 0x56, 0x60, 0x8f, 0xd0, 0x1c, 0x2f, 0x11, 0xbd, 0xac, + 0x2c, 0x54, 0x5c, 0x58, 0x13, 0xd6, 0x7a, 0x9f, 0xb5, 0xc2, 0x69, 0xee, 0xfc, 0x53, 0x5d, 0x84, 0x50, 0xe2, 0x53, + 0x99, 0xd5, 0x80, 0x62, 0xa8, 0x81, 0x64, 0x3f, 0x39, 0xbf, 0xf2, 0x59, 0x64, 0x06, 0xe4, 0x6b, 0xb7, 0xe3, 0x83, + 0x3b, 0x1e, 0x8c, 0x3b, 0xbe, 0x6d, 0x3f, 0xb5, 0xd6, 0x9b, 0x49, 0x56, 0x4d, 0x33, 0x73, 0xde, 0x05, 0x86, 0x1d, + 0x0e, 0x2e, 0xcf, 0xce, 0xe7, 0x2e, 0x68, 0xda, 0x13, 0x96, 0xa9, 0x45, 0x73, 0x1b, 0xf0, 0xe4, 0x23, 0x7a, 0x4a, + 0x23, 0x39, 0xbb, 0xc3, 0x7b, 0x20, 0x77, 0x28, 0x7d, 0x6a, 0x25, 0x5f, 0xb0, 0x62, 0xc1, 0x79, 0xb3, 0x20, 0x16, + 0xcb, 0xa6, 0xea, 0x25, 0x48, 0x3a, 0xc4, 0xf9, 0x6c, 0x70, 0x9d, 0x4a, 0xa1, 0x1b, 0xfc, 0x7f, 0x89, 0x91, 0x1c, + 0xb6, 0xa2, 0x20, 0x98, 0x3a, 0x27, 0x41, 0x25, 0x62, 0xff, 0x46, 0x86, 0x0e, 0xfe, 0x0c, 0xaa, 0x94, 0x7d, 0x44, + 0x97, 0x3e, 0xbb, 0x37, 0xc1, 0x89, 0xd8, 0xee, 0x19, 0xe9, 0x7c, 0x48, 0x68, 0x7f, 0x7e, 0xfe, 0xcd, 0x65, 0x1f, + 0x19, 0x62, 0xbe, 0xae, 0xdd, 0x9b, 0xf7, 0x20, 0xd5, 0xb3, 0x3f, 0x47, 0x2c, 0x86, 0xb3, 0xcc, 0x84, 0xe7, 0x51, + 0x4c, 0xaf, 0xdd, 0x37, 0x78, 0x12, 0x48, 0x18, 0x43, 0xd0, 0x3e, 0x5c, 0xe1, 0x9b, 0xaf, 0x22, 0x26, 0x6b, 0x48, + 0xf8, 0xf8, 0xac, 0xf8, 0xad, 0xb3, 0x17, 0xb5, 0xb8, 0x61, 0x68, 0xa6, 0x8e, 0xd3, 0x3c, 0x6f, 0xc1, 0x7d, 0x9e, + 0xda, 0x73, 0xa2, 0xd2, 0x68, 0x9d, 0xe7, 0xeb, 0x37, 0x61, 0x56, 0x2d, 0xdd, 0xe6, 0xef, 0xc2, 0xd8, 0x56, 0xa8, + 0x22, 0xff, 0xbc, 0x8b, 0xb0, 0x1f, 0x71, 0x1a, 0x68, 0x24, 0xbf, 0x6a, 0x4b, 0xbe, 0xf2, 0x4e, 0xc2, 0x2c, 0xcc, + 0x4d, 0xac, 0x8b, 0x8d, 0x32, 0x3f, 0x8b, 0xc9, 0xcf, 0x54, 0xe4, 0x63, 0xe3, 0x8a, 0xae, 0xf6, 0x09, 0xf1, 0xf0, + 0x68, 0x7d, 0x18, 0x77, 0xcb, 0x62, 0x6d, 0x56, 0xe6, 0x8b, 0xb2, 0x2b, 0xb5, 0xcf, 0xd3, 0x17, 0xfc, 0x68, 0xb1, + 0x3e, 0xd8, 0xb9, 0x97, 0x08, 0xc8, 0xa0, 0x5a, 0x96, 0xb6, 0x43, 0xe4, 0xe1, 0xe5, 0x26, 0x2f, 0x79, 0x9b, 0x27, + 0x2a, 0x4a, 0xdb, 0x21, 0xf0, 0xdd, 0x7d, 0x9d, 0x1c, 0xd0, 0x25, 0xcc, 0xd3, 0x15, 0x40, 0x6b, 0xc0, 0x42, 0x6e, + 0x56, 0x27, 0xf6, 0x5c, 0x95, 0x6c, 0xda, 0xdb, 0x35, 0xf9, 0x73, 0x07, 0x94, 0x13, 0x6e, 0xec, 0xcb, 0xc8, 0xb0, + 0x5c, 0x95, 0x6e, 0x84, 0xcf, 0xfa, 0xc8, 0x9d, 0x67, 0x1f, 0xf0, 0xc3, 0x6f, 0xc9, 0x3d, 0xfa, 0xeb, 0x3c, 0x32, + 0x2d, 0xdf, 0x16, 0x34, 0x6a, 0x1c, 0xa2, 0xf1, 0x56, 0x12, 0x10, 0x15, 0x55, 0x03, 0x1e, 0x53, 0x7e, 0x16, 0x2c, + 0x8f, 0x64, 0x94, 0x1d, 0xf2, 0xa5, 0xb6, 0xfb, 0xb1, 0x35, 0xf1, 0xcf, 0xac, 0x43, 0xab, 0xac, 0x4f, 0x86, 0x2f, + 0xb5, 0xdd, 0xde, 0x7b, 0xa1, 0x80, 0x08, 0xb0, 0x87, 0xc1, 0xe7, 0xd8, 0x5a, 0x2d, 0xf8, 0xfc, 0xf8, 0xf9, 0x81, + 0x3b, 0x56, 0xcc, 0x79, 0xdf, 0xf5, 0x5d, 0x80, 0x92, 0xcc, 0x08, 0x03, 0x3b, 0x66, 0x37, 0xc6, 0x90, 0xc4, 0x49, + 0xa3, 0x71, 0x1f, 0xc4, 0x09, 0xbd, 0x35, 0xec, 0x00, 0x70, 0x89, 0x3c, 0x19, 0x2e, 0x53, 0x48, 0x97, 0xc8, 0x87, + 0xe9, 0x7b, 0x5c, 0x91, 0x45, 0x02, 0x7c, 0xc3, 0x6b, 0x25, 0xdb, 0x26, 0x58, 0x41, 0x8b, 0x62, 0x0e, 0x64, 0x3a, + 0x4b, 0x15, 0xdf, 0x30, 0xe2, 0x9c, 0x3f, 0x74, 0x9b, 0x37, 0x17, 0x33, 0x5e, 0x3f, 0x9f, 0xfa, 0xb4, 0x97, 0x09, + 0xed, 0x68, 0x4e, 0x33, 0x30, 0xa0, 0xe8, 0x57, 0xc5, 0xe6, 0x4f, 0xb0, 0x44, 0xc9, 0x3f, 0xda, 0xc8, 0xce, 0x9f, + 0x10, 0xfa, 0x88, 0x24, 0x60, 0xa2, 0xb1, 0xfd, 0x74, 0x4e, 0xd1, 0xdb, 0xaa, 0x86, 0xae, 0x08, 0x0b, 0xef, 0x83, + 0x1d, 0x5b, 0xb8, 0x36, 0x43, 0xd1, 0x38, 0xa2, 0x1e, 0x98, 0xf7, 0x65, 0xc7, 0x69, 0xbe, 0x6f, 0x6c, 0x10, 0xa4, + 0x3e, 0x6f, 0x45, 0x26, 0x07, 0x24, 0x25, 0x36, 0xb0, 0xf0, 0xb8, 0x79, 0xba, 0xac, 0x4b, 0xbe, 0x77, 0x59, 0x9d, + 0xba, 0xa2, 0xb1, 0x85, 0x12, 0x67, 0x2c, 0x1a, 0x8c, 0x29, 0x11, 0x49, 0xe6, 0x42, 0xb0, 0x46, 0xc3, 0xdf, 0x7c, + 0x22, 0x68, 0x3e, 0xe1, 0xb1, 0xf7, 0x09, 0x77, 0x32, 0x99, 0xde, 0x50, 0x13, 0x65, 0x3b, 0x7a, 0xf7, 0xf3, 0x81, + 0xd2, 0x4e, 0x73, 0x3e, 0x96, 0x31, 0x73, 0xc9, 0x02, 0x94, 0x99, 0x08, 0x11, 0x90, 0x43, 0x8f, 0x3b, 0xc9, 0x22, + 0x41, 0xaf, 0xf1, 0x15, 0x26, 0x52, 0xd3, 0x91, 0xd9, 0xeb, 0x6a, 0x22, 0x1a, 0x8f, 0x1c, 0x29, 0xf0, 0x62, 0xbc, + 0xc9, 0xa8, 0x4b, 0xb1, 0x5a, 0xbc, 0x61, 0x92, 0x29, 0x7e, 0xf2, 0xd7, 0xf6, 0x27, 0x27, 0xe4, 0xbd, 0x9e, 0x5a, + 0xa1, 0xdf, 0xf3, 0xc6, 0xd6, 0xa5, 0xa0, 0xdd, 0xff, 0x6c, 0xc9, 0x28, 0xda, 0x98, 0x56, 0xcf, 0xe2, 0x4b, 0xfd, + 0xa2, 0x97, 0xc8, 0xe5, 0x4d, 0x1e, 0xdb, 0x87, 0x11, 0xa3, 0xd0, 0x5a, 0x85, 0xd9, 0x7b, 0x8f, 0x62, 0x63, 0xef, + 0xb5, 0x42, 0x34, 0xce, 0x21, 0xba, 0x84, 0xcb, 0x8d, 0x97, 0xc8, 0x24, 0x3e, 0x39, 0xe3, 0x2c, 0xf3, 0x6f, 0xa9, + 0x48, 0x58, 0xce, 0x32, 0x8f, 0xd1, 0xc3, 0xde, 0x54, 0x25, 0x9b, 0x02, 0x4e, 0x51, 0xd6, 0x8a, 0xb8, 0x99, 0xce, + 0x77, 0xad, 0x40, 0x6b, 0xe2, 0x67, 0x30, 0x8a, 0xc9, 0x6a, 0xf2, 0xe6, 0xd5, 0x7f, 0x73, 0xe2, 0x5f, 0x54, 0x33, + 0xfe, 0x50, 0xc6, 0xf8, 0x97, 0x8b, 0x75, 0x58, 0xf9, 0x7d, 0x73, 0x28, 0x09, 0x70, 0x8d, 0x93, 0x4a, 0x7c, 0xad, + 0x70, 0x8e, 0x00, 0xfa, 0xae, 0xbb, 0x24, 0xd4, 0x0b, 0x8e, 0x06, 0x1d, 0x16, 0x23, 0x58, 0x1c, 0x13, 0x7d, 0x74, + 0xff, 0x77, 0xc5, 0x00, 0x2d, 0x46, 0x23, 0xd7, 0x5f, 0xcf, 0xc5, 0xb1, 0x22, 0xc9, 0x26, 0x57, 0x58, 0x88, 0x11, + 0x02, 0x08, 0xb9, 0x48, 0x02, 0x1d, 0xe7, 0x07, 0x9f, 0x8a, 0x17, 0x8d, 0x48, 0x01, 0x68, 0x48, 0xfb, 0x6b, 0x80, + 0xc0, 0x21, 0x98, 0x23, 0x41, 0x30, 0x92, 0x67, 0x01, 0x91, 0x13, 0xb2, 0x77, 0xa2, 0x42, 0x84, 0x59, 0x1d, 0xec, + 0x7a, 0x83, 0xba, 0x80, 0x2d, 0x9a, 0xe7, 0x48, 0x50, 0x54, 0x21, 0x22, 0x2c, 0x2b, 0x36, 0x57, 0xaf, 0xd6, 0xbc, + 0x47, 0x85, 0x17, 0x85, 0x2e, 0x99, 0x3e, 0xcd, 0x2e, 0xa1, 0xcc, 0x2f, 0xc0, 0xbf, 0x16, 0x75, 0x60, 0xcf, 0xbb, + 0x0e, 0x1d, 0x5b, 0x71, 0x72, 0x2a, 0x2e, 0x7f, 0xce, 0x39, 0x00, 0x4a, 0x7a, 0xd6, 0x21, 0x86, 0x06, 0x9d, 0xeb, + 0x96, 0x6b, 0xd2, 0x00, 0x0c, 0x97, 0x8c, 0x57, 0x86, 0xda, 0xd6, 0xb3, 0xeb, 0x17, 0x7f, 0x46, 0x66, 0x8e, 0x0e, + 0x4d, 0xbc, 0x88, 0x12, 0x77, 0x59, 0x5c, 0x02, 0x15, 0xaf, 0xf3, 0x51, 0xad, 0x6b, 0xe5, 0x95, 0xed, 0x1a, 0x87, + 0x0b, 0x1a, 0x82, 0x2d, 0xbc, 0x6a, 0xc0, 0x75, 0xb8, 0xac, 0x8b, 0xc0, 0x8f, 0x9e, 0x16, 0x05, 0xca, 0xdb, 0xb5, + 0x20, 0x0d, 0x3d, 0xd9, 0x89, 0xca, 0xa7, 0x69, 0xe9, 0xef, 0xcd, 0x7a, 0xf9, 0x8e, 0x16, 0x53, 0x8e, 0x43, 0x85, + 0x3f, 0x03, 0xc2, 0xa6, 0xb8, 0x1b, 0x14, 0x0d, 0xe5, 0xc5, 0x0d, 0x84, 0x72, 0x3a, 0x3b, 0x7c, 0xdb, 0x69, 0x56, + 0x11, 0xc4, 0xbc, 0x1f, 0xfd, 0xa7, 0x5c, 0x56, 0x60, 0xe9, 0x74, 0xec, 0x71, 0x93, 0x39, 0x47, 0x79, 0xde, 0xf6, + 0x8d, 0xd4, 0xa9, 0x45, 0x48, 0x55, 0xbc, 0x5a, 0xf4, 0x55, 0xba, 0xf7, 0x69, 0x83, 0x99, 0xb7, 0x59, 0xb1, 0x07, + 0xd9, 0x8a, 0x8d, 0x68, 0x96, 0xbc, 0xee, 0x31, 0x25, 0xd5, 0x47, 0x4c, 0x1c, 0xa0, 0x5b, 0xde, 0x2f, 0x1e, 0xc3, + 0x54, 0xaf, 0x30, 0x62, 0xb5, 0xd9, 0x5f, 0x00, 0x73, 0x6f, 0xdc, 0xcf, 0x35, 0x7b, 0xe6, 0x53, 0x29, 0xa4, 0x58, + 0x85, 0xf0, 0xba, 0x2a, 0x33, 0x38, 0xf9, 0x14, 0x82, 0x21, 0x7f, 0xf9, 0x31, 0xf3, 0xeb, 0x55, 0xf7, 0x87, 0x19, + 0xcf, 0xea, 0x7b, 0x3a, 0x60, 0x6f, 0xa8, 0x0d, 0xaf, 0x7b, 0x0a, 0x71, 0x45, 0x98, 0xdd, 0xb3, 0x53, 0x60, 0xcd, + 0x64, 0x70, 0xdf, 0xb1, 0x29, 0xea, 0x09, 0xfc, 0x28, 0x9c, 0x37, 0x0b, 0xe6, 0x6f, 0x79, 0xc5, 0x68, 0xde, 0x4c, + 0x91, 0x74, 0xe1, 0xc1, 0x88, 0x4f, 0x19, 0x97, 0x28, 0x5b, 0xfa, 0x90, 0x7e, 0x87, 0x78, 0x23, 0xc7, 0x9b, 0xb5, + 0xf4, 0x8d, 0xe2, 0xb0, 0xd5, 0x64, 0x1b, 0x12, 0xa6, 0x00, 0x68, 0x91, 0xf3, 0x11, 0x30, 0x5d, 0xaf, 0xd9, 0x8a, + 0xb2, 0x0d, 0x61, 0x91, 0x86, 0x86, 0x50, 0x34, 0xac, 0x17, 0x4c, 0x4d, 0x8a, 0xbb, 0x43, 0x23, 0x26, 0xc6, 0x73, + 0xc6, 0xf2, 0x0b, 0xf2, 0xd3, 0xa2, 0x4c, 0x5b, 0x63, 0x2f, 0xae, 0xcc, 0x0a, 0x26, 0x1e, 0x34, 0x13, 0x20, 0x09, + 0xe0, 0xd5, 0x2a, 0xea, 0x8c, 0xf3, 0x54, 0x62, 0x73, 0x7f, 0xe3, 0x09, 0x19, 0x20, 0xd0, 0x29, 0x69, 0xa2, 0xa3, + 0xab, 0xf5, 0x41, 0x8a, 0xbd, 0x00, 0x94, 0x9d, 0xb0, 0xc1, 0x32, 0x86, 0x06, 0x58, 0xd7, 0x66, 0x73, 0x8a, 0x6b, + 0xd9, 0x53, 0x27, 0xb3, 0x36, 0xf2, 0x34, 0x7f, 0xb8, 0xb4, 0x30, 0x22, 0xc6, 0x45, 0xcd, 0x27, 0xe2, 0xab, 0x29, + 0x46, 0xa0, 0xf5, 0x04, 0xe4, 0xf5, 0x70, 0xca, 0xdb, 0x75, 0x8d, 0x71, 0xe9, 0x9a, 0x64, 0xf2, 0xa2, 0xc0, 0xa9, + 0x2f, 0x93, 0x7f, 0x19, 0x7f, 0x02, 0x9b, 0x78, 0x4e, 0x27, 0x3e, 0x4e, 0xb6, 0x3a, 0x51, 0x54, 0x40, 0x54, 0x8b, + 0xf0, 0x4a, 0x7a, 0x11, 0x92, 0x9a, 0xf1, 0x32, 0x10, 0xea, 0x78, 0xa3, 0x01, 0xc9, 0xfb, 0xba, 0x12, 0x5e, 0x5b, + 0xbe, 0x5c, 0x84, 0xbc, 0xd9, 0x0e, 0x6b, 0x77, 0x3e, 0x9d, 0x6e, 0x6f, 0x56, 0xc8, 0x0d, 0x50, 0x3a, 0x19, 0xae, + 0x82, 0xbe, 0xa1, 0xd9, 0x91, 0x3c, 0xa1, 0x23, 0x5f, 0x66, 0x65, 0x4c, 0xc2, 0xe2, 0x74, 0x43, 0x8e, 0x4a, 0x5e, + 0x29, 0xed, 0x2e, 0x64, 0x4f, 0x71, 0xaa, 0x3b, 0x58, 0x0f, 0x44, 0xe5, 0x10, 0x53, 0x6a, 0x14, 0x9e, 0xc4, 0xad, + 0x1c, 0x59, 0xfb, 0xb0, 0x4e, 0x2e, 0xbc, 0x8a, 0x33, 0x1d, 0xd2, 0xb6, 0xb5, 0xb9, 0xdb, 0x6c, 0x54, 0xa4, 0xcb, + 0x12, 0x69, 0xef, 0xed, 0xfa, 0x65, 0xd5, 0xa3, 0x49, 0x8a, 0xd2, 0xf7, 0x25, 0x8e, 0x2f, 0xeb, 0xa8, 0x7b, 0x3b, + 0x89, 0x25, 0x7c, 0x64, 0x99, 0x38, 0x05, 0x77, 0xc0, 0x58, 0x65, 0x27, 0xa2, 0x16, 0x48, 0xea, 0xbf, 0xf4, 0xb2, + 0x9e, 0x82, 0x14, 0xef, 0x96, 0xf5, 0x7a, 0x86, 0xc4, 0x7c, 0xc9, 0x18, 0xad, 0xc1, 0x00, 0x55, 0xd0, 0xf3, 0xd5, + 0x73, 0x40, 0xe0, 0x99, 0x4d, 0x2f, 0xbf, 0x13, 0x45, 0x9c, 0xdd, 0x65, 0x85, 0x26, 0x5a, 0x3c, 0xcb, 0x1e, 0x16, + 0xd8, 0x57, 0x0a, 0xf2, 0xec, 0xea, 0x25, 0x85, 0x96, 0x4d, 0x18, 0xf3, 0x1b, 0xa6, 0xbe, 0x02, 0xfb, 0xf2, 0x5a, + 0x99, 0x74, 0x56, 0x77, 0xb5, 0xc6, 0x02, 0xcf, 0x8b, 0x2a, 0x48, 0xa2, 0xde, 0x86, 0x99, 0x95, 0x89, 0x53, 0x3e, + 0xaa, 0x0a, 0x96, 0xb3, 0xf3, 0xdd, 0x94, 0xd0, 0xe8, 0xd1, 0x7f, 0xd5, 0x35, 0x09, 0xaa, 0xf4, 0xc8, 0x8c, 0x63, + 0x70, 0x11, 0x2d, 0xf4, 0xb3, 0x76, 0x5d, 0x54, 0x74, 0x7e, 0xcd, 0x62, 0x5a, 0x5f, 0x97, 0x92, 0x36, 0x3a, 0x2b, + 0xa4, 0xc4, 0xa2, 0x31, 0xcf, 0x2a, 0x64, 0xfb, 0xbd, 0xab, 0xad, 0xd5, 0x06, 0xc2, 0x4d, 0x26, 0x25, 0x48, 0x4a, + 0xc2, 0x3f, 0x94, 0x67, 0x5b, 0x46, 0x34, 0x2a, 0xad, 0x91, 0x2e, 0xaa, 0xd6, 0x9c, 0xb7, 0xa2, 0x30, 0x7f, 0xc2, + 0xe2, 0xbc, 0x46, 0xde, 0x08, 0x85, 0x00, 0xe1, 0x22, 0xfc, 0x39, 0x80, 0xfb, 0x3b, 0x96, 0x15, 0x0f, 0xab, 0xe9, + 0x29, 0xaf, 0xd4, 0x36, 0x8e, 0xc0, 0x01, 0x79, 0x8b, 0x93, 0xc1, 0x05, 0x92, 0x11, 0x26, 0x7e, 0x85, 0x68, 0x83, + 0xa5, 0x62, 0x52, 0x5a, 0x7c, 0xae, 0x6c, 0x42, 0xa7, 0x4f, 0xcb, 0x8a, 0xb9, 0xfa, 0x80, 0x3e, 0x5b, 0x55, 0x76, + 0xbe, 0x76, 0x0c, 0x43, 0x7e, 0x32, 0x5b, 0xe4, 0x49, 0xc9, 0xef, 0xc0, 0x98, 0x96, 0x37, 0x49, 0x6e, 0x53, 0x0d, + 0xfa, 0x58, 0x55, 0xf8, 0xea, 0x3d, 0xe7, 0x22, 0x3e, 0x98, 0xab, 0x11, 0xe9, 0x57, 0x83, 0xa9, 0x7f, 0xad, 0xdf, + 0xa7, 0x92, 0xe8, 0xd8, 0xe9, 0xba, 0xd0, 0xbc, 0x83, 0x4b, 0x2a, 0x2a, 0x72, 0x35, 0x0c, 0x51, 0x40, 0xa1, 0x94, + 0x91, 0xda, 0xd7, 0x12, 0x59, 0x9b, 0x95, 0x3b, 0xd9, 0x7e, 0xb4, 0x9a, 0xcd, 0x30, 0xe5, 0xa5, 0xf5, 0xae, 0xac, + 0x2b, 0x3f, 0xe8, 0xca, 0x0e, 0xe9, 0x83, 0x7a, 0x22, 0x97, 0x4d, 0xe1, 0xe7, 0x5b, 0x9b, 0x03, 0x94, 0xfa, 0x5f, + 0x68, 0x5c, 0x86, 0xb3, 0x81, 0x4d, 0xe8, 0xea, 0x40, 0x7c, 0x50, 0xe6, 0x92, 0x6c, 0x5f, 0xf0, 0x84, 0xba, 0xee, + 0x82, 0x79, 0xd6, 0x15, 0x8b, 0xa2, 0xff, 0xf8, 0x9e, 0x85, 0xf7, 0xf4, 0xc9, 0xb0, 0xf2, 0x80, 0x7a, 0x79, 0xac, + 0xe5, 0xb2, 0x0e, 0x4c, 0x56, 0x12, 0x53, 0x4d, 0x58, 0xd5, 0x2d, 0xcd, 0x61, 0xeb, 0x8c, 0xe6, 0x34, 0xdd, 0x24, + 0xdf, 0x1f, 0x28, 0x9c, 0x44, 0x86, 0xbf, 0x5a, 0xdb, 0x81, 0x81, 0x06, 0x89, 0xea, 0x02, 0x54, 0x4a, 0xdc, 0x2f, + 0x54, 0x6b, 0x52, 0x95, 0x65, 0x07, 0x0a, 0x4b, 0xbe, 0x51, 0xd5, 0x2d, 0xbf, 0x5d, 0x94, 0xa8, 0x60, 0x94, 0xff, + 0xa9, 0x75, 0x59, 0x40, 0xb4, 0x1d, 0x5c, 0xeb, 0xb1, 0x57, 0x3e, 0xed, 0x62, 0x38, 0xde, 0x61, 0x57, 0xbf, 0xd3, + 0xea, 0x1c, 0xd5, 0x85, 0xa5, 0xc4, 0xb9, 0x57, 0xc8, 0x73, 0xb6, 0xb3, 0x9f, 0xf3, 0xf6, 0xa2, 0x83, 0x32, 0x7e, + 0xb9, 0x35, 0xcc, 0x6c, 0x16, 0xae, 0x8a, 0x80, 0x19, 0x7d, 0x75, 0x25, 0x00, 0xb0, 0x80, 0x29, 0x61, 0x61, 0xc4, + 0x8e, 0xa3, 0x3c, 0x73, 0x4c, 0x65, 0x9f, 0x7b, 0x46, 0xd7, 0x37, 0x27, 0xee, 0x91, 0xcb, 0x1d, 0xb4, 0x5a, 0x89, + 0xe3, 0x64, 0x61, 0x2d, 0x5f, 0x74, 0x05, 0xa6, 0x09, 0x49, 0x97, 0x5f, 0xcd, 0x81, 0x54, 0xad, 0xee, 0xc4, 0x3c, + 0x67, 0x13, 0x40, 0x6f, 0xdf, 0x35, 0x01, 0x8f, 0xc9, 0xf1, 0xcd, 0xe8, 0xde, 0x02, 0x33, 0xd2, 0xf5, 0x85, 0xd0, + 0x77, 0x2b, 0x99, 0xaf, 0x5b, 0xa3, 0x60, 0x44, 0xbb, 0x20, 0xfc, 0x8d, 0xe3, 0x12, 0xdb, 0xd0, 0xd2, 0x5d, 0x2f, + 0x82, 0x18, 0x08, 0x44, 0x72, 0x63, 0x14, 0x78, 0x7f, 0x76, 0xae, 0x7b, 0x31, 0x64, 0x29, 0x68, 0x46, 0x0f, 0x5e, + 0xb0, 0x5d, 0x26, 0x24, 0x13, 0xf9, 0x0e, 0x0d, 0x81, 0x95, 0xb9, 0x13, 0xfd, 0x08, 0xf0, 0xaa, 0xb8, 0xb5, 0xdf, + 0x27, 0xfa, 0xbd, 0xea, 0x43, 0x71, 0xe9, 0xb5, 0x82, 0xca, 0x6a, 0x24, 0xde, 0x0c, 0x3a, 0xe0, 0xd1, 0xe5, 0xa7, + 0x4a, 0x8c, 0x66, 0x10, 0x3c, 0x40, 0x14, 0x11, 0x65, 0xf6, 0x95, 0xdc, 0x16, 0x77, 0x87, 0x53, 0x40, 0x20, 0x63, + 0xd6, 0x65, 0x17, 0xc3, 0x44, 0x60, 0x89, 0xf9, 0x66, 0x7c, 0x31, 0x82, 0x1f, 0xdb, 0x7d, 0x54, 0xce, 0x45, 0xb9, + 0x06, 0x63, 0x1b, 0xf3, 0x99, 0x15, 0x7b, 0x82, 0x6f, 0x34, 0xd2, 0xd1, 0xcb, 0x18, 0xca, 0x25, 0xca, 0xc1, 0x4a, + 0xf7, 0x09, 0xf4, 0x60, 0x45, 0x15, 0x20, 0xce, 0x6e, 0x9c, 0x71, 0x6a, 0xc0, 0x2c, 0xb9, 0x21, 0x2d, 0x68, 0x72, + 0xea, 0xf0, 0x6b, 0x3a, 0x7a, 0x0e, 0x30, 0x29, 0xee, 0xc9, 0xcb, 0xe1, 0xd4, 0xb4, 0xd6, 0xd3, 0x5a, 0x7f, 0x03, + 0x0d, 0xb1, 0xa0, 0x2d, 0x6a, 0x67, 0x6f, 0xc0, 0xcc, 0x17, 0x6c, 0x1b, 0x6a, 0x8d, 0x3f, 0xec, 0x87, 0x16, 0x76, + 0x92, 0xe1, 0x34, 0x88, 0x24, 0xce, 0xc1, 0x34, 0x0a, 0xf1, 0x87, 0x56, 0x97, 0x15, 0xab, 0xf2, 0xc4, 0x6f, 0xdd, + 0x5f, 0x2b, 0xa5, 0xd1, 0xe7, 0x9f, 0xc5, 0xc2, 0x19, 0x99, 0xd8, 0xaf, 0xf6, 0xc2, 0xc2, 0xa2, 0xb2, 0x03, 0x57, + 0x35, 0x1a, 0x9e, 0x25, 0x2f, 0x85, 0xa7, 0x1c, 0x56, 0x68, 0x99, 0x09, 0x3f, 0x8f, 0xf3, 0x6a, 0xec, 0xcd, 0xa8, + 0x46, 0xb5, 0x62, 0x0a, 0xea, 0xf4, 0xe8, 0x40, 0xb8, 0x4c, 0x01, 0x56, 0xd9, 0x02, 0xd4, 0x9f, 0x5f, 0xe7, 0x1e, + 0x7d, 0x1a, 0x06, 0x2f, 0xca, 0x31, 0xd6, 0x20, 0xe8, 0x1d, 0x44, 0x21, 0x46, 0x47, 0xd2, 0x37, 0x29, 0xbd, 0xf9, + 0x23, 0x8f, 0xf1, 0x0d, 0xf1, 0x77, 0xc1, 0xce, 0xb7, 0xdc, 0xe7, 0xce, 0xe2, 0x35, 0x66, 0xcd, 0x75, 0xb4, 0x0e, + 0x43, 0xdd, 0x25, 0x30, 0x0d, 0x41, 0xc3, 0x44, 0x13, 0x04, 0x63, 0xc9, 0x73, 0xba, 0x36, 0x9a, 0xa0, 0xf4, 0x42, + 0x12, 0xfc, 0xbf, 0x4a, 0x78, 0x39, 0xa7, 0x72, 0x3a, 0x8a, 0x5a, 0xf0, 0x10, 0x5c, 0x55, 0x43, 0x2d, 0x50, 0x27, + 0x0f, 0x4f, 0xa1, 0x25, 0x63, 0x05, 0x9e, 0x63, 0x9f, 0x9b, 0x34, 0xc0, 0x78, 0x24, 0xf3, 0xb0, 0x61, 0xc2, 0x15, + 0x7a, 0xb6, 0x98, 0x33, 0x3b, 0xe6, 0x6d, 0x85, 0x91, 0xbd, 0x19, 0x97, 0x78, 0xf6, 0x3a, 0x16, 0xb3, 0xed, 0x71, + 0x68, 0x31, 0x37, 0x0f, 0x1c, 0xb5, 0x58, 0x9b, 0x08, 0x0a, 0x7d, 0x03, 0x5b, 0x80, 0xc1, 0x4e, 0xce, 0xaa, 0x51, + 0x42, 0xb2, 0xe6, 0x16, 0x40, 0x9e, 0xe9, 0x28, 0x84, 0x54, 0x36, 0xfc, 0xc4, 0x5a, 0xf2, 0x77, 0x60, 0xe7, 0xfc, + 0xcd, 0xc3, 0x40, 0x88, 0xda, 0x46, 0x68, 0x02, 0xfa, 0xea, 0xb5, 0x96, 0x10, 0x20, 0x0c, 0x82, 0x2b, 0xfa, 0xcb, + 0x9e, 0x84, 0x6e, 0x2e, 0xaf, 0xcb, 0x7b, 0x42, 0xd4, 0x75, 0xb0, 0x1e, 0x91, 0xf1, 0xdc, 0x15, 0xfe, 0xab, 0xde, + 0x0f, 0x12, 0x25, 0x14, 0x4b, 0x45, 0xf2, 0x23, 0xaa, 0x23, 0xc6, 0x11, 0xda, 0xd1, 0x49, 0x3e, 0x76, 0x85, 0x81, + 0x38, 0x54, 0x5b, 0x66, 0x7a, 0x7e, 0xc4, 0x56, 0x33, 0xf6, 0x28, 0xb8, 0x9e, 0x2c, 0x35, 0xbc, 0x40, 0x94, 0xae, + 0x7f, 0x04, 0x34, 0x93, 0xff, 0x98, 0xd9, 0xe6, 0xa9, 0xd9, 0x47, 0x45, 0xdf, 0x64, 0x76, 0x8e, 0x2c, 0x38, 0x8a, + 0xc2, 0x2b, 0x21, 0xf0, 0x52, 0x47, 0x3c, 0x35, 0x52, 0xc4, 0x3c, 0x64, 0x9a, 0x82, 0x5c, 0x0f, 0xe8, 0x0b, 0x4d, + 0x8e, 0xaa, 0x2e, 0xc7, 0xf4, 0x40, 0x81, 0x58, 0x1d, 0xdb, 0x11, 0xe2, 0xe2, 0x36, 0x11, 0xc3, 0x69, 0xd5, 0x65, + 0x0f, 0x49, 0xad, 0xf3, 0x74, 0xcc, 0x14, 0xe4, 0xc0, 0x4d, 0x58, 0xfd, 0xce, 0x71, 0x68, 0x17, 0x05, 0xb7, 0x6f, + 0xa9, 0x44, 0xb2, 0x51, 0xa6, 0xfb, 0x32, 0xfc, 0x20, 0x9a, 0x45, 0x03, 0xc8, 0x06, 0x7c, 0xa5, 0x3f, 0x8c, 0xa2, + 0xeb, 0x3b, 0xbf, 0xcc, 0x9a, 0x29, 0x5b, 0xbf, 0xdb, 0x30, 0xdb, 0x3a, 0x1e, 0x28, 0xb4, 0xa6, 0x85, 0x46, 0x9b, + 0xfa, 0xac, 0xf0, 0xad, 0x75, 0x02, 0x31, 0x39, 0xb9, 0xd9, 0xc8, 0x63, 0xb0, 0x8e, 0xb0, 0xee, 0x31, 0x36, 0x27, + 0xf1, 0x2f, 0xb5, 0x99, 0x0b, 0xc2, 0x33, 0x2b, 0x59, 0xf0, 0x0f, 0xba, 0x19, 0x6c, 0x39, 0x6f, 0xc2, 0xbf, 0xb1, + 0xa6, 0x09, 0x93, 0x35, 0x69, 0x05, 0xe5, 0x90, 0xd4, 0x6e, 0x68, 0xb4, 0x4e, 0x5e, 0xb6, 0x28, 0x10, 0x52, 0x13, + 0xcf, 0x45, 0x75, 0x27, 0xa3, 0xa5, 0x17, 0xe9, 0xc6, 0xde, 0x37, 0x3f, 0x87, 0xcf, 0xb4, 0xc2, 0x8b, 0x15, 0xc2, + 0x80, 0xfe, 0x64, 0x58, 0xdf, 0xab, 0xa4, 0xdd, 0x57, 0xed, 0x64, 0xd1, 0xda, 0x98, 0xaf, 0x02, 0x26, 0xd6, 0x3d, + 0xc5, 0xbc, 0x5c, 0x9d, 0xf4, 0xf7, 0xae, 0xc5, 0x16, 0xc6, 0x23, 0x99, 0x47, 0x72, 0x4a, 0xf8, 0x63, 0x40, 0xe3, + 0xdf, 0x50, 0x46, 0x0d, 0x0c, 0x34, 0x58, 0x3d, 0x1a, 0xca, 0x75, 0x00, 0x0e, 0x31, 0x34, 0x11, 0xc9, 0x40, 0x3b, + 0x81, 0x3b, 0x9a, 0x09, 0x52, 0x4f, 0x5b, 0x82, 0x4d, 0x3c, 0x47, 0x37, 0x31, 0x39, 0x19, 0xbb, 0x00, 0x57, 0xe0, + 0x96, 0xf5, 0x0c, 0xdb, 0x6e, 0xb3, 0x5d, 0x84, 0x94, 0x9a, 0x0a, 0xb6, 0xf0, 0x58, 0x6b, 0x02, 0x78, 0x4a, 0x35, + 0xd1, 0xa2, 0x21, 0xd5, 0x97, 0x4e, 0xc0, 0x7e, 0x71, 0xd2, 0x98, 0x5b, 0xd3, 0x58, 0x59, 0x3e, 0x0d, 0xbc, 0xd4, + 0x64, 0x4d, 0xac, 0xd0, 0x67, 0x9c, 0x72, 0x04, 0xe2, 0x1d, 0x7e, 0x73, 0x79, 0x33, 0x49, 0x6f, 0x0b, 0xfd, 0xd8, + 0x64, 0x80, 0x61, 0xe4, 0x1c, 0xe1, 0x17, 0x33, 0xec, 0x6c, 0xc3, 0xf9, 0xe7, 0x04, 0xc9, 0x78, 0x51, 0xf8, 0x57, + 0xe3, 0x05, 0xe9, 0xa8, 0x26, 0x21, 0xfe, 0x81, 0xe8, 0xd7, 0x0b, 0xce, 0xa0, 0x81, 0x36, 0xfb, 0x72, 0x59, 0xb3, + 0xb0, 0x2a, 0x68, 0xb4, 0xcf, 0xcd, 0x57, 0xb3, 0xe5, 0xdb, 0xeb, 0x7f, 0x94, 0xeb, 0x9e, 0x73, 0x1c, 0xb9, 0xd7, + 0x1c, 0x97, 0xbd, 0x2c, 0xf9, 0x41, 0x4b, 0xeb, 0x9d, 0x72, 0xda, 0xca, 0x45, 0x57, 0x50, 0xc2, 0x3f, 0xf6, 0x4f, + 0x8a, 0x64, 0xe7, 0x11, 0x2c, 0x89, 0x72, 0xb9, 0xe6, 0xe2, 0x8d, 0xd5, 0xbd, 0xbd, 0x13, 0x2c, 0x0c, 0x6e, 0xfc, + 0x82, 0x3c, 0x41, 0x92, 0xf2, 0x43, 0xf9, 0x5d, 0x0a, 0x71, 0xb6, 0x9d, 0xd5, 0x75, 0xb4, 0x8a, 0x78, 0xec, 0x5d, + 0x0e, 0x17, 0x76, 0x88, 0xd2, 0xe5, 0x83, 0x8b, 0xab, 0x59, 0x6b, 0x99, 0x2a, 0x93, 0x74, 0xc6, 0x33, 0x16, 0x70, + 0x9c, 0xc9, 0x32, 0x44, 0xd0, 0x33, 0x48, 0xc4, 0xe8, 0x7b, 0x17, 0x2c, 0x46, 0xc3, 0xd6, 0xea, 0xdb, 0x44, 0xd0, + 0x16, 0x14, 0xcd, 0x22, 0x7a, 0x31, 0x32, 0x15, 0x5e, 0x67, 0x13, 0x5a, 0xae, 0x64, 0x5e, 0x42, 0xab, 0x21, 0x6b, + 0x58, 0x94, 0xfb, 0x34, 0xf1, 0x83, 0x79, 0x3e, 0xb7, 0x50, 0x5b, 0x19, 0xab, 0x9f, 0x70, 0x66, 0xa9, 0x73, 0x41, + 0xb3, 0x5f, 0xd3, 0x5c, 0x81, 0xe7, 0xc9, 0xe6, 0xcb, 0x6f, 0xc4, 0xfa, 0x78, 0xca, 0xcf, 0xa0, 0xca, 0xdb, 0x84, + 0x25, 0xec, 0xcf, 0xa9, 0x52, 0x3c, 0xb2, 0xe1, 0x96, 0x55, 0x4b, 0x51, 0x1d, 0x8b, 0x24, 0x8a, 0x8c, 0x9d, 0x0c, + 0x67, 0xe8, 0x85, 0xc4, 0xb3, 0x59, 0x83, 0x09, 0x93, 0xf3, 0x2c, 0xde, 0x29, 0xcc, 0x95, 0x48, 0x12, 0x8b, 0x34, + 0x42, 0x91, 0xf6, 0x2d, 0xb9, 0x4c, 0xf2, 0x53, 0x93, 0xdb, 0x91, 0x50, 0xe5, 0x15, 0xfe, 0x1a, 0x70, 0x49, 0x88, + 0x54, 0xa0, 0x12, 0x9f, 0xfb, 0x8e, 0x58, 0xa2, 0x49, 0x15, 0xa5, 0x28, 0xa8, 0x97, 0xe9, 0x5f, 0x31, 0x2f, 0x4d, + 0x69, 0xec, 0x8e, 0xc0, 0xdd, 0x77, 0x19, 0x2b, 0x89, 0x3b, 0x8e, 0x99, 0x4c, 0x6b, 0x01, 0xd8, 0xa3, 0xcb, 0x4d, + 0xde, 0xe5, 0x80, 0xcb, 0xe3, 0xe3, 0x16, 0x40, 0xb0, 0x87, 0x05, 0xfc, 0xb5, 0x9d, 0x4b, 0x1d, 0x67, 0x24, 0x22, + 0x16, 0x9c, 0x21, 0x8b, 0x27, 0x6f, 0x00, 0x48, 0xce, 0xef, 0xe2, 0xe7, 0x05, 0x6d, 0x07, 0x40, 0x15, 0x8e, 0x0a, + 0x40, 0xec, 0x90, 0x60, 0xd0, 0x85, 0x77, 0xb2, 0xaf, 0x5a, 0xb3, 0xe3, 0xed, 0x05, 0x75, 0x1b, 0xcd, 0x3c, 0xd2, + 0x93, 0x92, 0x08, 0xe2, 0x0c, 0xb3, 0x1f, 0x04, 0x25, 0xaa, 0x57, 0xf5, 0x84, 0x30, 0x3a, 0x5b, 0x92, 0xc5, 0x4d, + 0x83, 0x80, 0xb4, 0x8f, 0x10, 0x33, 0xd9, 0x2e, 0xe5, 0x98, 0x7c, 0xed, 0x19, 0xe7, 0xac, 0x39, 0x43, 0x28, 0x1a, + 0xd8, 0xad, 0x25, 0x10, 0xeb, 0x1c, 0xca, 0x68, 0x28, 0x4d, 0xf9, 0x85, 0x1c, 0x41, 0xad, 0x63, 0xaf, 0x4c, 0x86, + 0x76, 0x1b, 0xdc, 0xfd, 0x80, 0x14, 0x29, 0xdc, 0xa3, 0x81, 0x65, 0x93, 0xd5, 0xe2, 0x92, 0x59, 0xbc, 0xe5, 0x91, + 0x62, 0x25, 0xb3, 0x1f, 0x04, 0xc3, 0x0b, 0xac, 0xe1, 0x62, 0x91, 0x8e, 0x12, 0xb2, 0x0a, 0x2e, 0x8b, 0xf5, 0xfe, + 0xec, 0xd6, 0x5d, 0x37, 0x05, 0xb9, 0xcd, 0xc9, 0x98, 0x29, 0xc7, 0xe3, 0x0a, 0xd2, 0x86, 0x5c, 0x1e, 0x07, 0x69, + 0xa4, 0xa9, 0x50, 0x3a, 0xb3, 0x7e, 0xba, 0x3f, 0xd5, 0xe3, 0xc5, 0x5c, 0x9d, 0x2c, 0x20, 0xa0, 0x8d, 0x3b, 0x70, + 0xca, 0x2a, 0x2c, 0x09, 0x87, 0x24, 0x6c, 0x78, 0x00, 0xa6, 0x5a, 0x3f, 0x8a, 0x4a, 0xfc, 0xbb, 0x64, 0x13, 0x41, + 0xa5, 0xe7, 0x06, 0xe7, 0x67, 0x69, 0x3c, 0x19, 0x85, 0x9f, 0xc4, 0x14, 0xce, 0x38, 0xcc, 0x11, 0xa2, 0xb2, 0x44, + 0xbf, 0x41, 0x89, 0xe7, 0x7e, 0x5a, 0xf2, 0x7f, 0xb6, 0x71, 0xfe, 0xa0, 0x8c, 0xe6, 0xd9, 0xb2, 0xe9, 0xb3, 0x05, + 0xc3, 0xde, 0x96, 0xb4, 0xb5, 0xee, 0x2c, 0x8a, 0xff, 0x8d, 0xea, 0xf0, 0xe9, 0x0e, 0x93, 0x58, 0x0f, 0x5c, 0x49, + 0xf0, 0xa9, 0x39, 0xe1, 0xd3, 0x9d, 0x3a, 0xe1, 0x87, 0x84, 0x88, 0x77, 0x8c, 0x8c, 0xb6, 0xc6, 0xd4, 0x6c, 0x05, + 0x8b, 0x4b, 0x2f, 0x2a, 0x82, 0x9d, 0x64, 0xd8, 0x94, 0x77, 0xbf, 0xe3, 0x95, 0x66, 0x07, 0x09, 0xe1, 0x45, 0xaa, + 0xed, 0x5c, 0xa3, 0xc5, 0x9c, 0x16, 0x50, 0x4a, 0x2a, 0x25, 0xd1, 0x6c, 0x1a, 0xc7, 0x6a, 0x81, 0x9f, 0x17, 0x24, + 0xb9, 0x55, 0xac, 0xfb, 0xb5, 0x33, 0x9c, 0xa8, 0x92, 0x9a, 0x92, 0x9a, 0xba, 0xb4, 0xa4, 0xc7, 0x60, 0xfe, 0xb7, + 0xc6, 0x44, 0xca, 0x35, 0x2e, 0x3c, 0xf0, 0x84, 0x32, 0x7e, 0x3d, 0x54, 0x3b, 0x59, 0x85, 0x33, 0xaf, 0xef, 0x2f, + 0xc3, 0xa1, 0xce, 0x85, 0x33, 0x0e, 0xdd, 0x70, 0x39, 0xb6, 0xdd, 0x6f, 0xed, 0xfc, 0x05, 0xfc, 0x45, 0x50, 0x2c, + 0x49, 0xa4, 0x66, 0xee, 0xfc, 0xa0, 0xec, 0xd4, 0x61, 0xee, 0x50, 0xa3, 0x95, 0xf1, 0xd4, 0x38, 0xd7, 0xd7, 0x58, + 0xa6, 0x37, 0x6a, 0x52, 0x45, 0x58, 0xd3, 0x40, 0x0d, 0x0f, 0xe9, 0x3c, 0x0b, 0x7a, 0x6a, 0x65, 0xfb, 0x74, 0xd0, + 0x07, 0x09, 0xcf, 0xa9, 0x80, 0x38, 0x13, 0x45, 0x2e, 0xbe, 0xf6, 0xfe, 0x32, 0xef, 0x57, 0x70, 0xb0, 0x41, 0xc9, + 0x5c, 0x3c, 0xb3, 0x38, 0x47, 0xcf, 0x0c, 0xe7, 0xf4, 0x99, 0xb5, 0xb3, 0x1d, 0xf6, 0x73, 0x29, 0x76, 0x25, 0xb0, + 0x28, 0x49, 0xca, 0x74, 0x5c, 0xb9, 0x3a, 0x9c, 0x3b, 0x0b, 0xe7, 0x91, 0xa9, 0x3a, 0xc5, 0x60, 0x52, 0xa6, 0xb4, + 0x7a, 0x62, 0x5b, 0x62, 0x6c, 0x99, 0x40, 0xb0, 0x4b, 0xbf, 0xae, 0xdc, 0xf6, 0x8b, 0xbb, 0x24, 0x85, 0xda, 0xd2, + 0xda, 0xf4, 0x24, 0x0a, 0x59, 0xf3, 0x4b, 0x1b, 0x4f, 0x89, 0xbd, 0xf3, 0x63, 0x15, 0x1d, 0xa4, 0x4b, 0x71, 0xac, + 0xdd, 0x89, 0x80, 0xcb, 0xb5, 0xa1, 0xfc, 0xb4, 0x35, 0x10, 0xd9, 0xf3, 0x16, 0xc9, 0xca, 0x1f, 0xca, 0xb0, 0x3c, + 0x21, 0x84, 0x89, 0xc0, 0xca, 0x58, 0x28, 0xad, 0x24, 0xb0, 0x0a, 0x7c, 0x94, 0xaa, 0xc5, 0xec, 0xb4, 0xf8, 0x3e, + 0x84, 0x6c, 0x8e, 0x9b, 0xd0, 0x9d, 0x00, 0xf2, 0x7a, 0x06, 0xd3, 0x55, 0x88, 0x02, 0xcd, 0x0c, 0x20, 0xe1, 0x87, + 0xec, 0xf6, 0x05, 0xcc, 0x1f, 0xd3, 0xb5, 0x5b, 0xb5, 0x72, 0x17, 0xed, 0x74, 0x2e, 0x89, 0x55, 0x9a, 0x6a, 0x52, + 0x5c, 0x94, 0x64, 0x21, 0xb1, 0x68, 0xe0, 0x95, 0x2b, 0x56, 0x9d, 0xfb, 0xc0, 0x6f, 0xd8, 0x36, 0x9e, 0xaf, 0xfa, + 0x31, 0xae, 0x40, 0xd5, 0xa8, 0x86, 0x1d, 0x7e, 0x00, 0xa6, 0xa6, 0x17, 0x09, 0x62, 0xb1, 0x59, 0xec, 0xce, 0x40, + 0x47, 0xf6, 0x51, 0xf1, 0xa4, 0x4c, 0x25, 0x0b, 0x54, 0x72, 0x8d, 0x14, 0x56, 0x5b, 0xb3, 0xa8, 0x4d, 0xc4, 0x7b, + 0xee, 0xd0, 0xba, 0x8a, 0xcb, 0x4c, 0x11, 0x7b, 0xa8, 0xe8, 0x33, 0x7a, 0xee, 0xc3, 0x2d, 0xbe, 0x87, 0x14, 0xc1, + 0x96, 0x8a, 0x91, 0x29, 0x09, 0xed, 0xc9, 0x0a, 0xa5, 0xc9, 0x12, 0x3c, 0x34, 0x50, 0x85, 0x0d, 0xf9, 0x0c, 0x07, + 0x6c, 0x3f, 0xa6, 0xf0, 0x64, 0x81, 0x12, 0x73, 0xa8, 0x76, 0x73, 0xf0, 0x5d, 0xed, 0x80, 0x77, 0x64, 0xcc, 0xcb, + 0xe4, 0x26, 0x17, 0xde, 0x91, 0xae, 0xbf, 0x1f, 0x07, 0x3b, 0x44, 0x18, 0xea, 0xa6, 0x80, 0xf4, 0xbc, 0x97, 0x0b, + 0x45, 0x89, 0x6f, 0xad, 0x18, 0xa8, 0xde, 0x20, 0x5d, 0x34, 0x05, 0xea, 0x60, 0xd4, 0x03, 0x3f, 0x21, 0xc8, 0x01, + 0x95, 0xd1, 0xa7, 0x2b, 0xda, 0xe2, 0xfa, 0xf3, 0x6c, 0x08, 0xc8, 0xd0, 0x8a, 0xe4, 0x1d, 0x30, 0x8d, 0xfa, 0x68, + 0x68, 0xf7, 0x4d, 0x2c, 0x13, 0x80, 0x64, 0x17, 0xaf, 0x2c, 0x91, 0x09, 0x60, 0x0b, 0xec, 0xd8, 0x3c, 0xba, 0xe1, + 0xdb, 0xf5, 0xc9, 0x80, 0xa1, 0x65, 0xd6, 0xdb, 0xa7, 0xab, 0x8f, 0xc6, 0xe7, 0x9a, 0x1a, 0x15, 0xc7, 0x45, 0x32, + 0x64, 0xaa, 0xa8, 0xf3, 0xc9, 0x26, 0x2a, 0x62, 0x6d, 0x2e, 0xfb, 0xce, 0x87, 0x31, 0xe8, 0xb1, 0x45, 0x95, 0x11, + 0xd7, 0x8e, 0xd9, 0xaf, 0x2f, 0xd6, 0xe5, 0x78, 0x9c, 0xd3, 0x07, 0x12, 0xa6, 0xed, 0x24, 0x42, 0xdd, 0x89, 0xf2, + 0x4d, 0x53, 0x9a, 0x05, 0x61, 0x3f, 0x58, 0xa4, 0x63, 0x0d, 0x9b, 0x6f, 0xc6, 0x6c, 0x6e, 0xa0, 0xba, 0xf2, 0x03, + 0xd4, 0x81, 0xb8, 0x18, 0x70, 0xf1, 0x0e, 0x8c, 0x39, 0xf3, 0xef, 0xb8, 0xbf, 0x2a, 0xa5, 0x34, 0xea, 0xf8, 0xb2, + 0xd4, 0xc8, 0xf6, 0x7e, 0xd9, 0x7f, 0x65, 0xf6, 0x21, 0xdf, 0x15, 0xa8, 0x50, 0x85, 0x34, 0x34, 0x89, 0x7a, 0xed, + 0x00, 0xb1, 0x9d, 0x8d, 0x26, 0x6a, 0xc5, 0x22, 0x52, 0x1e, 0x01, 0x0e, 0xe5, 0xf0, 0x5e, 0xb7, 0x0d, 0x23, 0xbe, + 0xbd, 0x4a, 0x3d, 0xd3, 0x96, 0x68, 0x1d, 0x0c, 0xf1, 0x2e, 0x5a, 0x4d, 0xe2, 0xcc, 0x0c, 0xe9, 0xd8, 0x99, 0xba, + 0x5f, 0xa2, 0xe8, 0x5d, 0x16, 0xd8, 0x6a, 0xae, 0xb6, 0x7e, 0x67, 0x45, 0x1f, 0xc2, 0x6a, 0xd9, 0xea, 0x7b, 0x31, + 0xd3, 0xd8, 0x4c, 0x9c, 0x20, 0x16, 0x40, 0xb3, 0x77, 0xee, 0x12, 0xc5, 0x98, 0xd9, 0x70, 0x59, 0xcc, 0x12, 0x29, + 0xc2, 0x0e, 0xe8, 0x24, 0x1a, 0x30, 0x31, 0xa7, 0x38, 0x36, 0x62, 0xdf, 0x27, 0xad, 0x6c, 0xe2, 0x4a, 0x28, 0x83, + 0x32, 0x69, 0xdd, 0x4e, 0xbf, 0x4c, 0x7c, 0xef, 0x5b, 0x40, 0xd3, 0x29, 0x73, 0x02, 0x7b, 0xce, 0x11, 0xe2, 0x0b, + 0x10, 0xe4, 0x56, 0x25, 0xef, 0x01, 0x96, 0xea, 0x9c, 0xa5, 0xeb, 0x53, 0x6f, 0x6c, 0x4b, 0xea, 0x89, 0xc3, 0xcc, + 0xf1, 0x3c, 0x2a, 0xbd, 0x02, 0xed, 0xe7, 0xbd, 0xef, 0xad, 0x6a, 0x9b, 0xf8, 0xeb, 0xf5, 0x01, 0x25, 0x46, 0xb3, + 0xb1, 0xa5, 0x9e, 0x6a, 0x69, 0xbe, 0xa9, 0x83, 0x9b, 0x10, 0xa2, 0x73, 0x5b, 0xb1, 0x66, 0xcd, 0xda, 0x91, 0x65, + 0xf4, 0xaf, 0x60, 0x85, 0x6f, 0x60, 0x2d, 0x2e, 0x01, 0xcd, 0xdf, 0x18, 0xdf, 0x08, 0x79, 0x5c, 0x7e, 0xa0, 0xf3, + 0x33, 0x46, 0x5d, 0x85, 0xa9, 0x22, 0xe1, 0xe1, 0xa5, 0xd6, 0x6b, 0x2d, 0xe8, 0x98, 0x96, 0x8f, 0x35, 0xf8, 0x42, + 0x4d, 0xab, 0x1d, 0xbc, 0xe5, 0x57, 0x6a, 0xea, 0x42, 0xe7, 0xe7, 0xa9, 0x03, 0x29, 0x5e, 0x7e, 0x45, 0x62, 0x8e, + 0xe5, 0xb7, 0x19, 0xfa, 0xe8, 0x7b, 0xf8, 0x75, 0xc3, 0x0f, 0x3a, 0x2f, 0x50, 0x49, 0x35, 0xce, 0x30, 0x0e, 0xe7, + 0xa7, 0x59, 0x35, 0x62, 0xa6, 0x08, 0x3f, 0x38, 0x75, 0x60, 0xf5, 0xba, 0x96, 0x87, 0x2e, 0x45, 0xa8, 0x02, 0x62, + 0x4f, 0xe3, 0xe7, 0x43, 0x98, 0x2a, 0xa6, 0x22, 0x81, 0x30, 0xa9, 0xd0, 0x9e, 0x92, 0x82, 0xdd, 0x62, 0xd5, 0xae, + 0x7a, 0xb7, 0x62, 0x5e, 0x93, 0x89, 0x80, 0x31, 0xde, 0x81, 0xe6, 0xcd, 0x6c, 0x5b, 0x87, 0xce, 0x89, 0x1d, 0x15, + 0xd8, 0x03, 0x32, 0xf6, 0x0e, 0x77, 0xbf, 0x99, 0x01, 0x27, 0x5c, 0xc3, 0xf4, 0x3c, 0x34, 0x1b, 0xdd, 0x70, 0xe5, + 0x5b, 0xfa, 0x74, 0xe6, 0xc4, 0xd9, 0x02, 0xcd, 0xd7, 0xc8, 0x56, 0xa2, 0xab, 0x9e, 0xa0, 0xee, 0x81, 0x64, 0x6f, + 0xdf, 0x5c, 0xf7, 0x76, 0x77, 0x05, 0x49, 0xa7, 0xbb, 0x19, 0xb0, 0x3b, 0x5c, 0xf0, 0x6e, 0xf5, 0x4c, 0x22, 0x09, + 0x00, 0x90, 0x3d, 0xe9, 0x3e, 0x0a, 0x5b, 0xe8, 0x4e, 0xb7, 0xbf, 0x76, 0x53, 0x59, 0xd0, 0x26, 0x5d, 0x79, 0x0c, + 0x6d, 0x13, 0x46, 0xc4, 0x90, 0x5d, 0x97, 0x91, 0x75, 0x4b, 0x5f, 0x08, 0x17, 0xf0, 0x88, 0x03, 0xb6, 0xc3, 0x76, + 0x41, 0x30, 0x12, 0x90, 0x90, 0x73, 0x21, 0xfe, 0x36, 0x0d, 0x35, 0x2b, 0xb8, 0xdb, 0x6c, 0x88, 0xdd, 0x24, 0xa1, + 0x3f, 0xe8, 0x0a, 0x6f, 0x6e, 0xbd, 0x1c, 0x2b, 0x28, 0xf3, 0xd1, 0x73, 0xb5, 0x9f, 0x35, 0x53, 0x7b, 0x3a, 0x69, + 0x69, 0xc6, 0xbc, 0x54, 0x6a, 0x9e, 0xc8, 0xbb, 0xb9, 0x81, 0x67, 0xe3, 0x99, 0x39, 0xc4, 0x89, 0x2d, 0x4d, 0xeb, + 0x66, 0xcc, 0xd1, 0xee, 0x6c, 0x3e, 0xf6, 0xec, 0xab, 0x9f, 0xcb, 0xbc, 0x54, 0x9f, 0xcd, 0xcd, 0xd2, 0xac, 0x9c, + 0x3f, 0x43, 0x54, 0xd8, 0x56, 0x16, 0x53, 0x4d, 0xe2, 0x18, 0x04, 0x46, 0x8b, 0x6e, 0x6f, 0xa1, 0x19, 0x76, 0x31, + 0x3b, 0xce, 0xa5, 0x59, 0x77, 0x7b, 0x85, 0xe3, 0x97, 0x99, 0xaf, 0x55, 0xed, 0x8d, 0x1b, 0x25, 0x0a, 0x4e, 0x87, + 0x83, 0xf3, 0xb0, 0xfd, 0x4b, 0x91, 0x37, 0x33, 0x8c, 0x25, 0x81, 0x68, 0x2d, 0x5a, 0xb8, 0xca, 0x68, 0xb5, 0x59, + 0x15, 0x21, 0x39, 0xb5, 0x33, 0xff, 0x85, 0x06, 0x90, 0x5a, 0xf0, 0x0a, 0x75, 0x73, 0x81, 0x05, 0xc7, 0xa8, 0xd4, + 0xa1, 0xf1, 0x29, 0xa7, 0x24, 0x43, 0x2a, 0x3a, 0xec, 0x72, 0xa2, 0x75, 0x4e, 0xb6, 0x1c, 0x01, 0x08, 0x94, 0x6a, + 0xc3, 0x06, 0x53, 0x1f, 0x26, 0x99, 0x5b, 0x99, 0x8e, 0x30, 0x53, 0x05, 0xc6, 0xdf, 0xac, 0x16, 0x7b, 0x97, 0x73, + 0x91, 0x24, 0xcc, 0xed, 0x0c, 0x3d, 0x59, 0x80, 0x0e, 0x63, 0x70, 0x7c, 0x3b, 0xf9, 0xa9, 0xfe, 0xb4, 0xba, 0x22, + 0xe3, 0xd4, 0x31, 0x39, 0x7b, 0x6d, 0x07, 0x05, 0x8d, 0xda, 0xee, 0x65, 0x78, 0xcd, 0xb3, 0x02, 0xed, 0xf3, 0xbf, + 0xda, 0xbd, 0xdd, 0xbc, 0xf0, 0xe5, 0xb7, 0x90, 0x15, 0x48, 0x3d, 0xc1, 0x6b, 0x53, 0x19, 0x95, 0x6a, 0xe7, 0x12, + 0x6d, 0xbf, 0x3c, 0x21, 0xc9, 0xb6, 0xf1, 0x6f, 0x91, 0x4b, 0x29, 0x90, 0xfc, 0x7d, 0x6d, 0x24, 0xb2, 0xc5, 0x2c, + 0x49, 0x98, 0xea, 0x35, 0x49, 0x75, 0x1e, 0xd6, 0xb1, 0x9b, 0x8e, 0xff, 0x2c, 0x43, 0xf4, 0x34, 0x12, 0x52, 0xeb, + 0x6d, 0x4d, 0xe6, 0x61, 0x1d, 0xdd, 0xc9, 0x16, 0xcf, 0x79, 0xc4, 0x53, 0x41, 0xc6, 0x6c, 0xb3, 0xee, 0x52, 0x89, + 0x44, 0x2d, 0x58, 0x06, 0xda, 0xed, 0x66, 0x38, 0x45, 0xad, 0x03, 0x14, 0x3b, 0x15, 0x7d, 0x19, 0xba, 0xd2, 0xd4, + 0x67, 0xb2, 0xa1, 0xb0, 0x52, 0x8b, 0xba, 0xbd, 0x94, 0x7a, 0xce, 0x86, 0xae, 0xbc, 0x3c, 0x99, 0x6b, 0xbe, 0x03, + 0xd8, 0x46, 0x1b, 0x4b, 0x37, 0x80, 0x6e, 0x34, 0x03, 0x37, 0x21, 0x03, 0x50, 0xd6, 0x14, 0x2a, 0x37, 0x35, 0xb8, + 0xa4, 0x9e, 0x95, 0x62, 0x0e, 0x48, 0x04, 0x67, 0xec, 0xdb, 0x00, 0x63, 0x7f, 0x8d, 0x9c, 0xc3, 0x55, 0xeb, 0xaa, + 0xad, 0x60, 0x6d, 0x9d, 0x3e, 0x6d, 0x1c, 0xc6, 0x2b, 0xfb, 0x27, 0xe0, 0xbb, 0x78, 0x51, 0x3b, 0x32, 0xfd, 0x2d, + 0x8e, 0x35, 0x84, 0x42, 0xd7, 0x27, 0x86, 0xc2, 0x8c, 0xc1, 0x30, 0xbb, 0xbb, 0x20, 0x4c, 0xaf, 0x2f, 0x05, 0x0c, + 0x0b, 0x37, 0x97, 0x62, 0xc7, 0xf1, 0xf3, 0x07, 0xfb, 0x89, 0x22, 0x1c, 0x9a, 0xa9, 0x10, 0x3e, 0x97, 0xae, 0x8c, + 0x82, 0x9c, 0x99, 0xcc, 0x09, 0x3c, 0xd8, 0x3e, 0x07, 0xd4, 0x28, 0x12, 0x8a, 0x2c, 0x2e, 0x43, 0x13, 0xe5, 0x4d, + 0xc2, 0x05, 0xe9, 0xcb, 0x71, 0x7d, 0x34, 0xbd, 0x86, 0x29, 0x59, 0x99, 0xb7, 0x48, 0xfc, 0x6c, 0x99, 0xf5, 0x11, + 0xe1, 0x74, 0xaf, 0x6d, 0x60, 0x8b, 0xb5, 0x6d, 0xef, 0xd7, 0x3d, 0xe0, 0xca, 0xc2, 0x81, 0xa1, 0x8d, 0x4c, 0x7d, + 0xb5, 0xa1, 0x97, 0x14, 0x71, 0xfe, 0x15, 0x3d, 0x32, 0x7e, 0x30, 0xf6, 0x7d, 0x07, 0x77, 0x0a, 0xa4, 0xb7, 0x39, + 0xbf, 0x61, 0xa6, 0xf7, 0x60, 0x75, 0x03, 0x35, 0xac, 0xc1, 0xa5, 0x32, 0x4b, 0x8d, 0xf9, 0x17, 0xb7, 0xc4, 0x27, + 0x0b, 0x8e, 0x12, 0x9f, 0x42, 0xe2, 0x1a, 0xae, 0x4f, 0x1f, 0x1f, 0x99, 0xf4, 0x6d, 0x12, 0x8a, 0xec, 0x56, 0x2c, + 0xdb, 0x43, 0xc5, 0x98, 0x1c, 0xee, 0x8a, 0xab, 0x36, 0x38, 0x60, 0x88, 0xd2, 0xd1, 0x10, 0x49, 0x83, 0x26, 0x0e, + 0x24, 0x8c, 0xf7, 0xc5, 0x0c, 0xcb, 0x0d, 0x5d, 0xbc, 0x22, 0x7a, 0x6b, 0xcd, 0xce, 0xa4, 0xec, 0x65, 0x45, 0xbe, + 0x29, 0xd5, 0xe4, 0x63, 0xba, 0x5a, 0x4f, 0x4b, 0xaf, 0x2d, 0xf7, 0x58, 0x00, 0x74, 0xaf, 0x8e, 0x7f, 0xbd, 0xef, + 0x75, 0xd5, 0x67, 0x22, 0xdf, 0xfa, 0x7a, 0x18, 0xbe, 0xdd, 0x7f, 0xa9, 0xa3, 0x38, 0xb8, 0x45, 0xec, 0xdf, 0xfe, + 0x48, 0x59, 0xd4, 0xd6, 0xaa, 0x1f, 0xd4, 0xc1, 0xa1, 0xa7, 0x1e, 0x37, 0x67, 0x61, 0x4d, 0x30, 0xe1, 0x54, 0x81, + 0x73, 0xa6, 0x83, 0xd0, 0xc6, 0xf2, 0x6f, 0x1c, 0xd5, 0xa6, 0x6e, 0xdc, 0xa0, 0x3c, 0xe3, 0xd9, 0x58, 0x19, 0xea, + 0xb2, 0x95, 0x9d, 0xf9, 0xb2, 0xf3, 0x8c, 0x9c, 0xf7, 0xac, 0xa6, 0x5f, 0x1a, 0x0b, 0x7c, 0xa5, 0xe2, 0x08, 0xe1, + 0x67, 0xc0, 0xbb, 0xc4, 0xb1, 0x63, 0x66, 0x7c, 0x0c, 0x4a, 0xbb, 0x5b, 0xd2, 0xe8, 0x30, 0xb2, 0x1d, 0x74, 0x9d, + 0xcb, 0x64, 0x44, 0x50, 0x10, 0x22, 0xe4, 0x30, 0xb4, 0x43, 0x28, 0x67, 0xfb, 0xb1, 0xaa, 0xdc, 0x5e, 0xf4, 0x06, + 0xf3, 0x4a, 0xb6, 0x50, 0x04, 0x4c, 0x09, 0xbe, 0x5f, 0xd5, 0xd4, 0x88, 0x7d, 0xd3, 0xbf, 0x3d, 0x7c, 0x9a, 0x8b, + 0x9a, 0xa0, 0x01, 0xff, 0x3b, 0x96, 0xd5, 0xa0, 0x37, 0x56, 0x5f, 0x68, 0xd9, 0xb7, 0x86, 0x1c, 0x18, 0x55, 0x92, + 0xb6, 0x6e, 0x2f, 0x64, 0x95, 0x39, 0x57, 0xbb, 0x42, 0xf5, 0xa5, 0x47, 0x39, 0x99, 0xa6, 0x00, 0x30, 0x5d, 0x69, + 0x01, 0x71, 0x41, 0x21, 0xb4, 0xe0, 0xb0, 0x9a, 0x85, 0x4c, 0x5f, 0xcf, 0x4e, 0x61, 0xc1, 0x68, 0xbc, 0x30, 0xad, + 0x0d, 0x89, 0x32, 0x33, 0xa7, 0x4c, 0x4a, 0x77, 0xa9, 0xdd, 0x82, 0x3c, 0xf8, 0x2d, 0x2d, 0x1b, 0x80, 0x11, 0x13, + 0xc9, 0x77, 0x61, 0x13, 0x59, 0xfb, 0x7c, 0xce, 0xb8, 0xcd, 0xec, 0x49, 0xdf, 0xd4, 0xf4, 0x64, 0xe3, 0x34, 0x58, + 0x7f, 0x84, 0x9c, 0xe7, 0x6e, 0xa4, 0x6c, 0x6d, 0xe2, 0x96, 0xfb, 0x12, 0x1d, 0x43, 0x9f, 0x68, 0x97, 0x13, 0x76, + 0x40, 0x07, 0xfa, 0x4c, 0x5a, 0xc3, 0x35, 0x10, 0xe5, 0x30, 0x88, 0xa7, 0x72, 0x28, 0xae, 0x97, 0x3d, 0x46, 0x9d, + 0xc6, 0x02, 0x35, 0xb0, 0xc2, 0x17, 0x18, 0x46, 0x55, 0x05, 0x7b, 0xe0, 0x6f, 0x82, 0x9c, 0xae, 0xbe, 0x53, 0x2c, + 0x79, 0xd3, 0x12, 0xd1, 0x2e, 0x98, 0xb0, 0x0e, 0x2a, 0x1e, 0x63, 0xab, 0x49, 0x4a, 0x83, 0xa1, 0xeb, 0xc9, 0x77, + 0x41, 0xc6, 0x66, 0x32, 0xd2, 0xb4, 0x80, 0x3b, 0xcc, 0xed, 0x3c, 0x29, 0xcc, 0x21, 0x96, 0x8d, 0xab, 0xb8, 0x71, + 0xed, 0x6b, 0x84, 0x40, 0x27, 0x48, 0xa7, 0x3b, 0xa3, 0xcd, 0x8b, 0xf6, 0x11, 0xaf, 0x63, 0x89, 0x65, 0xad, 0x9c, + 0xee, 0x30, 0xc2, 0x80, 0x88, 0xfb, 0x48, 0x17, 0xcc, 0x2c, 0xb5, 0xb5, 0xb8, 0x2a, 0x62, 0x59, 0xb6, 0x6b, 0x2c, + 0x06, 0x60, 0x14, 0xd8, 0x1f, 0xce, 0x6b, 0x19, 0x34, 0x7a, 0x3e, 0x7c, 0xba, 0x22, 0xa7, 0x65, 0x6d, 0x86, 0x0d, + 0xcf, 0xa6, 0x03, 0x54, 0xb8, 0x26, 0x56, 0xe7, 0x25, 0xd8, 0x8b, 0xb5, 0xe5, 0xe8, 0xdf, 0xbb, 0xf4, 0x22, 0x9e, + 0x17, 0x84, 0x70, 0x2a, 0x36, 0x5b, 0x1a, 0x20, 0x0e, 0x60, 0x17, 0x53, 0x1d, 0x10, 0x82, 0xba, 0xb3, 0xda, 0x03, + 0xf2, 0xf9, 0x6b, 0x86, 0xbe, 0x8f, 0x84, 0x6f, 0x02, 0x64, 0xa6, 0xa0, 0x3c, 0x51, 0xfb, 0x14, 0x45, 0xf4, 0xe0, + 0x27, 0x5d, 0x65, 0xb3, 0x16, 0x75, 0x12, 0x38, 0x1d, 0x71, 0x72, 0x16, 0xa3, 0x70, 0x5e, 0x3e, 0x13, 0xc0, 0x97, + 0x6b, 0x34, 0x98, 0x16, 0xdb, 0x28, 0x4e, 0xd9, 0x4e, 0xba, 0x5b, 0x03, 0x74, 0xc7, 0xe7, 0x88, 0xc3, 0x41, 0x26, + 0xca, 0xde, 0xae, 0x33, 0x5d, 0x86, 0x75, 0x53, 0x47, 0xbd, 0x9f, 0x28, 0x7c, 0x4a, 0xdd, 0xe3, 0x7d, 0x2e, 0x45, + 0x50, 0x21, 0x54, 0x12, 0xd4, 0x32, 0xa4, 0x3f, 0x6a, 0x79, 0x4e, 0x8d, 0xd4, 0x29, 0x8f, 0xcb, 0x05, 0x49, 0x6a, + 0xfb, 0x3e, 0x7b, 0xb4, 0x2f, 0x4f, 0xe4, 0x8e, 0x1b, 0x38, 0xd1, 0xb5, 0x02, 0x86, 0x4e, 0x73, 0x0f, 0x76, 0xde, + 0x8a, 0x8a, 0x64, 0x22, 0x8a, 0xa1, 0xbd, 0x84, 0xb3, 0x2a, 0xbb, 0x49, 0x42, 0x7f, 0x16, 0x2b, 0x9c, 0xd9, 0xb9, + 0x0c, 0xb5, 0x69, 0x6c, 0x61, 0x90, 0x51, 0x21, 0xb4, 0xdb, 0x98, 0x47, 0x98, 0xdc, 0x45, 0x6e, 0xf0, 0x2b, 0xad, + 0x54, 0x2e, 0x15, 0x92, 0xa6, 0x4b, 0x6f, 0xfd, 0x2f, 0x3b, 0x6a, 0xc5, 0x8d, 0xb7, 0x36, 0xca, 0x35, 0xca, 0xc5, + 0xcc, 0xf9, 0x8f, 0xd8, 0xe3, 0x12, 0x6b, 0xd8, 0x82, 0xcb, 0x86, 0xae, 0x50, 0x59, 0x4a, 0x03, 0x47, 0x1e, 0x88, + 0xa4, 0xee, 0x6b, 0x38, 0xe2, 0x16, 0xd5, 0x9f, 0xec, 0xf5, 0xc1, 0x06, 0xb5, 0x63, 0x36, 0x72, 0xb1, 0x8d, 0x5a, + 0xa1, 0x0b, 0x59, 0x45, 0x0d, 0x5c, 0x92, 0xb7, 0x60, 0x9a, 0x0c, 0xd1, 0x4d, 0x92, 0xb8, 0x7b, 0x3a, 0xc3, 0x2c, + 0x33, 0xbd, 0xe8, 0x7f, 0x56, 0xa2, 0xd2, 0xa1, 0xac, 0xb9, 0x92, 0xc3, 0x59, 0x47, 0xf5, 0xe3, 0xb0, 0x1f, 0xe2, + 0xd8, 0x74, 0x87, 0xe5, 0x80, 0x01, 0xac, 0x3a, 0xcc, 0x91, 0xa2, 0xf1, 0x62, 0xeb, 0xbb, 0x7d, 0xb7, 0x8d, 0x94, + 0xd0, 0xd0, 0x2f, 0x76, 0x08, 0xd8, 0xb7, 0xdf, 0x86, 0x39, 0xe3, 0xb6, 0x36, 0x8e, 0xf6, 0x51, 0x44, 0xda, 0x54, + 0x10, 0xfc, 0x91, 0x34, 0x39, 0xc0, 0x36, 0x5d, 0xca, 0x61, 0x73, 0xe5, 0xde, 0x67, 0x86, 0xc5, 0x94, 0x11, 0x31, + 0xab, 0xf7, 0x54, 0xe8, 0xaf, 0x7f, 0xf7, 0xdf, 0x2d, 0x5a, 0x5a, 0x34, 0xca, 0x8b, 0xf3, 0x72, 0x30, 0xb6, 0xea, + 0xd2, 0x7b, 0xb3, 0x34, 0xd6, 0x01, 0x40, 0xe5, 0xee, 0xfd, 0x45, 0x88, 0xbb, 0xeb, 0x2a, 0x44, 0x1f, 0xcc, 0x52, + 0x93, 0xf2, 0xa9, 0xa7, 0x6c, 0x2c, 0x89, 0x3c, 0x65, 0xd6, 0xce, 0xed, 0x33, 0xbb, 0x09, 0x80, 0xf1, 0xbf, 0x32, + 0x3f, 0x2d, 0x2c, 0xf4, 0x9d, 0x56, 0x72, 0x51, 0xdb, 0x68, 0x67, 0xf4, 0x3e, 0x47, 0x81, 0x39, 0x40, 0x24, 0x27, + 0xe4, 0x3d, 0xbe, 0xa2, 0x78, 0xfc, 0x3f, 0x66, 0xa5, 0x61, 0xe3, 0xc4, 0x8e, 0xf2, 0xed, 0xc7, 0x0d, 0x1b, 0x29, + 0x39, 0x0f, 0x23, 0x23, 0x4c, 0xff, 0x1e, 0x99, 0x38, 0x8d, 0xca, 0xce, 0x3e, 0x2a, 0x88, 0x7a, 0xe2, 0xe3, 0xfb, + 0x73, 0x6c, 0xdd, 0x3f, 0x12, 0x2d, 0x65, 0x10, 0x96, 0x02, 0x38, 0x29, 0xf3, 0x48, 0xc3, 0x02, 0x98, 0xa2, 0x79, + 0x90, 0xf1, 0xc9, 0x69, 0x68, 0xbf, 0x7f, 0xe9, 0xf4, 0x1a, 0xb4, 0xbb, 0xc6, 0x30, 0x91, 0x35, 0x38, 0x77, 0x75, + 0xfb, 0x68, 0xd0, 0xdb, 0x7b, 0xde, 0x7e, 0x34, 0xe9, 0xad, 0xcd, 0x59, 0x43, 0x1b, 0x12, 0xc7, 0x3f, 0x6c, 0xff, + 0xa5, 0x9e, 0x27, 0x7b, 0xb7, 0x9a, 0x49, 0x91, 0x75, 0x39, 0xc4, 0x69, 0xd8, 0x52, 0x24, 0x1e, 0x80, 0xb8, 0xd4, + 0xfe, 0x58, 0xb2, 0xbc, 0xda, 0x83, 0xa2, 0xff, 0xd1, 0xfc, 0x48, 0x6b, 0xb2, 0x0f, 0xbe, 0x4c, 0xa1, 0x6a, 0xf7, + 0x33, 0xba, 0x3b, 0xbf, 0x07, 0x39, 0xb6, 0x59, 0x9a, 0xf8, 0xe2, 0xad, 0xa3, 0xe7, 0x89, 0xb4, 0x16, 0x5a, 0x99, + 0x61, 0x7a, 0xea, 0x1e, 0xc3, 0x52, 0x24, 0x4b, 0xcb, 0xde, 0xf2, 0x35, 0xe7, 0xe9, 0x4c, 0x7f, 0x7c, 0x10, 0xd7, + 0xb7, 0x7d, 0x4a, 0x7c, 0x8a, 0x98, 0x5f, 0xed, 0x13, 0xe0, 0x2c, 0x09, 0x1e, 0x47, 0x44, 0xa0, 0xb3, 0x15, 0xe5, + 0x23, 0x55, 0x75, 0xcd, 0xae, 0xff, 0xd1, 0xca, 0x02, 0x3b, 0x33, 0x1b, 0x77, 0x2b, 0x67, 0xfa, 0xe8, 0x34, 0xcf, + 0x72, 0x43, 0x3b, 0x90, 0x8b, 0x0d, 0x70, 0x60, 0xff, 0xb6, 0x49, 0x30, 0xac, 0x6d, 0xb8, 0x3f, 0x52, 0xbd, 0x31, + 0x4a, 0xfe, 0x46, 0x00, 0x46, 0x51, 0xd1, 0x56, 0xf1, 0xe6, 0x1a, 0xba, 0x90, 0x51, 0xbd, 0x3f, 0x79, 0x0f, 0xdf, + 0xef, 0x43, 0x1f, 0xba, 0x75, 0xd0, 0x5a, 0x70, 0x2a, 0x8b, 0x72, 0x39, 0xda, 0x3c, 0xef, 0x46, 0x5c, 0x7a, 0xf9, + 0x4d, 0x4f, 0x94, 0x7a, 0xfb, 0x6b, 0x07, 0x5b, 0x5a, 0x7e, 0x44, 0xa6, 0x9e, 0x24, 0x0a, 0x39, 0xd6, 0x4e, 0xf0, + 0x6a, 0xe9, 0x48, 0xc5, 0x81, 0xc3, 0xdd, 0x93, 0x91, 0x6f, 0xe6, 0x8c, 0x5d, 0x4b, 0x3a, 0x1e, 0x6c, 0x0c, 0xeb, + 0xe6, 0x6b, 0x29, 0xcd, 0x32, 0xeb, 0xd5, 0x3d, 0x3b, 0x11, 0x5e, 0x70, 0x78, 0x25, 0xb6, 0x29, 0xa4, 0xf9, 0xd5, + 0x44, 0x02, 0x37, 0xaf, 0xf7, 0x05, 0x20, 0x97, 0xb9, 0x74, 0x2e, 0xd8, 0x82, 0x74, 0xc5, 0x7f, 0x8e, 0x2a, 0xd0, + 0x27, 0x3f, 0xb3, 0x4a, 0xd7, 0xfa, 0x4a, 0x59, 0xa5, 0xf2, 0x1c, 0xdf, 0xd1, 0xa4, 0xd8, 0x3b, 0xda, 0x93, 0xd9, + 0x21, 0x1c, 0x8d, 0xc1, 0xcd, 0xfd, 0x46, 0x25, 0x65, 0x16, 0xa7, 0x5e, 0x92, 0xfe, 0x4b, 0xc2, 0x0c, 0x83, 0x84, + 0x04, 0x31, 0xff, 0x47, 0xdc, 0x98, 0xa3, 0x0e, 0x69, 0x0c, 0x4e, 0x64, 0x82, 0xd1, 0x42, 0x21, 0xba, 0x29, 0xcb, + 0x95, 0x3a, 0x11, 0xcf, 0x5e, 0xa2, 0x70, 0xda, 0x65, 0x8d, 0x34, 0x2f, 0x7d, 0x0f, 0xbb, 0x87, 0x81, 0x14, 0x34, + 0x0a, 0x4b, 0x63, 0x0c, 0xec, 0xec, 0x26, 0x6d, 0x63, 0xb8, 0xd5, 0x1b, 0x68, 0x0a, 0x77, 0xef, 0xe9, 0x1a, 0xfa, + 0x5c, 0x24, 0x12, 0x4b, 0x7a, 0xb4, 0x8b, 0xc9, 0xb5, 0x96, 0xca, 0xb2, 0x5a, 0x8e, 0xdf, 0x4e, 0xf7, 0xb2, 0xbc, + 0x20, 0x68, 0xc8, 0x81, 0x93, 0xa3, 0xc0, 0x29, 0x97, 0x77, 0x32, 0x0d, 0x8e, 0x83, 0xb7, 0x99, 0x85, 0xc4, 0x1f, + 0xe4, 0x6d, 0xe8, 0xc8, 0x9c, 0xfd, 0xa0, 0x4d, 0x7f, 0xd4, 0x54, 0x85, 0x59, 0xd4, 0x43, 0x24, 0x03, 0x93, 0xee, + 0xde, 0x36, 0x06, 0x1d, 0x1f, 0xd7, 0x35, 0x6c, 0xee, 0x23, 0x0c, 0xae, 0x90, 0x08, 0xcd, 0xb1, 0x90, 0x3c, 0x03, + 0x9f, 0xe2, 0x61, 0x93, 0xa7, 0xcc, 0xdd, 0x8e, 0x89, 0xe3, 0xed, 0xe7, 0x9a, 0x26, 0x7b, 0xd9, 0xab, 0x7a, 0xf2, + 0x14, 0xb5, 0x5f, 0xb5, 0x6c, 0x46, 0x5a, 0xae, 0x79, 0x37, 0xf7, 0x30, 0x7a, 0x3e, 0xc5, 0x03, 0x3b, 0x08, 0xdc, + 0x19, 0xb1, 0x38, 0xc6, 0xf9, 0xbd, 0x55, 0x3c, 0x8c, 0xd1, 0x75, 0x19, 0x60, 0xd4, 0x06, 0xa2, 0x0f, 0x82, 0xf8, + 0x3e, 0x3b, 0x60, 0xdd, 0x39, 0xb0, 0x78, 0x63, 0x7a, 0xdc, 0x26, 0xe1, 0xb4, 0xd4, 0x27, 0xe3, 0x43, 0x36, 0x07, + 0x24, 0x54, 0x8a, 0x39, 0x0b, 0x42, 0x34, 0x01, 0x62, 0x0e, 0x29, 0xc9, 0x5e, 0xed, 0x03, 0x28, 0x62, 0x2e, 0x54, + 0x2e, 0x9a, 0x83, 0x1e, 0x10, 0x82, 0x1c, 0x66, 0x6c, 0xff, 0x31, 0x7e, 0x0c, 0x0f, 0x77, 0x58, 0xc8, 0x32, 0xe7, + 0x0d, 0x2e, 0xf2, 0xfd, 0x57, 0xc0, 0x5c, 0xba, 0x7a, 0xab, 0xbe, 0xd3, 0x13, 0xa4, 0xf4, 0x3e, 0x4b, 0x95, 0x5a, + 0x45, 0xee, 0x62, 0x95, 0x10, 0x5e, 0x17, 0x69, 0x34, 0x50, 0xd2, 0xdd, 0xa1, 0x1f, 0x7e, 0x05, 0x11, 0xd3, 0x17, + 0x12, 0xf0, 0x27, 0xf2, 0x03, 0xb1, 0xe0, 0xf5, 0x86, 0xa2, 0x30, 0x20, 0x7a, 0x0c, 0xb5, 0xf0, 0x0c, 0x95, 0x8a, + 0x93, 0xac, 0xe0, 0xee, 0x30, 0x4a, 0xd9, 0x3f, 0x4e, 0xe4, 0xc7, 0xaa, 0x3a, 0x76, 0x69, 0xd3, 0x5d, 0xc5, 0x6f, + 0x4b, 0xf6, 0x02, 0x88, 0x99, 0x2a, 0x3b, 0x53, 0x25, 0x22, 0x5f, 0x17, 0x88, 0xa0, 0x24, 0x3d, 0x4b, 0x76, 0xaf, + 0x86, 0xcf, 0xac, 0x62, 0x47, 0x9c, 0xd9, 0x25, 0x87, 0x80, 0xdf, 0x39, 0x99, 0xd8, 0xf4, 0xc1, 0xde, 0x79, 0xa0, + 0x4d, 0x1f, 0xa4, 0xc5, 0x5f, 0x16, 0x24, 0xf2, 0x0f, 0x5d, 0xf7, 0x16, 0x8c, 0x4d, 0x5a, 0x7f, 0x33, 0xf6, 0x20, + 0x6c, 0x8f, 0xb9, 0x79, 0x3b, 0x72, 0xcd, 0x7c, 0x89, 0x51, 0xee, 0xcd, 0xe9, 0x34, 0x7b, 0x9b, 0x61, 0x57, 0x37, + 0x7a, 0xd0, 0xac, 0x46, 0xfa, 0xe2, 0x27, 0xaa, 0xea, 0xe9, 0x06, 0x50, 0xef, 0xfc, 0xf4, 0xe9, 0xe8, 0xcb, 0x30, + 0x5b, 0x43, 0x72, 0x98, 0x30, 0x2f, 0xaa, 0xb1, 0xe7, 0xfa, 0x0c, 0xc1, 0xac, 0xa4, 0x7d, 0x07, 0x66, 0xe9, 0xb7, + 0x4c, 0x24, 0x87, 0x3e, 0xc5, 0xbe, 0x65, 0x45, 0x30, 0x48, 0xe7, 0xae, 0x46, 0xc5, 0x71, 0x16, 0xfa, 0x68, 0xda, + 0x72, 0x5f, 0xd4, 0xc8, 0x6d, 0x89, 0xd3, 0xe7, 0x72, 0xce, 0x40, 0xd8, 0x2f, 0xee, 0x38, 0xb3, 0xbe, 0xf3, 0x0e, + 0x49, 0x6b, 0x3d, 0x43, 0xbf, 0xa6, 0xf5, 0xd2, 0xad, 0xff, 0x3e, 0xf2, 0x56, 0xa6, 0x1d, 0x56, 0xcb, 0x39, 0x4d, + 0x4f, 0x55, 0xd9, 0x1b, 0x3c, 0x29, 0x03, 0x94, 0x2c, 0xa0, 0xbb, 0xac, 0x2d, 0x77, 0x13, 0xa0, 0xfe, 0x3a, 0xd2, + 0xb5, 0xfe, 0xce, 0x0a, 0x18, 0x14, 0x81, 0xda, 0x7e, 0x95, 0xf3, 0xa4, 0xbd, 0x12, 0x1f, 0x7f, 0xaf, 0x28, 0x36, + 0x5b, 0x9e, 0xbc, 0x15, 0xc8, 0x44, 0x3f, 0x0d, 0xcf, 0x9d, 0x9f, 0xab, 0x59, 0x98, 0x98, 0x4f, 0x97, 0x96, 0xdf, + 0xe3, 0x43, 0x77, 0x01, 0xad, 0xf7, 0x19, 0x21, 0x8d, 0xf9, 0x3f, 0xc7, 0x2c, 0x4b, 0xbc, 0x42, 0xb3, 0x7c, 0x1b, + 0xe0, 0x98, 0x0e, 0x4f, 0x49, 0xe3, 0x39, 0x0e, 0x28, 0x74, 0x83, 0x52, 0x6f, 0x37, 0x43, 0x2d, 0xc9, 0xc3, 0x42, + 0x41, 0x26, 0xfd, 0x88, 0xe6, 0x51, 0x76, 0x24, 0x80, 0x91, 0x69, 0xf5, 0xb7, 0xb9, 0xb6, 0xc8, 0xa3, 0x56, 0xfb, + 0x55, 0xe1, 0x5e, 0x9f, 0x45, 0xa3, 0xff, 0x6e, 0x06, 0x9c, 0x58, 0x1b, 0xb2, 0x37, 0x01, 0xd3, 0x88, 0x62, 0x8a, + 0x82, 0x1f, 0x0b, 0x92, 0x42, 0xa5, 0xe2, 0x5d, 0xd8, 0x22, 0x2c, 0x5c, 0x6a, 0x69, 0x19, 0x6b, 0xe2, 0x79, 0x0b, + 0xd0, 0xd1, 0xfe, 0xeb, 0xe2, 0xbb, 0xec, 0x99, 0xc1, 0x28, 0x29, 0xf7, 0x18, 0x8f, 0x04, 0xf5, 0x38, 0x2b, 0x01, + 0xfb, 0x2d, 0x84, 0xf8, 0x4a, 0x50, 0x93, 0x26, 0x75, 0x17, 0xc1, 0xe9, 0x36, 0x14, 0x70, 0x19, 0xad, 0x35, 0x12, + 0x34, 0x7c, 0x77, 0x92, 0x16, 0xb0, 0x2a, 0x78, 0x2f, 0x71, 0xc9, 0x8f, 0x81, 0x99, 0x8a, 0xee, 0xf0, 0x07, 0xd3, + 0xc7, 0x3b, 0xca, 0xf3, 0xb2, 0xd3, 0xba, 0xf6, 0x6e, 0xc3, 0x20, 0x8c, 0x18, 0x9f, 0x19, 0xe8, 0xc8, 0x5e, 0x0f, + 0xd8, 0x92, 0xc9, 0x18, 0x1b, 0xf0, 0x84, 0x28, 0xc8, 0x68, 0x9d, 0x8f, 0x2c, 0x5f, 0xec, 0xeb, 0x1e, 0x06, 0x27, + 0x64, 0x6c, 0x1c, 0x81, 0x1b, 0x35, 0x20, 0x43, 0xc2, 0x2c, 0xe1, 0xc7, 0x1e, 0xe1, 0x58, 0x3b, 0xf8, 0xaf, 0xb4, + 0x01, 0x05, 0xe4, 0x68, 0x4f, 0x0a, 0x49, 0xe6, 0x31, 0xcc, 0x1a, 0x14, 0x3e, 0x22, 0x43, 0x99, 0x93, 0xff, 0x1c, + 0x4b, 0x8a, 0x0d, 0xc7, 0xb1, 0x18, 0x99, 0x75, 0x1c, 0x7f, 0xea, 0xcc, 0x6f, 0x1b, 0xde, 0x83, 0x2a, 0x7a, 0xba, + 0x0e, 0x1e, 0x42, 0x29, 0x42, 0xb9, 0x99, 0x09, 0xe5, 0x47, 0x58, 0x74, 0x67, 0xb4, 0x01, 0xad, 0x1f, 0xa3, 0x47, + 0xbe, 0x7e, 0x83, 0x93, 0xcb, 0x50, 0x81, 0x75, 0xf1, 0xc3, 0x0f, 0xc4, 0xf4, 0xd9, 0x3b, 0x16, 0x6b, 0xe5, 0x6c, + 0x4e, 0x7d, 0xc9, 0xd0, 0x05, 0x5f, 0xa7, 0xeb, 0x13, 0xef, 0x95, 0x09, 0x52, 0xb3, 0xb0, 0x5a, 0x27, 0x36, 0x91, + 0x49, 0x8b, 0xd3, 0xe4, 0xdd, 0xfc, 0xe5, 0x69, 0x36, 0xf1, 0xca, 0xa5, 0xc0, 0xa4, 0x67, 0x51, 0x25, 0x36, 0x32, + 0xd3, 0x65, 0xc3, 0xbf, 0x1c, 0xe5, 0xf3, 0x6c, 0xa8, 0x67, 0x9e, 0x5f, 0xd0, 0x8d, 0xfb, 0xc3, 0x2c, 0x12, 0x6a, + 0x76, 0x5b, 0xe7, 0xcc, 0x4e, 0xb5, 0xcd, 0x7f, 0xb0, 0x73, 0xfb, 0xd8, 0xf7, 0x99, 0x8f, 0x64, 0x96, 0xae, 0x28, + 0x09, 0xbb, 0xe3, 0x21, 0xe9, 0x14, 0x93, 0x15, 0x67, 0x4e, 0x03, 0xf5, 0x5c, 0x16, 0xe7, 0x35, 0xb9, 0xbb, 0x80, + 0xb7, 0x82, 0x29, 0x03, 0x24, 0xd5, 0xf1, 0x45, 0x70, 0x55, 0x11, 0x38, 0x35, 0xb5, 0x50, 0x45, 0xf1, 0xb8, 0x33, + 0xdb, 0x2d, 0xa0, 0xea, 0xa7, 0x6a, 0x71, 0x69, 0xa4, 0xa2, 0x84, 0x67, 0xb2, 0x15, 0xa6, 0x40, 0xa6, 0x2b, 0x27, + 0xcd, 0x49, 0xac, 0x70, 0xd0, 0xcf, 0x22, 0x27, 0xd9, 0x8b, 0xaa, 0x76, 0xc3, 0x4b, 0x5b, 0x6b, 0x56, 0x18, 0xed, + 0x6c, 0xb5, 0x48, 0x27, 0x52, 0xb1, 0x7d, 0xa8, 0x1e, 0x0a, 0xf7, 0xdd, 0x04, 0x56, 0x6a, 0xa4, 0xb2, 0xfc, 0x75, + 0xa4, 0x86, 0x47, 0xfc, 0xb5, 0x49, 0x07, 0x49, 0xc3, 0x86, 0x1d, 0x6d, 0x6e, 0x9b, 0xcb, 0xa0, 0x58, 0xd6, 0x38, + 0x8c, 0x4a, 0x43, 0xb7, 0x11, 0xf9, 0x0a, 0xe5, 0x91, 0x7d, 0x13, 0x79, 0x43, 0x2c, 0xd9, 0x43, 0xbc, 0x46, 0xc2, + 0x24, 0xa5, 0x8f, 0x62, 0x8b, 0xc6, 0x85, 0xa2, 0x5b, 0xa6, 0xdd, 0xa6, 0x3b, 0x57, 0x49, 0x2e, 0xd3, 0x53, 0xcf, + 0xb3, 0x40, 0x29, 0xac, 0x44, 0x44, 0x42, 0xc8, 0x98, 0x67, 0x6f, 0xfc, 0xd4, 0xf4, 0xdc, 0x23, 0x4a, 0x34, 0xfb, + 0x02, 0xef, 0x85, 0x3e, 0x88, 0x11, 0x1f, 0x9b, 0x90, 0x63, 0xf8, 0xca, 0xe9, 0xf0, 0xbd, 0x6d, 0x4e, 0xfe, 0x68, + 0xe7, 0xe3, 0x89, 0x32, 0x25, 0xaa, 0x76, 0xf1, 0xb4, 0x81, 0xc4, 0x1a, 0x20, 0x9e, 0xa5, 0x63, 0x09, 0x4a, 0xa3, + 0xc7, 0x60, 0xe7, 0xf3, 0x6a, 0x97, 0x85, 0xda, 0xf4, 0x74, 0x97, 0xa5, 0x09, 0x70, 0xc1, 0x8e, 0xd2, 0xdb, 0xc4, + 0xee, 0xee, 0x2f, 0x1c, 0xd0, 0xdd, 0x37, 0x19, 0xd9, 0x68, 0x76, 0xd9, 0x90, 0xb0, 0xc4, 0xbf, 0x8b, 0xa6, 0x8d, + 0x25, 0x52, 0x21, 0xde, 0xd8, 0x37, 0x80, 0x99, 0x4c, 0x7b, 0xa6, 0xd1, 0x03, 0x91, 0xe2, 0x37, 0x60, 0x3b, 0x50, + 0x09, 0x0d, 0x32, 0x12, 0xf5, 0x93, 0x08, 0x35, 0x31, 0xee, 0x71, 0xfe, 0xe3, 0x9a, 0x72, 0x74, 0x90, 0x40, 0x8e, + 0x07, 0xbb, 0x67, 0x9d, 0x11, 0xc5, 0x59, 0x4f, 0x5a, 0xd5, 0x3c, 0x73, 0xd1, 0xa1, 0x74, 0x66, 0x1f, 0x28, 0xd1, + 0x13, 0x45, 0x7f, 0xb9, 0x1d, 0xe9, 0x47, 0x80, 0xc1, 0xb0, 0x2b, 0xcc, 0x7f, 0x32, 0x9d, 0x70, 0x41, 0x44, 0x3d, + 0x77, 0x57, 0xbd, 0x1d, 0x16, 0x3b, 0x93, 0xc9, 0xf8, 0xe4, 0x97, 0x81, 0xbb, 0x6f, 0x87, 0x88, 0xb4, 0x89, 0xdb, + 0x18, 0xf9, 0x64, 0x12, 0x30, 0xdb, 0xd5, 0x8d, 0x6a, 0x46, 0x3a, 0x26, 0x11, 0x53, 0xeb, 0x37, 0xf6, 0x79, 0x9b, + 0x5e, 0xbb, 0x07, 0xff, 0xfd, 0x06, 0x4d, 0x90, 0x7a, 0xa6, 0x8a, 0xc5, 0xfb, 0x70, 0xe7, 0xa0, 0xfb, 0xcb, 0xf6, + 0x59, 0x5e, 0xf6, 0xb0, 0x14, 0x32, 0xb4, 0x4a, 0xd4, 0xf9, 0x58, 0x3b, 0x8c, 0x74, 0x7e, 0xa6, 0x61, 0xb9, 0xd6, + 0x7f, 0xaa, 0x3a, 0x98, 0xf5, 0x9b, 0xc1, 0x49, 0x65, 0xb1, 0xa8, 0xa6, 0x92, 0x0b, 0xef, 0xe0, 0x6b, 0x38, 0xb7, + 0x86, 0x7e, 0xed, 0xa6, 0xf2, 0xd4, 0x55, 0xe1, 0xb2, 0xef, 0xa2, 0x9f, 0xb0, 0xa9, 0xbf, 0x8a, 0x41, 0xf9, 0xeb, + 0xe0, 0x14, 0x54, 0x6f, 0xc8, 0x1b, 0x23, 0x75, 0x17, 0xef, 0x17, 0x52, 0x62, 0x72, 0xc4, 0x3f, 0x0a, 0x86, 0x46, + 0x69, 0x5b, 0xaa, 0x63, 0xec, 0x2c, 0x98, 0xec, 0xac, 0x76, 0x5b, 0xff, 0x8e, 0x82, 0x27, 0xda, 0xce, 0xef, 0x7e, + 0x24, 0x35, 0x0f, 0xb0, 0xce, 0xfa, 0xf2, 0x23, 0x70, 0x5e, 0xdb, 0x8f, 0xd4, 0xf3, 0xa1, 0x98, 0x9e, 0x68, 0x7d, + 0xcc, 0xda, 0xf3, 0x6c, 0xc1, 0x9e, 0xef, 0x59, 0x08, 0x35, 0x52, 0x67, 0xfc, 0xc0, 0xcc, 0xef, 0x42, 0x67, 0x3b, + 0xec, 0xb2, 0x63, 0xad, 0x79, 0xbf, 0x32, 0x63, 0xa5, 0xca, 0x7a, 0xe7, 0xd8, 0x91, 0x6b, 0x8d, 0x27, 0x63, 0x18, + 0x48, 0x1a, 0xab, 0x9b, 0xe1, 0xd4, 0x09, 0x95, 0x6f, 0x66, 0x41, 0xc7, 0x4e, 0xa2, 0x9b, 0xe5, 0x22, 0x4a, 0xa4, + 0xc8, 0xdf, 0x06, 0x99, 0x62, 0x38, 0x64, 0xc2, 0xa3, 0xb8, 0x37, 0x41, 0xc2, 0xbc, 0x56, 0x52, 0x26, 0x56, 0x3b, + 0xba, 0x5e, 0xa5, 0x47, 0xc1, 0xc1, 0x9a, 0x2a, 0x69, 0x33, 0x10, 0x75, 0xa9, 0xfb, 0xb0, 0xa6, 0xfb, 0x43, 0xa3, + 0x3a, 0xd8, 0x5f, 0x79, 0x2b, 0xb5, 0x68, 0xfe, 0x45, 0x0d, 0xc7, 0x6a, 0x84, 0xcd, 0x0c, 0x78, 0x1c, 0xfd, 0x1f, + 0x49, 0xa1, 0x43, 0xd7, 0x02, 0xa0, 0xf6, 0xc7, 0xf2, 0x06, 0x45, 0x31, 0x02, 0xb4, 0x1f, 0x56, 0xde, 0x48, 0x7d, + 0xca, 0x1f, 0xcc, 0xae, 0xdb, 0x8e, 0x2c, 0x17, 0xc1, 0x58, 0x93, 0x6d, 0x00, 0x08, 0xcb, 0x17, 0xb0, 0x81, 0x28, + 0x1a, 0x45, 0xd9, 0xd2, 0x3b, 0xec, 0x16, 0x2f, 0x21, 0x5a, 0xf3, 0x98, 0x50, 0xf4, 0x0d, 0xa9, 0xa4, 0xb2, 0xac, + 0xf0, 0xfd, 0xab, 0x0b, 0xe6, 0x5a, 0x18, 0xbd, 0xb5, 0xe7, 0x56, 0xb6, 0xe8, 0xbc, 0xbb, 0xab, 0xe9, 0x9f, 0xda, + 0xd5, 0xf0, 0x91, 0x6d, 0xc0, 0x8c, 0x2c, 0x6c, 0xc9, 0x0f, 0x4f, 0xeb, 0x26, 0x1c, 0xff, 0x28, 0x2a, 0x46, 0x85, + 0x2b, 0x08, 0x16, 0xb5, 0x46, 0x9c, 0x92, 0x7f, 0x1c, 0x00, 0x05, 0xda, 0xc3, 0x92, 0x08, 0x89, 0x51, 0x95, 0xa1, + 0x12, 0xd9, 0x53, 0xf1, 0xab, 0x36, 0x90, 0xc1, 0x24, 0x1c, 0x4a, 0x06, 0x6e, 0x6a, 0xd7, 0x9c, 0x98, 0x9d, 0xb9, + 0xf5, 0x1f, 0xb7, 0x8c, 0xd5, 0x30, 0x61, 0x89, 0xfa, 0x14, 0x66, 0x7a, 0x59, 0xf5, 0x08, 0x8f, 0xa6, 0x85, 0xee, + 0x21, 0x48, 0x2d, 0x8b, 0x84, 0xdf, 0xb3, 0x8e, 0x50, 0x23, 0x98, 0x90, 0xdd, 0xb3, 0x32, 0x0e, 0x21, 0xd7, 0xe9, + 0x71, 0xc6, 0x0b, 0x50, 0xcb, 0x7a, 0x9d, 0xb1, 0xcc, 0x91, 0xd7, 0x82, 0x2e, 0xc9, 0x05, 0xd5, 0x6b, 0x94, 0x0d, + 0x33, 0xae, 0x3f, 0x97, 0x44, 0x23, 0xdf, 0xa0, 0xa1, 0x76, 0xe4, 0x39, 0xf1, 0x79, 0xce, 0xd1, 0x14, 0xc9, 0x1d, + 0x3d, 0x83, 0x56, 0x33, 0x5b, 0x73, 0xd3, 0x9b, 0xaf, 0x36, 0x23, 0x6c, 0x77, 0x3c, 0x4e, 0x99, 0x26, 0x4e, 0x06, + 0xe7, 0x47, 0xa0, 0xcd, 0x9d, 0x96, 0xdc, 0xb8, 0xf8, 0x3f, 0x44, 0x1e, 0xdd, 0x3c, 0x9e, 0x23, 0x98, 0xcb, 0x6d, + 0x8c, 0xe2, 0xe1, 0xe6, 0xd8, 0x05, 0x36, 0xec, 0xff, 0x13, 0x17, 0x5d, 0x13, 0xf1, 0xe2, 0x50, 0x2b, 0x51, 0x09, + 0x71, 0x62, 0x7d, 0xb6, 0x0f, 0xa4, 0xf5, 0x88, 0x84, 0x13, 0x65, 0x9d, 0xcd, 0xc2, 0x38, 0xd6, 0x65, 0xf0, 0xe1, + 0x07, 0x6a, 0x09, 0x41, 0x60, 0x98, 0xbf, 0xc4, 0xfe, 0x04, 0x56, 0x5c, 0x88, 0x42, 0x19, 0xf1, 0xc2, 0xbf, 0xfa, + 0x8c, 0xcf, 0x69, 0x56, 0x56, 0xba, 0xc6, 0xe5, 0xc8, 0x4c, 0xad, 0x61, 0x4e, 0x9e, 0xb0, 0x69, 0x8a, 0x58, 0x4c, + 0xcf, 0x0d, 0x53, 0xcc, 0x08, 0x68, 0x68, 0xce, 0xb9, 0x23, 0x65, 0x4d, 0x82, 0x97, 0x31, 0x29, 0x96, 0x02, 0x74, + 0x85, 0x2e, 0x33, 0xbb, 0xed, 0x0c, 0xe3, 0x60, 0xc8, 0xcd, 0x02, 0x40, 0xb8, 0x12, 0x41, 0x2d, 0x02, 0xcf, 0x8a, + 0x7d, 0x25, 0x32, 0x07, 0x73, 0x91, 0xa3, 0xde, 0xeb, 0xa4, 0xbf, 0x41, 0xc2, 0x25, 0xbc, 0x95, 0x02, 0x27, 0x03, + 0xba, 0x4c, 0xa4, 0x40, 0xf3, 0x12, 0x21, 0xc6, 0x1a, 0x90, 0xd4, 0x36, 0x7e, 0xb9, 0x88, 0x70, 0xcf, 0x07, 0xd9, + 0x70, 0xd6, 0x0d, 0x02, 0x20, 0x8f, 0xf2, 0xfa, 0x3b, 0x8b, 0x74, 0x87, 0x39, 0x01, 0x89, 0x0b, 0x8e, 0x91, 0x13, + 0xda, 0x39, 0x35, 0xd8, 0x32, 0x17, 0xa3, 0x8c, 0xdb, 0x1a, 0x25, 0x4b, 0xe1, 0x6c, 0x23, 0xed, 0x36, 0x72, 0x46, + 0x32, 0x50, 0xeb, 0x32, 0x09, 0x3b, 0x74, 0xed, 0xc9, 0x54, 0x6e, 0x07, 0x78, 0x67, 0xcd, 0x40, 0x9f, 0x6e, 0x3d, + 0x1f, 0xfb, 0x9f, 0x36, 0x57, 0xc9, 0xf4, 0x7d, 0x93, 0x31, 0x62, 0x2e, 0xd1, 0x97, 0x1c, 0x66, 0x9f, 0xf6, 0xfb, + 0x7c, 0x07, 0x8b, 0xf5, 0x55, 0xfc, 0x55, 0xc5, 0x46, 0xfd, 0xd4, 0x7a, 0xc1, 0x24, 0x49, 0x2c, 0xb9, 0x35, 0x28, + 0x29, 0xa8, 0xcc, 0xdb, 0xa8, 0x21, 0x2b, 0xa6, 0xb5, 0x66, 0x3b, 0xf1, 0xbf, 0x73, 0xc5, 0xcc, 0xc4, 0xc0, 0x8f, + 0x71, 0xc4, 0x3e, 0x79, 0xc4, 0xc6, 0xdb, 0xdb, 0x0f, 0x9c, 0xa1, 0x63, 0xf2, 0x00, 0x81, 0x42, 0x64, 0x5e, 0xba, + 0xc4, 0x9c, 0x5b, 0x33, 0x6b, 0xd6, 0xd4, 0xca, 0x7f, 0x66, 0xd7, 0xfa, 0xd0, 0xd8, 0x27, 0xc2, 0xd7, 0xd9, 0xda, + 0xed, 0xd8, 0x87, 0x50, 0xa8, 0x22, 0x5f, 0x48, 0x1d, 0xcc, 0x5c, 0xbc, 0xa9, 0x0c, 0x6e, 0x7a, 0xfb, 0x28, 0x09, + 0x30, 0x39, 0x1b, 0xfd, 0x44, 0xad, 0x08, 0x3e, 0x7b, 0x44, 0xf8, 0x62, 0xbb, 0x2d, 0xa2, 0xe0, 0xca, 0x68, 0xc6, + 0xbb, 0x8c, 0x7e, 0x72, 0xa3, 0xc5, 0x2f, 0xd3, 0xb2, 0x3c, 0x7b, 0x6a, 0x3b, 0x85, 0xf6, 0x71, 0x10, 0xbb, 0x22, + 0x68, 0x6b, 0x63, 0x41, 0x90, 0x35, 0x75, 0xd9, 0xa4, 0x22, 0xc5, 0x6f, 0xad, 0x93, 0xce, 0xeb, 0xc4, 0x33, 0xdb, + 0xe5, 0x3e, 0x24, 0x62, 0x04, 0x6e, 0x8b, 0x6e, 0xb7, 0x41, 0x54, 0x70, 0xe9, 0xe8, 0x64, 0x82, 0x47, 0x5d, 0xe2, + 0xa4, 0xda, 0xf5, 0x76, 0xdc, 0xfe, 0xd9, 0x1c, 0xf6, 0x03, 0x50, 0xe9, 0x3a, 0xd0, 0x7f, 0x4b, 0xaf, 0x64, 0x8e, + 0x3d, 0xec, 0xcd, 0x41, 0x73, 0x0b, 0xf4, 0x53, 0xb5, 0x89, 0xa2, 0xee, 0x0b, 0xfa, 0xcc, 0x38, 0xfe, 0x2f, 0x55, + 0x56, 0x30, 0x14, 0x26, 0x33, 0xd1, 0xac, 0xb6, 0x20, 0x9d, 0x85, 0x41, 0xed, 0x87, 0xb7, 0x1a, 0x39, 0x60, 0x8b, + 0x79, 0xc4, 0xa1, 0x1e, 0x34, 0x82, 0x97, 0x50, 0x20, 0xcc, 0xbd, 0x33, 0x34, 0x06, 0x3d, 0x28, 0x0f, 0x90, 0x81, + 0x62, 0xd0, 0xb2, 0x14, 0x1a, 0xda, 0x84, 0x54, 0xbb, 0xdf, 0x1f, 0xbd, 0x3e, 0xf4, 0x7b, 0x35, 0x8a, 0x68, 0xd4, + 0x3b, 0x07, 0x09, 0x28, 0x7a, 0xc5, 0x81, 0x0c, 0x94, 0x37, 0x4b, 0x62, 0xc4, 0x32, 0x1e, 0x07, 0xb9, 0x3a, 0x78, + 0xbc, 0x52, 0x72, 0x3c, 0x2b, 0x84, 0x1e, 0x03, 0x18, 0xd6, 0x3d, 0x70, 0x2f, 0xbb, 0x15, 0x2c, 0x02, 0x9e, 0xd5, + 0x2b, 0xea, 0xd9, 0x6a, 0x3e, 0xd4, 0xbf, 0x97, 0x17, 0xef, 0xb7, 0xb4, 0x9f, 0x4a, 0xec, 0xb1, 0xac, 0xa9, 0x02, + 0x1f, 0xfe, 0xfc, 0x29, 0xf3, 0xb1, 0x58, 0xa4, 0x4f, 0x9f, 0x5c, 0xc3, 0x09, 0xd1, 0x75, 0xc9, 0xbf, 0x70, 0x71, + 0x6c, 0x53, 0x40, 0x0d, 0xa7, 0x61, 0xe7, 0x8a, 0xf0, 0x38, 0x61, 0x0d, 0x17, 0x45, 0x38, 0xec, 0xe0, 0x60, 0x23, + 0x8c, 0x6e, 0xa8, 0x21, 0x96, 0xf4, 0x4e, 0x7c, 0x3b, 0xc0, 0x25, 0xf8, 0x79, 0xa1, 0x97, 0x49, 0x80, 0xf8, 0x63, + 0x8b, 0xc1, 0x04, 0xb9, 0xc4, 0xda, 0x6c, 0xca, 0x6e, 0xf5, 0x5e, 0x6b, 0xda, 0x79, 0x9a, 0x6e, 0xee, 0xad, 0xd9, + 0x9c, 0xa8, 0x3c, 0x70, 0x92, 0x51, 0x5c, 0x90, 0x1e, 0xd5, 0x33, 0xf9, 0x2f, 0x8e, 0x13, 0x40, 0x66, 0xf1, 0xe0, + 0x5e, 0x09, 0x8c, 0xed, 0x2b, 0x5d, 0x8b, 0xf8, 0x97, 0xc8, 0xf8, 0xd9, 0x68, 0xc6, 0xec, 0x15, 0x96, 0x5c, 0x6d, + 0xa8, 0x0d, 0x07, 0xcc, 0x45, 0x2f, 0x15, 0x9d, 0x63, 0x8c, 0x5a, 0xd8, 0x8c, 0x5f, 0x8c, 0xdd, 0x42, 0xa4, 0x51, + 0x8d, 0xd9, 0xf6, 0x6b, 0x4b, 0x74, 0xdf, 0xe3, 0x89, 0x24, 0x68, 0x5e, 0x12, 0x50, 0x80, 0x5d, 0x4c, 0x30, 0x24, + 0xd7, 0x30, 0x8c, 0x69, 0x86, 0xe7, 0x29, 0xd4, 0xb5, 0x9e, 0x1a, 0x95, 0x97, 0xba, 0xcb, 0xda, 0x5c, 0xb6, 0x9b, + 0x3c, 0xee, 0x51, 0x90, 0x38, 0x6a, 0x9c, 0xa1, 0x61, 0x56, 0x3d, 0x43, 0xca, 0xb0, 0x84, 0x48, 0x2b, 0x2e, 0xf2, + 0xb6, 0x76, 0x99, 0xc2, 0x40, 0xde, 0x89, 0x6e, 0x3a, 0xa7, 0x42, 0x04, 0xbb, 0x8b, 0x8a, 0x84, 0x4d, 0xdb, 0xb2, + 0x89, 0x16, 0x3a, 0xf7, 0x6d, 0x28, 0x74, 0x09, 0xf1, 0x43, 0xb6, 0x17, 0xee, 0x5e, 0x22, 0xf6, 0x10, 0xc6, 0xe6, + 0x88, 0x2d, 0x3e, 0xea, 0x25, 0xad, 0x97, 0x43, 0x42, 0x70, 0xb6, 0x59, 0xfa, 0xfc, 0x77, 0x6c, 0xe8, 0xca, 0xcb, + 0x0d, 0x8d, 0xca, 0x8e, 0xae, 0xaf, 0xae, 0x5a, 0x25, 0x16, 0xa9, 0xc6, 0x1c, 0x72, 0xe2, 0xa1, 0x45, 0xe7, 0x01, + 0x6d, 0xe2, 0xac, 0x9c, 0x11, 0x92, 0x3b, 0x6b, 0x51, 0xe8, 0x1a, 0xec, 0xbd, 0x0b, 0x00, 0x3b, 0x36, 0x99, 0xea, + 0xc5, 0xca, 0x53, 0x92, 0x60, 0xe8, 0x56, 0xe8, 0x9d, 0xaf, 0x72, 0x07, 0x0a, 0x31, 0xac, 0x03, 0x2c, 0x9c, 0x95, + 0xcc, 0x09, 0xdb, 0x87, 0xf5, 0xf8, 0x31, 0xaa, 0x2d, 0x60, 0x7c, 0x08, 0xa1, 0xbe, 0xb7, 0x71, 0x1b, 0x8a, 0x8e, + 0xce, 0x68, 0x72, 0x97, 0x13, 0x64, 0xd0, 0x77, 0xae, 0x94, 0x4c, 0xf1, 0x84, 0xbc, 0x9c, 0x39, 0x52, 0xa8, 0xf2, + 0xa6, 0x55, 0xfa, 0x62, 0xfb, 0xf6, 0x4b, 0x1f, 0x61, 0x5d, 0x23, 0x2e, 0x15, 0x63, 0x3d, 0x20, 0xfb, 0xee, 0x28, + 0x5a, 0xd3, 0x5e, 0x3c, 0x5d, 0x11, 0xcf, 0xf1, 0x26, 0x1c, 0xe1, 0x4f, 0x9f, 0xaf, 0xab, 0xd5, 0x79, 0x40, 0xb9, + 0xf7, 0x66, 0xc1, 0x31, 0xea, 0x1d, 0x97, 0x88, 0x60, 0xd2, 0x39, 0xdd, 0x69, 0x33, 0xa8, 0x26, 0x22, 0x33, 0x7c, + 0xb8, 0xf4, 0xdb, 0xfd, 0xaf, 0x20, 0x58, 0x77, 0x11, 0x2e, 0xdc, 0xd2, 0x20, 0x0e, 0x59, 0x8a, 0x90, 0x76, 0x45, + 0x30, 0xd2, 0x51, 0x41, 0x6c, 0xc5, 0x4e, 0x8a, 0x3c, 0x5f, 0x43, 0x20, 0xe2, 0x1c, 0x5c, 0x3e, 0xb3, 0x0a, 0x2f, + 0xaa, 0xd7, 0x3f, 0x37, 0x48, 0xe9, 0xb2, 0x3a, 0xe8, 0x7f, 0x9d, 0x2c, 0xfc, 0xe4, 0xe0, 0xc0, 0xcb, 0xc8, 0xda, + 0xda, 0xec, 0xb4, 0xa9, 0xde, 0x0a, 0x76, 0xdc, 0xce, 0xf5, 0xbe, 0x7e, 0x03, 0x4a, 0xa3, 0xad, 0xa8, 0xd9, 0x6d, + 0xca, 0x4c, 0x8d, 0xe1, 0x31, 0xab, 0x45, 0x03, 0x5c, 0xb8, 0xc3, 0xfe, 0x64, 0xc0, 0xde, 0xc1, 0x54, 0xf4, 0xbc, + 0x6f, 0xff, 0xec, 0x64, 0x86, 0x84, 0xe9, 0x84, 0x43, 0xee, 0xc0, 0x67, 0x4c, 0x4f, 0x27, 0x7d, 0x2f, 0x10, 0xbf, + 0x8a, 0x24, 0x9b, 0xf0, 0xb7, 0x0a, 0xef, 0x69, 0x64, 0x6c, 0x09, 0x19, 0xdd, 0x16, 0x95, 0x22, 0x52, 0x4b, 0x83, + 0x81, 0x31, 0x8a, 0xf9, 0x94, 0x68, 0x26, 0x96, 0xdd, 0x61, 0x43, 0x62, 0x9f, 0xed, 0x39, 0x7b, 0xbb, 0x98, 0x4d, + 0x09, 0x5a, 0x56, 0x7b, 0xf1, 0x6a, 0x6d, 0xde, 0x2b, 0x8f, 0xae, 0x8f, 0x1b, 0x18, 0xb1, 0x3f, 0xb7, 0xda, 0x5b, + 0xe0, 0x41, 0x07, 0xfc, 0xf3, 0x9d, 0xe2, 0xc5, 0xad, 0xf2, 0x25, 0x04, 0x3f, 0x64, 0x9a, 0x2c, 0x81, 0x32, 0xc8, + 0xc5, 0x96, 0x0b, 0x1e, 0x48, 0x15, 0xb5, 0xdd, 0x7a, 0x8c, 0xd8, 0x3c, 0x9f, 0x7c, 0xda, 0xc1, 0xf0, 0x4c, 0x41, + 0x07, 0xfb, 0x97, 0xed, 0xfd, 0x06, 0x68, 0xdd, 0x64, 0xc8, 0xbf, 0x6b, 0xdd, 0x04, 0x19, 0xc1, 0xc7, 0xaf, 0xb6, + 0xbf, 0xb0, 0xe6, 0xd3, 0xd4, 0x76, 0x8c, 0x96, 0x41, 0xb7, 0xfc, 0x5d, 0x72, 0x0a, 0x71, 0x5d, 0xed, 0x01, 0x7c, + 0xba, 0x8c, 0x01, 0x5f, 0xa2, 0x6f, 0x90, 0x1a, 0x40, 0xe4, 0xb7, 0x1f, 0xf5, 0xe3, 0xa7, 0xe6, 0x66, 0xf5, 0x43, + 0xc7, 0x86, 0x12, 0x31, 0x38, 0xac, 0x42, 0xb6, 0xe3, 0x00, 0xa0, 0xe2, 0x61, 0xe5, 0x88, 0x0e, 0x9a, 0x0f, 0x05, + 0xfb, 0x14, 0x0f, 0x3b, 0x07, 0x5f, 0xd7, 0x45, 0x91, 0x35, 0xa2, 0x24, 0x07, 0x4b, 0x25, 0xdd, 0x2f, 0x8e, 0xd2, + 0x0c, 0xaa, 0xf6, 0x04, 0x71, 0x15, 0x01, 0xc4, 0x63, 0x30, 0xba, 0xaf, 0x4b, 0xbf, 0xe7, 0x8a, 0x05, 0xe0, 0xe7, + 0x14, 0x6e, 0x63, 0x9e, 0x8f, 0x29, 0x80, 0xa0, 0xcf, 0x86, 0x06, 0x73, 0x88, 0x48, 0xc6, 0xe9, 0xec, 0x5a, 0x8c, + 0xf2, 0x32, 0xf2, 0xed, 0x88, 0xad, 0x22, 0x7f, 0xc7, 0x2a, 0x2f, 0x2e, 0xee, 0x85, 0x64, 0x17, 0xab, 0x74, 0x06, + 0x91, 0xda, 0x85, 0x99, 0x8c, 0x46, 0x47, 0xa6, 0xe9, 0x04, 0xd1, 0x5e, 0x2a, 0xa4, 0x64, 0x18, 0xe5, 0x18, 0xc5, + 0x22, 0x8e, 0x9c, 0x83, 0x93, 0x25, 0x0c, 0xc3, 0x92, 0xe0, 0xbf, 0x69, 0x40, 0xd0, 0x2b, 0x95, 0x14, 0xec, 0xa2, + 0x84, 0xb7, 0x43, 0x06, 0x0d, 0x80, 0xa5, 0xc6, 0x3b, 0x24, 0x4f, 0x35, 0xaa, 0x93, 0x73, 0xad, 0xc8, 0x70, 0x2a, + 0x75, 0x21, 0x3b, 0xc6, 0x13, 0x02, 0x89, 0x71, 0xde, 0xf9, 0x3c, 0x0f, 0x1a, 0x20, 0xf6, 0x64, 0x6a, 0x8d, 0xf4, + 0xbc, 0x62, 0x0f, 0x66, 0x5b, 0xda, 0x86, 0x46, 0x33, 0x07, 0x46, 0x02, 0x9b, 0x3f, 0x30, 0x53, 0x15, 0x53, 0xf3, + 0xc8, 0x31, 0x08, 0x43, 0x68, 0xbd, 0x95, 0xd5, 0x01, 0x21, 0xb4, 0x3c, 0x29, 0x93, 0x0c, 0xe2, 0xda, 0xf8, 0x30, + 0xea, 0x1a, 0x1f, 0x34, 0x12, 0xa0, 0x35, 0x73, 0xb5, 0xc5, 0xc7, 0xb3, 0x85, 0xc2, 0x19, 0x4b, 0x46, 0x7f, 0xb6, + 0x35, 0xb5, 0x92, 0xee, 0xe6, 0xee, 0xaf, 0xb0, 0xe5, 0xeb, 0xe4, 0x22, 0xdd, 0x2e, 0x65, 0x5b, 0x50, 0x1e, 0x68, + 0xb7, 0x9b, 0x59, 0xff, 0xfc, 0x37, 0x1f, 0x3f, 0x22, 0x74, 0x91, 0xb0, 0x0b, 0xc9, 0x2d, 0xea, 0xf8, 0x8b, 0x8f, + 0x86, 0x27, 0x63, 0xd8, 0xee, 0x0c, 0xcc, 0x1d, 0xe6, 0x39, 0x86, 0xbd, 0xc7, 0xc7, 0x31, 0x0c, 0x10, 0x93, 0xaf, + 0xa6, 0x0a, 0x13, 0x79, 0x87, 0x81, 0xca, 0x55, 0xaf, 0x1d, 0x20, 0x22, 0xce, 0xd4, 0x3e, 0x89, 0xe6, 0x9f, 0xa9, + 0xc8, 0xfb, 0x67, 0xdb, 0x13, 0x92, 0x20, 0x5f, 0xcf, 0x9a, 0xb8, 0x8e, 0x29, 0xf0, 0x00, 0xdb, 0x97, 0x58, 0x34, + 0x76, 0x97, 0x84, 0xd0, 0x42, 0x17, 0xa1, 0xa4, 0xc1, 0x87, 0x50, 0xf5, 0x6a, 0x95, 0x6c, 0x98, 0x0a, 0x0b, 0xbc, + 0xf8, 0xf4, 0x70, 0x0c, 0xef, 0x8f, 0x07, 0xca, 0x05, 0x85, 0x5c, 0x4e, 0xf0, 0x21, 0x6e, 0x1a, 0x7b, 0x06, 0x52, + 0x90, 0xf6, 0x4d, 0xe1, 0x9a, 0x9f, 0x8c, 0xac, 0x0b, 0x1d, 0x59, 0x4e, 0x4e, 0x4c, 0xf6, 0x24, 0xfc, 0x8b, 0x92, + 0x19, 0x92, 0xbc, 0x1c, 0x9c, 0xda, 0xc0, 0xd7, 0x2e, 0xe9, 0x28, 0xd7, 0xa2, 0x6d, 0xc3, 0xaf, 0x15, 0x27, 0xe8, + 0x94, 0x43, 0x37, 0xc1, 0xcb, 0x5e, 0x7d, 0x4e, 0xcd, 0x8d, 0xef, 0x95, 0xb7, 0x8b, 0xfb, 0xd7, 0xab, 0x01, 0x0e, + 0xbe, 0x40, 0x8e, 0xf7, 0xcc, 0x28, 0xce, 0xbf, 0x1d, 0xc6, 0xab, 0xe5, 0x98, 0x21, 0x30, 0x81, 0x84, 0x4c, 0x23, + 0x62, 0x1b, 0x4e, 0xf0, 0xf1, 0x43, 0x9d, 0xa3, 0x92, 0xd0, 0xd2, 0x8a, 0x83, 0xe3, 0x5c, 0x7f, 0x1b, 0x65, 0x48, + 0x29, 0xcb, 0xa5, 0x8c, 0x30, 0xc4, 0xc4, 0x01, 0x39, 0xdb, 0x95, 0xef, 0xc5, 0xe7, 0xcc, 0x33, 0x0d, 0xa4, 0x77, + 0xf1, 0x80, 0x16, 0x19, 0xf5, 0x07, 0x85, 0x1a, 0x44, 0x9a, 0x18, 0x7c, 0x46, 0x49, 0x20, 0x31, 0xc6, 0x46, 0x08, + 0x94, 0x90, 0x63, 0xeb, 0x07, 0x8b, 0x2a, 0x4c, 0x84, 0x22, 0x80, 0x96, 0x68, 0x79, 0x24, 0x28, 0xc8, 0x0c, 0x69, + 0xa4, 0xc7, 0xdc, 0x2d, 0x1d, 0x98, 0x16, 0x60, 0x4a, 0xc5, 0x23, 0x80, 0x7c, 0x32, 0x86, 0xa9, 0x88, 0x60, 0x70, + 0x57, 0x5e, 0x26, 0x0d, 0x1d, 0xd6, 0x30, 0x17, 0xcd, 0xc5, 0x94, 0x79, 0x19, 0x85, 0x72, 0x82, 0xc9, 0x55, 0x3b, + 0x21, 0xee, 0x0c, 0xa6, 0x75, 0x17, 0xf3, 0x79, 0x80, 0xd0, 0xf6, 0xd6, 0xd9, 0x14, 0x28, 0x33, 0x92, 0xd8, 0x04, + 0x11, 0x91, 0x0c, 0x76, 0x20, 0x0d, 0x45, 0x22, 0x24, 0x84, 0x4a, 0x52, 0xd0, 0x3a, 0x99, 0x13, 0x11, 0x9f, 0x56, + 0x58, 0xec, 0x83, 0xb4, 0x58, 0x22, 0x9b, 0xf7, 0xad, 0x32, 0xcc, 0x0f, 0x04, 0x85, 0x15, 0x8b, 0xac, 0x0a, 0x16, + 0x21, 0x91, 0xb0, 0x7a, 0x9d, 0x30, 0x76, 0x5e, 0x5f, 0x7c, 0x9a, 0x08, 0x4a, 0x9c, 0xd0, 0x91, 0x60, 0x1c, 0xab, + 0xa2, 0x58, 0xc9, 0x9f, 0x14, 0x39, 0xac, 0xd8, 0xf0, 0xe5, 0x55, 0xe9, 0x26, 0x91, 0x7c, 0xc7, 0xae, 0xfa, 0x95, + 0xb0, 0xfb, 0xa1, 0x9e, 0x38, 0xab, 0x44, 0x72, 0x8a, 0x6a, 0xab, 0xfb, 0x4f, 0xcf, 0x57, 0x95, 0x44, 0x79, 0xa1, + 0xa4, 0x0c, 0x7a, 0x8f, 0xdb, 0x62, 0x2f, 0x28, 0x36, 0x69, 0x76, 0xcc, 0xb7, 0xbd, 0x4a, 0xe4, 0x55, 0xc1, 0x94, + 0x2e, 0xc4, 0x12, 0x10, 0x37, 0x83, 0x65, 0x28, 0x6d, 0xcc, 0xf9, 0x07, 0x08, 0x7d, 0xf5, 0x3e, 0x2a, 0xb3, 0x1f, + 0xfd, 0x60, 0x05, 0x4d, 0x5c, 0x3f, 0xb3, 0xe6, 0x7a, 0x93, 0x46, 0x24, 0x30, 0xce, 0x42, 0x2f, 0xc5, 0xbe, 0x1a, + 0x97, 0x33, 0x57, 0x9a, 0x3d, 0xde, 0x8c, 0x56, 0x20, 0x76, 0x95, 0x86, 0x1d, 0x71, 0x3c, 0x07, 0x80, 0x74, 0x1e, + 0x85, 0x23, 0xa9, 0x14, 0xde, 0x6b, 0x81, 0xeb, 0x86, 0x68, 0x4b, 0x3d, 0x1f, 0x19, 0x80, 0x73, 0xb2, 0xc8, 0x4a, + 0xde, 0x84, 0x8c, 0xc4, 0xbf, 0x3c, 0xf3, 0x98, 0x31, 0xf6, 0x5e, 0x55, 0x18, 0x21, 0xcd, 0xaf, 0x5e, 0xa8, 0xb8, + 0x60, 0x63, 0x35, 0x9f, 0x96, 0xa7, 0x3c, 0x90, 0x2a, 0x9f, 0xaf, 0xb4, 0xa9, 0x1f, 0x39, 0x1f, 0x89, 0xb9, 0xb9, + 0x99, 0x93, 0x7a, 0x60, 0x10, 0xb1, 0x71, 0x9f, 0x20, 0xf2, 0x48, 0xf1, 0x67, 0x45, 0x2e, 0xd2, 0x61, 0x05, 0x56, + 0x0a, 0x01, 0x0b, 0x0d, 0x90, 0x56, 0xde, 0x4f, 0xb0, 0xe5, 0xd7, 0x24, 0x1a, 0x52, 0x2f, 0x99, 0x0d, 0xa8, 0x06, + 0xf1, 0x7b, 0x27, 0x9b, 0xcd, 0x9d, 0x9c, 0xce, 0xb7, 0x27, 0x6b, 0x86, 0xc8, 0x11, 0x74, 0xf4, 0xeb, 0xfe, 0x8e, + 0xbd, 0x20, 0x6d, 0x3a, 0x3b, 0xd9, 0x5a, 0x1f, 0x5e, 0x01, 0x93, 0x4e, 0xc5, 0x48, 0x93, 0x1a, 0x95, 0xb0, 0xcc, + 0x94, 0x4d, 0x29, 0xd1, 0x15, 0xd8, 0x4a, 0xc9, 0xc1, 0x96, 0x24, 0x9f, 0x79, 0xf8, 0x98, 0x74, 0xd7, 0x08, 0x17, + 0xe0, 0x18, 0x78, 0x2f, 0x83, 0xd2, 0x79, 0x60, 0xf4, 0x62, 0x10, 0xf3, 0x24, 0x84, 0x37, 0x5c, 0x96, 0xbc, 0x6c, + 0xed, 0xc9, 0x8a, 0x1a, 0xab, 0x6f, 0xdf, 0x7c, 0x3b, 0xe8, 0xca, 0xac, 0xd9, 0xc9, 0xef, 0xe7, 0x26, 0x5f, 0x77, + 0xcd, 0xf7, 0xbc, 0x6d, 0x7f, 0xc7, 0xb5, 0xcb, 0x37, 0x38, 0x2e, 0x18, 0x05, 0x3b, 0x5d, 0xac, 0x4e, 0x1b, 0x74, + 0x5c, 0x2f, 0x61, 0x57, 0x66, 0x04, 0xb4, 0x7b, 0x5f, 0xeb, 0x7f, 0x07, 0x98, 0x99, 0x62, 0x1f, 0x09, 0x22, 0x59, + 0x89, 0x6a, 0xcf, 0xfc, 0x42, 0xed, 0x2f, 0x08, 0x05, 0xf3, 0x35, 0xc8, 0xa3, 0xb7, 0x43, 0xc2, 0x68, 0x99, 0x89, + 0x38, 0xc1, 0x86, 0x05, 0x8f, 0xae, 0xe7, 0x72, 0xb6, 0xc5, 0x0e, 0x8f, 0xad, 0xae, 0xda, 0xdc, 0xab, 0x14, 0xf9, + 0x88, 0xeb, 0xe3, 0x19, 0x7a, 0xdf, 0x99, 0x79, 0xd0, 0xd1, 0x85, 0x48, 0xd8, 0xe2, 0x3a, 0x7e, 0x60, 0xbe, 0x46, + 0xa1, 0x60, 0xae, 0x94, 0xb9, 0xbd, 0x45, 0xdd, 0x4f, 0x75, 0xcf, 0xc9, 0xee, 0xfb, 0x92, 0x6f, 0x7e, 0xa4, 0xbd, + 0x1f, 0x45, 0xb3, 0xc2, 0x13, 0x2b, 0x5c, 0x47, 0xcf, 0xe6, 0x37, 0x1f, 0x33, 0x45, 0x08, 0x61, 0x04, 0xfd, 0xc2, + 0xaf, 0x70, 0x2d, 0xf0, 0x46, 0x99, 0xb6, 0x61, 0x2f, 0xa9, 0xa5, 0x20, 0xae, 0x1d, 0x1e, 0xce, 0xd9, 0xad, 0x35, + 0x59, 0xec, 0x8e, 0xab, 0xbe, 0xd0, 0x28, 0x7f, 0x87, 0x4c, 0x3b, 0x7c, 0xf3, 0x0d, 0xb9, 0x61, 0xaf, 0xa6, 0x4f, + 0x46, 0x68, 0xe2, 0x4e, 0xbd, 0x7e, 0x0a, 0x24, 0xf3, 0x34, 0x01, 0xa2, 0x31, 0xfc, 0xdf, 0x25, 0x7b, 0x34, 0xa6, + 0x13, 0x36, 0xcc, 0x86, 0xac, 0x36, 0x60, 0xec, 0x21, 0x89, 0x1e, 0x7f, 0x45, 0xfe, 0xdf, 0x9a, 0x04, 0xc7, 0x4b, + 0x71, 0x9f, 0x1b, 0xfe, 0xb2, 0x0c, 0xb3, 0x9c, 0xc4, 0x2c, 0xb8, 0x65, 0xc5, 0xab, 0x20, 0x5c, 0xa6, 0x5d, 0x61, + 0x19, 0x96, 0x0b, 0x2c, 0x43, 0x59, 0x7d, 0x11, 0x49, 0x22, 0xed, 0x91, 0x98, 0x9d, 0xce, 0xde, 0x8b, 0x13, 0xb2, + 0xe3, 0x06, 0x4d, 0x8e, 0x2e, 0xb2, 0x31, 0x13, 0x45, 0xed, 0x41, 0x23, 0x83, 0x72, 0x35, 0x78, 0xb9, 0x86, 0x8e, + 0x0c, 0xe1, 0x6a, 0x54, 0xa1, 0x71, 0x21, 0x9d, 0x4f, 0x2f, 0x8f, 0x0c, 0x24, 0x19, 0x34, 0xc5, 0xb0, 0x23, 0x54, + 0xc5, 0xbc, 0x4e, 0xf5, 0x42, 0x6a, 0x85, 0x47, 0xf2, 0x28, 0xc3, 0xf2, 0xd2, 0x22, 0xa3, 0xdd, 0xbe, 0x72, 0x74, + 0x5d, 0x38, 0x8e, 0x9f, 0xc3, 0x64, 0xa1, 0x0e, 0xd7, 0x60, 0x20, 0xdd, 0x4f, 0x1f, 0x79, 0xe9, 0x7f, 0x34, 0x5d, + 0x0d, 0xed, 0xb3, 0x85, 0xf8, 0xea, 0x21, 0x23, 0x8e, 0xaf, 0x5e, 0x58, 0x84, 0xa3, 0xe5, 0x96, 0xe9, 0xe3, 0x98, + 0x6d, 0x1d, 0xaa, 0xdc, 0x1a, 0x8d, 0x67, 0xb5, 0x18, 0x3f, 0xba, 0x0a, 0x1a, 0x82, 0xa6, 0x24, 0x0b, 0xf7, 0x15, + 0x75, 0x41, 0x56, 0x30, 0x19, 0xac, 0xb0, 0xbc, 0x99, 0xdf, 0xa7, 0xb5, 0xa9, 0xb4, 0x7c, 0x24, 0xf8, 0x07, 0x0f, + 0xb1, 0xac, 0x4f, 0x85, 0xdd, 0x12, 0x17, 0x0f, 0x2c, 0xe4, 0x59, 0x2f, 0xdc, 0x68, 0xeb, 0x94, 0xab, 0x72, 0xd9, + 0x2d, 0x5d, 0x78, 0x55, 0xb7, 0xbc, 0x14, 0x82, 0xd7, 0x21, 0xc9, 0x49, 0x6e, 0x42, 0x2c, 0x06, 0x03, 0x6f, 0xe4, + 0xa4, 0xef, 0x14, 0x5c, 0xc8, 0x0d, 0x74, 0xa5, 0xaf, 0x13, 0x4b, 0x01, 0x05, 0xb0, 0x17, 0x1e, 0xd8, 0x78, 0x02, + 0x89, 0x3c, 0xbf, 0x5e, 0xd4, 0x89, 0x0e, 0x07, 0xbf, 0x9f, 0xaf, 0x14, 0x07, 0xf0, 0x5d, 0x82, 0x7c, 0x71, 0xde, + 0x48, 0xf0, 0x0f, 0xb0, 0xc3, 0xd9, 0xb9, 0xbf, 0xc1, 0x5c, 0xc2, 0x92, 0xec, 0x28, 0xe0, 0xb3, 0x02, 0xab, 0x9b, + 0x80, 0x8b, 0x04, 0xc1, 0x41, 0x5b, 0x2c, 0x17, 0x04, 0x87, 0x34, 0x0a, 0x15, 0xf3, 0x21, 0x3f, 0x37, 0x9b, 0x92, + 0x28, 0x92, 0xe1, 0xe7, 0xd7, 0xe0, 0x32, 0x23, 0x9c, 0xe2, 0x63, 0x4d, 0x95, 0x0f, 0x75, 0x5e, 0x8c, 0x74, 0x02, + 0x0c, 0x6f, 0xa6, 0x21, 0xda, 0x3f, 0x46, 0x8d, 0x92, 0xca, 0x3d, 0x0b, 0x97, 0xc6, 0xd9, 0x10, 0x2e, 0xf3, 0x6f, + 0xb2, 0xcc, 0x6b, 0x29, 0x96, 0x57, 0x36, 0x65, 0xc1, 0xf9, 0x1e, 0xd6, 0x71, 0xe4, 0xee, 0xb3, 0x5e, 0x59, 0x72, + 0x88, 0x76, 0xcb, 0x47, 0x67, 0x39, 0xa0, 0x57, 0x71, 0x75, 0xe3, 0x14, 0xc4, 0x91, 0x97, 0x97, 0x91, 0xca, 0x70, + 0x3c, 0x95, 0x04, 0x1c, 0xa9, 0xa7, 0xf8, 0xbf, 0x29, 0xe1, 0x1d, 0x04, 0x83, 0x30, 0x76, 0x0f, 0xf5, 0x2b, 0x40, + 0xd1, 0x16, 0x66, 0x07, 0x37, 0x25, 0x6e, 0xe2, 0xc0, 0x28, 0x47, 0x6f, 0x83, 0xf9, 0xd2, 0x12, 0xb4, 0xc2, 0x6a, + 0x46, 0xa0, 0xd5, 0xe7, 0x71, 0xaf, 0x08, 0xfc, 0xd4, 0x85, 0xe3, 0x79, 0x5e, 0x43, 0x77, 0x68, 0x1a, 0xcb, 0x33, + 0x69, 0x4b, 0xc2, 0x40, 0xd2, 0x2c, 0xb4, 0xf1, 0xe3, 0x73, 0x4d, 0x75, 0x3b, 0x8b, 0xe8, 0x7a, 0xbd, 0x0c, 0xa5, + 0x88, 0x58, 0xb4, 0x70, 0x34, 0x27, 0x1b, 0xd0, 0xe9, 0x3e, 0xd9, 0x98, 0x1a, 0x0e, 0x86, 0x90, 0x18, 0xb9, 0x0d, + 0xe3, 0x9c, 0xd8, 0xf0, 0x84, 0x2a, 0xd5, 0x13, 0x3f, 0x45, 0x5b, 0x89, 0xe0, 0x09, 0x95, 0x46, 0x1e, 0x78, 0x54, + 0xd1, 0x1a, 0x90, 0xc3, 0xc3, 0x47, 0xe0, 0x94, 0x6f, 0x30, 0x56, 0x47, 0x28, 0x50, 0x8e, 0x60, 0x4e, 0x91, 0x1f, + 0xee, 0xf0, 0x21, 0x7c, 0x2d, 0x4f, 0x30, 0x53, 0x6b, 0x2f, 0xc7, 0x5c, 0x0f, 0xb9, 0x1d, 0xf2, 0xb0, 0xff, 0xc4, + 0xcb, 0xc8, 0x46, 0x68, 0xf8, 0x91, 0x5f, 0xf5, 0x58, 0x7f, 0x3d, 0xc0, 0x7c, 0x3a, 0xd9, 0x83, 0x09, 0x67, 0x05, + 0x40, 0xfc, 0xd1, 0x55, 0x70, 0x37, 0x68, 0x58, 0x1f, 0x63, 0x12, 0x66, 0x27, 0x0e, 0x87, 0x6f, 0xa5, 0x02, 0x50, + 0x9e, 0x87, 0x19, 0x89, 0x2c, 0x24, 0xf3, 0xf3, 0x72, 0x8a, 0x6d, 0x51, 0xa6, 0xb6, 0xb4, 0x75, 0x0d, 0x38, 0x91, + 0xc5, 0xcd, 0x24, 0x79, 0x0a, 0x35, 0x2a, 0x22, 0x46, 0xc2, 0x2c, 0xd8, 0x7a, 0x99, 0xb0, 0xc7, 0x6f, 0x8c, 0x61, + 0xd4, 0xa6, 0x8d, 0xf4, 0x86, 0xbd, 0xea, 0x4f, 0xd6, 0xef, 0x11, 0x5b, 0x15, 0xe0, 0xbe, 0xa5, 0x1f, 0xa0, 0x48, + 0xe3, 0x96, 0x76, 0xf2, 0xd3, 0x89, 0x04, 0xfa, 0x87, 0x18, 0x36, 0x89, 0x0d, 0x0a, 0x8e, 0x2f, 0xb5, 0x29, 0xde, + 0x06, 0xce, 0x0c, 0xc5, 0x7a, 0xad, 0x67, 0xe0, 0x44, 0x1b, 0x41, 0x2a, 0x74, 0xcf, 0x58, 0x1e, 0x91, 0xa9, 0xf3, + 0x4f, 0x48, 0xcb, 0x16, 0xa6, 0x25, 0x9f, 0xe4, 0x74, 0x24, 0xd9, 0xf9, 0x29, 0x9a, 0xe4, 0x9d, 0xde, 0x25, 0x52, + 0x7c, 0x7d, 0x19, 0x66, 0x2f, 0xff, 0x84, 0x9a, 0x14, 0x22, 0x1d, 0x5c, 0xdd, 0x30, 0x31, 0xd4, 0x0a, 0x8c, 0xea, + 0x78, 0x9f, 0x8a, 0x4c, 0x1c, 0x0f, 0x6a, 0xe6, 0x45, 0x85, 0xc1, 0x13, 0x4b, 0x70, 0x94, 0xca, 0xae, 0xb6, 0xec, + 0x2d, 0x9c, 0x0c, 0x7e, 0x8a, 0x35, 0x49, 0xd5, 0xf1, 0x82, 0xb6, 0x6a, 0x68, 0x0e, 0x5d, 0x33, 0x6f, 0x66, 0xc7, + 0x63, 0xff, 0x2a, 0x61, 0xd1, 0xc0, 0x9a, 0x0e, 0x88, 0x5e, 0x07, 0xfd, 0x9c, 0x16, 0xdc, 0x2f, 0xbc, 0x0e, 0xbc, + 0x11, 0x83, 0x08, 0x0a, 0x66, 0x28, 0xf1, 0x79, 0xb5, 0x40, 0xc6, 0x86, 0x62, 0x92, 0x54, 0xd2, 0xb1, 0x71, 0x65, + 0x94, 0x8d, 0xcb, 0xf4, 0x72, 0xca, 0xdb, 0x2c, 0xe8, 0x21, 0x6f, 0xe5, 0x2b, 0x88, 0x93, 0xc6, 0x31, 0x22, 0x02, + 0x1c, 0x0f, 0x97, 0x39, 0x87, 0xbc, 0x59, 0x6c, 0x41, 0x4f, 0x09, 0xad, 0xc1, 0xcd, 0xce, 0x59, 0x4f, 0xf9, 0x52, + 0x3c, 0x5d, 0x14, 0x97, 0xdd, 0x6f, 0xa0, 0x00, 0x08, 0x03, 0xff, 0x23, 0x09, 0x7d, 0x56, 0x20, 0x63, 0x8e, 0x07, + 0xc9, 0x91, 0xe5, 0xb1, 0x96, 0x47, 0xa0, 0x85, 0x18, 0xa9, 0xde, 0x86, 0xbf, 0xf3, 0x29, 0x5e, 0x68, 0x07, 0x2b, + 0x77, 0x83, 0x20, 0x48, 0x70, 0x00, 0xfc, 0x85, 0xf7, 0xdd, 0xd0, 0x07, 0xef, 0xb7, 0x7b, 0x87, 0xff, 0xa7, 0x39, + 0xb3, 0x8f, 0x18, 0xdb, 0x7e, 0x88, 0x55, 0xdf, 0x25, 0xff, 0xf1, 0xd0, 0xd0, 0x06, 0xe8, 0xe1, 0x03, 0x1b, 0xce, + 0xde, 0xd3, 0x10, 0x6e, 0xdb, 0xa8, 0x02, 0x96, 0x14, 0x86, 0x48, 0x39, 0xa9, 0xa3, 0xfa, 0x22, 0x95, 0xdc, 0x24, + 0xfe, 0x3c, 0x33, 0x50, 0xfd, 0x83, 0x85, 0x5f, 0x83, 0x2f, 0xbb, 0x07, 0x05, 0x66, 0x62, 0x7d, 0x1a, 0x50, 0xaa, + 0xd4, 0x61, 0x7e, 0xf1, 0xd0, 0xfe, 0x1a, 0xb5, 0xab, 0x6c, 0x78, 0x7f, 0xd1, 0x95, 0x82, 0xb0, 0xc5, 0xe5, 0x21, + 0xd7, 0xe6, 0xde, 0x3e, 0xad, 0x5d, 0xed, 0x03, 0xef, 0x0a, 0x11, 0x60, 0xa7, 0xce, 0xe4, 0xe4, 0x19, 0x9f, 0x9a, + 0x40, 0xe7, 0xec, 0xde, 0xfe, 0x66, 0x03, 0x7e, 0x34, 0xc0, 0x76, 0x57, 0xf7, 0xf6, 0x01, 0x0c, 0xca, 0x65, 0xd4, + 0x50, 0x21, 0x31, 0xc4, 0x4b, 0x2a, 0x48, 0x39, 0x8a, 0xce, 0x87, 0xc8, 0x93, 0x43, 0x4c, 0x1b, 0x09, 0xae, 0xab, + 0xb4, 0x3d, 0x72, 0x12, 0xb4, 0x3c, 0xb2, 0x7b, 0x18, 0xb7, 0x51, 0x71, 0x5c, 0x64, 0x34, 0xf2, 0x0c, 0xee, 0x70, + 0xae, 0x23, 0xf4, 0x68, 0x55, 0x0a, 0x90, 0x26, 0x5c, 0x41, 0xfd, 0x3e, 0x9c, 0x8d, 0xb2, 0x87, 0xaa, 0xe5, 0xd8, + 0x4f, 0xe0, 0x35, 0x45, 0xef, 0xc8, 0x9f, 0x5b, 0x99, 0x15, 0xe1, 0xf2, 0xca, 0x62, 0xb2, 0x10, 0xcc, 0xd1, 0x36, + 0x6e, 0x99, 0x74, 0xf0, 0x0c, 0xf5, 0xfc, 0x50, 0xb5, 0x87, 0x15, 0x5f, 0x10, 0xc9, 0x34, 0xc5, 0x9d, 0xc3, 0xaf, + 0x63, 0x7e, 0x55, 0x38, 0x05, 0x72, 0x37, 0x1d, 0x26, 0xc2, 0x35, 0xdb, 0x4d, 0x90, 0x45, 0x9a, 0x0f, 0x15, 0xba, + 0x7d, 0xd6, 0x51, 0x9f, 0xb9, 0xc4, 0xa8, 0x3d, 0x4a, 0x66, 0x7c, 0xc2, 0x76, 0xbb, 0x91, 0x7e, 0xd1, 0xb5, 0x14, + 0x43, 0x24, 0x95, 0x29, 0xa5, 0x4b, 0x69, 0x89, 0x23, 0xb5, 0x0c, 0x65, 0x1c, 0x4a, 0xe8, 0x14, 0x40, 0xdb, 0x78, + 0xc8, 0x4c, 0x22, 0xed, 0x54, 0xfb, 0xac, 0xca, 0x64, 0x8f, 0xc5, 0x91, 0x30, 0x64, 0xe1, 0x99, 0x60, 0x7d, 0x7f, + 0xae, 0xf9, 0x92, 0x02, 0x55, 0x19, 0xac, 0x3b, 0x06, 0x7f, 0x2c, 0xb4, 0x79, 0x29, 0x2f, 0x85, 0x5e, 0x85, 0xa9, + 0x50, 0x5b, 0xbd, 0xb4, 0x4e, 0x1b, 0x42, 0x05, 0xb2, 0x76, 0x96, 0xe8, 0x51, 0x36, 0x38, 0xc8, 0xf1, 0xbf, 0x0d, + 0x22, 0xdb, 0x1e, 0x04, 0xdb, 0x7b, 0xa6, 0x22, 0xf5, 0xbd, 0xd5, 0x77, 0x9b, 0xf1, 0x89, 0x09, 0x81, 0xcb, 0x80, + 0xab, 0xce, 0xc7, 0x6e, 0x6c, 0xc3, 0x1f, 0x11, 0xe0, 0xef, 0x70, 0xe5, 0xa9, 0xf0, 0x55, 0xfa, 0x5a, 0xd9, 0xca, + 0x2b, 0xef, 0x39, 0x05, 0xb6, 0x6d, 0x7d, 0xa5, 0x09, 0x58, 0x31, 0xd0, 0x8b, 0x80, 0x6f, 0x73, 0xf2, 0x03, 0xf9, + 0xbc, 0x0b, 0xed, 0x99, 0x13, 0xb0, 0x19, 0xec, 0xc1, 0x8e, 0xdd, 0x8d, 0xd1, 0x28, 0x35, 0xe1, 0x57, 0xe6, 0xf6, + 0xa3, 0xaf, 0xa5, 0xff, 0xfc, 0x25, 0x86, 0xe8, 0x25, 0x30, 0x85, 0xf3, 0xd7, 0x11, 0xea, 0x0e, 0x59, 0x52, 0xda, + 0x91, 0x6a, 0x14, 0x5d, 0x54, 0x61, 0x5d, 0x0b, 0xb0, 0x42, 0x63, 0xf5, 0x8d, 0xe1, 0xb5, 0x92, 0x74, 0x14, 0x6b, + 0x2d, 0x86, 0xb7, 0xe9, 0xfc, 0xbe, 0x8a, 0x9d, 0x04, 0x2c, 0x60, 0xbe, 0x4e, 0x70, 0x17, 0x19, 0xec, 0x0e, 0xf7, + 0x6c, 0x3f, 0x27, 0x1a, 0x8e, 0x5c, 0x28, 0x80, 0x0a, 0x6f, 0x17, 0xd2, 0xa4, 0x5f, 0xe7, 0x3f, 0x57, 0xc5, 0x77, + 0x4c, 0x2d, 0x39, 0x4c, 0xf4, 0x52, 0xe3, 0x5f, 0xf7, 0xc6, 0x0f, 0xe5, 0xeb, 0xe5, 0x83, 0xbd, 0x10, 0x6e, 0x79, + 0x8e, 0x95, 0x15, 0x51, 0x0d, 0x71, 0x7f, 0xe8, 0x64, 0x46, 0xb9, 0x9b, 0x6b, 0x92, 0xd5, 0x49, 0x5a, 0x05, 0x4f, + 0x7d, 0x95, 0xf1, 0x67, 0x66, 0x94, 0x7b, 0x6e, 0x2c, 0x43, 0x89, 0x74, 0xe0, 0x8b, 0x86, 0xe6, 0x67, 0xa8, 0x8e, + 0x28, 0x9e, 0xab, 0x01, 0x1b, 0x40, 0x69, 0x5e, 0x0f, 0x03, 0x6b, 0x99, 0xba, 0x30, 0xaa, 0x36, 0xa2, 0xa0, 0x04, + 0x53, 0x48, 0x6b, 0x69, 0x4b, 0x2c, 0x50, 0x51, 0xb3, 0xa8, 0xb1, 0xd1, 0xcf, 0x74, 0x58, 0xe3, 0x66, 0x87, 0x7b, + 0x82, 0x19, 0x41, 0x50, 0x45, 0xb6, 0x3e, 0xb4, 0xaa, 0x46, 0x51, 0x3c, 0xf5, 0x73, 0x40, 0x41, 0xf9, 0x8f, 0x2b, + 0x5f, 0xda, 0xe2, 0xb8, 0x63, 0x35, 0xe0, 0x8b, 0xe2, 0xfd, 0x1e, 0xb9, 0x2a, 0x25, 0x36, 0x71, 0x93, 0x5b, 0x34, + 0x65, 0x04, 0xb1, 0xa7, 0x3f, 0x41, 0x95, 0x14, 0x29, 0x5d, 0xc4, 0x0d, 0xad, 0xb9, 0x38, 0x59, 0xa2, 0x0d, 0xf5, + 0xc0, 0xad, 0x6f, 0x64, 0x68, 0xa2, 0x57, 0xfb, 0xf2, 0x8d, 0x42, 0x0c, 0xc2, 0x92, 0x85, 0xbc, 0x62, 0x62, 0xcc, + 0x60, 0xe0, 0x48, 0xd1, 0xb6, 0x51, 0x2e, 0x46, 0x6f, 0xc4, 0x72, 0x75, 0x9c, 0xef, 0x64, 0x3b, 0x8a, 0x4a, 0x67, + 0xdc, 0x2b, 0xd0, 0xe6, 0xa7, 0x6d, 0xff, 0x86, 0xa7, 0xff, 0xa4, 0x26, 0x5c, 0x8f, 0xd0, 0xb3, 0x08, 0x9f, 0x7a, + 0x40, 0x2c, 0x8f, 0x81, 0x14, 0xa6, 0xe7, 0x2f, 0x52, 0x38, 0x46, 0xd2, 0x8d, 0x25, 0xb1, 0xe4, 0x39, 0x4e, 0xf1, + 0x3d, 0x75, 0xbe, 0xa4, 0x03, 0xea, 0xe8, 0xe4, 0x16, 0x12, 0x68, 0x9a, 0x09, 0xc9, 0xe6, 0x13, 0xda, 0xbc, 0x4c, + 0xcd, 0xba, 0x41, 0xf2, 0x96, 0xa7, 0x96, 0xef, 0x54, 0x2c, 0xe3, 0xfb, 0x21, 0x12, 0x42, 0xd6, 0x2b, 0x6a, 0x96, + 0xfc, 0x82, 0x94, 0xed, 0x02, 0xb4, 0x4b, 0x05, 0x61, 0x71, 0xf6, 0x9a, 0x98, 0xfb, 0x4a, 0xa5, 0x1f, 0xca, 0x6b, + 0xa4, 0x3d, 0x1c, 0x02, 0x00, 0xe3, 0x7e, 0x6f, 0xc2, 0xc4, 0xe8, 0x0c, 0x17, 0x4e, 0xd4, 0x52, 0x21, 0x09, 0xdb, + 0x24, 0x65, 0x76, 0x6f, 0xd6, 0xf1, 0xc3, 0x5f, 0x63, 0x38, 0x37, 0x5a, 0x2b, 0xe1, 0x36, 0xd0, 0x55, 0x67, 0xc8, + 0xab, 0x73, 0xc4, 0xde, 0xdc, 0x29, 0x7f, 0x2e, 0x07, 0xa1, 0xd2, 0x6a, 0x3e, 0x5b, 0x85, 0x5b, 0x30, 0x85, 0x27, + 0x5e, 0x13, 0x6c, 0x30, 0x2f, 0xe1, 0x25, 0xa0, 0xc6, 0x20, 0xe3, 0x23, 0x1f, 0x6c, 0x81, 0x95, 0xd5, 0xf8, 0x73, + 0xec, 0xdf, 0x87, 0xb9, 0xdb, 0xb3, 0x68, 0xe3, 0x7a, 0x31, 0x7d, 0x40, 0x49, 0x26, 0x9c, 0x76, 0xb7, 0x60, 0xdd, + 0xd0, 0x4e, 0x4c, 0xa1, 0x51, 0x31, 0x37, 0x20, 0x75, 0xec, 0xc6, 0xa1, 0xb6, 0xa3, 0xf5, 0xe6, 0x23, 0x8a, 0x06, + 0x71, 0x8e, 0xc8, 0xd6, 0x66, 0xbd, 0xb6, 0xb4, 0x5b, 0x18, 0x40, 0x29, 0x58, 0x4e, 0x09, 0xce, 0xbb, 0x72, 0xd1, + 0x2e, 0x0a, 0xb3, 0x05, 0x90, 0xd2, 0x0d, 0x64, 0xc8, 0x23, 0xea, 0x35, 0x99, 0x63, 0xb7, 0xf4, 0xf8, 0xd9, 0x40, + 0xec, 0x47, 0xbf, 0x24, 0xe3, 0xcc, 0xc0, 0xa4, 0xbd, 0x2f, 0x29, 0x79, 0xa2, 0x9c, 0xbc, 0x6a, 0xe4, 0x30, 0x6c, + 0xe9, 0x1d, 0xcb, 0xc3, 0x8c, 0x1e, 0x82, 0x04, 0x99, 0xf7, 0x8e, 0xe9, 0x12, 0x21, 0xbe, 0x87, 0x85, 0x80, 0x69, + 0xb4, 0xfe, 0xb7, 0xda, 0xe5, 0x53, 0x9f, 0xe7, 0x36, 0xed, 0xad, 0x3b, 0x74, 0xc5, 0x95, 0x4b, 0x6e, 0xfd, 0xa8, + 0x9e, 0xa9, 0xda, 0xa9, 0x7c, 0x6f, 0xb5, 0xec, 0xeb, 0x1c, 0xa4, 0xa1, 0x3d, 0xf2, 0x41, 0xbb, 0xd8, 0x58, 0xb6, + 0xa6, 0x59, 0x34, 0xb3, 0x68, 0xe3, 0x58, 0xd9, 0xc5, 0xb7, 0xc4, 0x23, 0x71, 0xc1, 0xdc, 0xc6, 0xa3, 0x32, 0x12, + 0x3b, 0x3c, 0xa2, 0x2d, 0x7c, 0x23, 0xb4, 0x6d, 0x98, 0x8b, 0x8f, 0x13, 0x70, 0xac, 0x2d, 0x9f, 0x96, 0x63, 0x66, + 0xb5, 0x88, 0x32, 0x59, 0x01, 0x8c, 0x8b, 0xda, 0x71, 0x6f, 0x82, 0xa1, 0x0e, 0xc9, 0xc5, 0x1a, 0x84, 0x30, 0xbd, + 0x16, 0xea, 0xcc, 0xab, 0xfc, 0x8a, 0x6c, 0x6d, 0x2c, 0xc2, 0x42, 0xcb, 0xc6, 0xcc, 0x64, 0x4d, 0x0b, 0x60, 0x8d, + 0xa0, 0xd7, 0x6b, 0xb8, 0x7b, 0x6e, 0xa9, 0xfc, 0x19, 0x1c, 0xb9, 0xf8, 0x18, 0xcc, 0xc7, 0x5e, 0xa5, 0xa4, 0xa9, + 0x87, 0xcf, 0x13, 0xcd, 0x08, 0x8e, 0x5b, 0xe5, 0x0d, 0xb5, 0x67, 0xc3, 0xff, 0x81, 0xaf, 0xe1, 0x73, 0x4e, 0x6e, + 0x85, 0x71, 0x70, 0x66, 0xed, 0x8b, 0x77, 0x75, 0x8c, 0x98, 0x45, 0x8c, 0xf1, 0x25, 0x6b, 0x4a, 0xb9, 0xf9, 0x2c, + 0xd6, 0x47, 0x50, 0x04, 0x96, 0xaf, 0x31, 0x19, 0x21, 0x1c, 0x2c, 0x6e, 0x34, 0x19, 0x62, 0x03, 0xdd, 0x9b, 0x7b, + 0x40, 0x0c, 0x06, 0xe0, 0x21, 0xce, 0x05, 0x59, 0xe8, 0x00, 0xb2, 0xfc, 0x01, 0x12, 0x08, 0x49, 0x60, 0x81, 0x22, + 0x21, 0x23, 0xaf, 0x5a, 0x87, 0x9a, 0x97, 0xb3, 0xcb, 0x0c, 0x17, 0x10, 0x0c, 0x5b, 0xb9, 0x4b, 0xab, 0x51, 0x9b, + 0xed, 0x2d, 0x96, 0x81, 0xde, 0x9e, 0x35, 0x95, 0xa4, 0x48, 0xf9, 0xb5, 0xe9, 0x18, 0xff, 0x78, 0xc8, 0x56, 0x74, + 0xae, 0x18, 0xc3, 0xfd, 0x48, 0x9e, 0xdd, 0xf9, 0xcb, 0xc2, 0x72, 0xb1, 0x1e, 0x4a, 0x3d, 0x34, 0x66, 0x17, 0x0b, + 0x1d, 0xb6, 0x45, 0xc3, 0x22, 0x52, 0xff, 0x24, 0xbc, 0x1e, 0xdc, 0xa3, 0xa8, 0x9c, 0x4c, 0xf0, 0x84, 0xda, 0xb9, + 0xb4, 0x6e, 0x04, 0xde, 0xa5, 0x3c, 0x9f, 0xa6, 0x60, 0x4b, 0xe3, 0x52, 0xa1, 0x94, 0x9d, 0x19, 0xd3, 0xa8, 0x93, + 0xf3, 0xd2, 0x1a, 0xeb, 0x36, 0x69, 0xc1, 0x18, 0x07, 0xa1, 0xfe, 0x74, 0xd6, 0x6f, 0xfe, 0x2d, 0x3f, 0x13, 0x52, + 0x39, 0x97, 0xdc, 0x3f, 0x64, 0x5a, 0xd5, 0xd4, 0x12, 0x68, 0x26, 0xd0, 0x0c, 0x7e, 0x2d, 0x90, 0xcf, 0x43, 0xb8, + 0x67, 0xfa, 0x2c, 0xc4, 0xfd, 0x20, 0x26, 0x1c, 0xb4, 0xf1, 0x86, 0x5e, 0xa3, 0x44, 0xea, 0xbf, 0x25, 0x03, 0x3a, + 0xb9, 0xc5, 0x42, 0xa9, 0x25, 0x89, 0xf3, 0xb5, 0x48, 0x75, 0xe7, 0xfb, 0xb8, 0x8e, 0x72, 0x41, 0x94, 0x74, 0xae, + 0x03, 0xdc, 0x27, 0x94, 0x73, 0x5a, 0x23, 0x6a, 0x24, 0xac, 0xd9, 0x4a, 0xe1, 0xd7, 0xe0, 0x41, 0x20, 0x3a, 0x80, + 0x84, 0xfd, 0xe1, 0xd5, 0xf6, 0x9a, 0xd9, 0xf9, 0xa0, 0x90, 0x05, 0xe2, 0x52, 0xb7, 0xdd, 0x62, 0x27, 0x21, 0x00, + 0xd1, 0x6d, 0x12, 0x0c, 0x14, 0xd4, 0x8e, 0xd3, 0x6f, 0xd0, 0xd0, 0x3b, 0xad, 0xec, 0xdd, 0xdd, 0x84, 0xa2, 0x08, + 0x21, 0x01, 0x12, 0x7b, 0xa0, 0xd2, 0x50, 0x10, 0x45, 0xcb, 0x41, 0x44, 0x28, 0xb1, 0xfb, 0xb8, 0x17, 0xa2, 0x82, + 0x1b, 0xe9, 0x88, 0x34, 0x62, 0xe8, 0x15, 0x6c, 0x88, 0x80, 0x40, 0x95, 0x87, 0x91, 0xc6, 0x2f, 0x09, 0x57, 0xb7, + 0x82, 0x4e, 0xd4, 0xc5, 0x9c, 0xb2, 0xb5, 0xf7, 0x20, 0x86, 0xe7, 0xb6, 0x92, 0x3e, 0x09, 0x42, 0xf4, 0x29, 0x98, + 0x43, 0x99, 0x7f, 0xca, 0x18, 0x63, 0x34, 0x90, 0xb3, 0x7d, 0x11, 0x22, 0x32, 0xb1, 0x9c, 0x51, 0x06, 0xa6, 0xd0, + 0x11, 0xb9, 0x44, 0x86, 0xfe, 0xe4, 0xe6, 0x8b, 0xda, 0x93, 0x1a, 0xc7, 0x75, 0xac, 0x5e, 0xaa, 0x67, 0x0d, 0xb6, + 0xa1, 0xf8, 0x47, 0xf2, 0xd8, 0x1c, 0x26, 0xe5, 0x17, 0x43, 0xd4, 0xe5, 0xec, 0xb0, 0xde, 0x43, 0x16, 0xab, 0xa2, + 0x01, 0x60, 0xb5, 0x5b, 0x61, 0x9d, 0x67, 0xa6, 0x7e, 0xa7, 0xcb, 0x1b, 0x5b, 0x5b, 0xb8, 0x45, 0x5d, 0xa8, 0x86, + 0x82, 0xf2, 0x6a, 0x50, 0x7f, 0xc2, 0xe8, 0x2f, 0x4f, 0x25, 0xe6, 0x19, 0x21, 0x99, 0xa7, 0xee, 0x1d, 0x7f, 0x09, + 0x8f, 0xc3, 0x96, 0x70, 0x75, 0xaa, 0xdb, 0x7f, 0xa9, 0xe2, 0xc9, 0xcc, 0xad, 0x46, 0xbe, 0x71, 0x0b, 0x85, 0x62, + 0x2f, 0x57, 0x23, 0xba, 0x0f, 0xad, 0xb2, 0x5f, 0xf6, 0x91, 0x6c, 0x84, 0x39, 0xcc, 0xe7, 0x63, 0x9f, 0x06, 0x1e, + 0x9f, 0x7b, 0xea, 0x78, 0x3b, 0xc0, 0x4d, 0x81, 0xcd, 0xd6, 0x81, 0xf0, 0x7c, 0x40, 0xf7, 0xd9, 0xa8, 0xc9, 0xba, + 0xd7, 0x45, 0x01, 0x62, 0x0b, 0x83, 0x2b, 0xad, 0x7f, 0xe4, 0x45, 0xd6, 0x17, 0x55, 0xa0, 0xed, 0x97, 0xe9, 0xbe, + 0x28, 0x0c, 0x0d, 0x4f, 0xbb, 0x8d, 0xd8, 0x63, 0x07, 0xcd, 0x92, 0xd6, 0x30, 0x79, 0x25, 0x1d, 0xe4, 0x6f, 0x62, + 0xb2, 0x48, 0x94, 0xbf, 0xfd, 0x35, 0x39, 0xc9, 0x5d, 0x6f, 0x76, 0x7b, 0x29, 0x6a, 0x2a, 0x4c, 0xfd, 0x5d, 0xfb, + 0xb0, 0x52, 0xff, 0x95, 0x7e, 0xb9, 0x0a, 0x75, 0x47, 0xd6, 0x82, 0x44, 0x4e, 0xc3, 0x90, 0x6b, 0x75, 0x58, 0x73, + 0xaa, 0xf3, 0x6c, 0x9d, 0xb5, 0x1d, 0x3e, 0x81, 0x9b, 0x25, 0x67, 0x09, 0x53, 0xf1, 0xa6, 0x26, 0x04, 0x87, 0x81, + 0xa0, 0x30, 0x5c, 0x14, 0x87, 0x48, 0x58, 0xbc, 0xd9, 0xe1, 0x85, 0xd3, 0x65, 0xb0, 0xf1, 0xd5, 0x7e, 0xa1, 0xcc, + 0x33, 0xb6, 0x0b, 0x0b, 0x50, 0x2d, 0xa2, 0xfc, 0x18, 0x35, 0xc0, 0xea, 0x9f, 0xf0, 0xf1, 0x7b, 0x12, 0xb6, 0x1e, + 0x74, 0x49, 0x6a, 0xd9, 0x54, 0x4a, 0x54, 0x5b, 0xc6, 0x35, 0xd6, 0x50, 0x71, 0xed, 0xf0, 0xc8, 0xea, 0x0c, 0xfd, + 0x47, 0xe7, 0x8b, 0xc4, 0x73, 0x48, 0x27, 0x8f, 0x56, 0x82, 0x68, 0x71, 0xcb, 0xba, 0xf5, 0xa1, 0xaf, 0xb9, 0x1c, + 0x85, 0x6d, 0x34, 0x94, 0xd2, 0x45, 0xbc, 0xa4, 0x76, 0x1d, 0x94, 0x81, 0xf4, 0x70, 0x65, 0xa2, 0xb3, 0x0f, 0xd4, + 0xb0, 0xee, 0x40, 0xe3, 0x4d, 0x8f, 0x29, 0xb4, 0xb1, 0xab, 0x16, 0xf3, 0x8a, 0xa9, 0x53, 0x74, 0x4b, 0x6c, 0x09, + 0xec, 0xae, 0xcb, 0xe1, 0xd6, 0xf4, 0x25, 0x10, 0x53, 0x4a, 0xc4, 0xb7, 0x5c, 0x6a, 0xee, 0xba, 0x8e, 0xe2, 0x13, + 0xb6, 0x42, 0x4b, 0xd6, 0xad, 0xc3, 0x6d, 0xac, 0xf5, 0x5a, 0x10, 0x52, 0xff, 0x52, 0x8b, 0x70, 0xf0, 0xea, 0x82, + 0x65, 0x5b, 0x7c, 0x74, 0xc2, 0x86, 0x16, 0x6d, 0x7b, 0x54, 0x41, 0x8b, 0x1d, 0x55, 0xe3, 0xa5, 0xcd, 0x5c, 0xe2, + 0x6a, 0x26, 0xc6, 0x37, 0xf4, 0xdb, 0x14, 0x07, 0x96, 0x9d, 0x15, 0xa0, 0xf1, 0xe0, 0xa4, 0xb7, 0x22, 0x2f, 0x35, + 0x3b, 0xa8, 0x99, 0x51, 0xcb, 0x67, 0x9a, 0x48, 0xeb, 0x05, 0xac, 0x11, 0xda, 0x5a, 0x7e, 0xe0, 0x8a, 0x13, 0x09, + 0xb6, 0x65, 0xfa, 0x7f, 0x98, 0x92, 0x56, 0x62, 0x47, 0x00, 0xcf, 0xb8, 0x8b, 0xfe, 0x81, 0x66, 0x05, 0x30, 0xa6, + 0x60, 0x26, 0x14, 0xf2, 0xb3, 0xe1, 0x08, 0x19, 0xdb, 0x3f, 0x69, 0x1f, 0x5b, 0x76, 0x73, 0x80, 0x00, 0x47, 0x96, + 0x81, 0x71, 0xef, 0x55, 0x2a, 0xda, 0xd3, 0x19, 0x8e, 0x51, 0xd5, 0xd2, 0x9a, 0xfb, 0x95, 0xaa, 0x24, 0x33, 0x60, + 0x37, 0x2f, 0x9a, 0xf6, 0xda, 0x22, 0x97, 0x28, 0xce, 0x90, 0xd5, 0xa9, 0x22, 0xb3, 0x30, 0xd6, 0xae, 0x92, 0x05, + 0x11, 0x1f, 0xfb, 0xc2, 0x19, 0xff, 0xd3, 0x94, 0xa0, 0xfc, 0xb8, 0xaf, 0xe9, 0xa4, 0x42, 0xa5, 0xb0, 0x8f, 0xcb, + 0x69, 0x7c, 0xc5, 0xc0, 0xa4, 0x02, 0x1b, 0x1a, 0xcc, 0x8f, 0x9b, 0x60, 0x50, 0x75, 0x00, 0x8f, 0x6e, 0x1a, 0xc5, + 0x5b, 0x06, 0xdf, 0x94, 0x2e, 0xb4, 0x1c, 0xe5, 0xa8, 0x1c, 0x70, 0xe6, 0xc8, 0xad, 0xc9, 0x4a, 0x04, 0x57, 0xd6, + 0x0e, 0x52, 0x54, 0x60, 0xb8, 0xb3, 0x6a, 0x90, 0xe6, 0xd2, 0x23, 0x25, 0xe3, 0xaf, 0x0b, 0xd0, 0x02, 0x08, 0xc3, + 0xca, 0xbf, 0xdc, 0x2c, 0xe3, 0x15, 0xca, 0x4a, 0xe9, 0x54, 0x73, 0x98, 0x26, 0xa6, 0xa5, 0x53, 0x7a, 0x3a, 0xe1, + 0x8d, 0xe7, 0x88, 0x73, 0x41, 0x50, 0x3b, 0xd5, 0x7c, 0x6f, 0x30, 0x0c, 0xea, 0xa4, 0x33, 0x40, 0x3e, 0x6a, 0x1a, + 0x4c, 0x78, 0x6d, 0xc9, 0xd1, 0x8b, 0xb8, 0xae, 0xc0, 0x72, 0x62, 0x67, 0x57, 0x09, 0x20, 0x97, 0xd6, 0xc2, 0x0e, + 0x32, 0x30, 0x96, 0xf1, 0xc7, 0x40, 0xee, 0xf8, 0xf4, 0x49, 0x69, 0xd9, 0x23, 0xeb, 0x95, 0x0d, 0x17, 0x9f, 0x7c, + 0x32, 0x7a, 0x83, 0xa5, 0xa2, 0xc9, 0xfd, 0x67, 0x63, 0x41, 0xdf, 0xc9, 0x06, 0x63, 0x2d, 0x3a, 0x07, 0x51, 0xae, + 0x42, 0x3b, 0xf2, 0x55, 0x59, 0x97, 0x05, 0xd9, 0xbe, 0x01, 0x29, 0x3f, 0xa7, 0x15, 0xa1, 0x54, 0x0b, 0x2a, 0x79, + 0x87, 0x55, 0x5c, 0x10, 0x42, 0xa9, 0x01, 0x07, 0x65, 0x08, 0x70, 0x94, 0x71, 0x77, 0xa7, 0x91, 0x00, 0x90, 0x8a, + 0xa1, 0x60, 0xce, 0x5c, 0xd6, 0xde, 0xe2, 0x02, 0x5b, 0x9c, 0x33, 0x73, 0x8d, 0xe1, 0x79, 0x2d, 0xcc, 0xd7, 0x1c, + 0x2b, 0x12, 0xc8, 0xda, 0x72, 0xba, 0x16, 0x5d, 0xaa, 0xa7, 0x47, 0x43, 0x81, 0xcc, 0x4a, 0x72, 0xee, 0xe5, 0xf3, + 0x0a, 0xa1, 0x95, 0xe4, 0xbf, 0x59, 0x62, 0x03, 0xc6, 0x38, 0xb1, 0x7f, 0x7c, 0xa1, 0x42, 0x7e, 0xe4, 0x71, 0xe3, + 0x90, 0x1f, 0x87, 0x13, 0x9c, 0x52, 0x02, 0x06, 0xb5, 0xf6, 0x39, 0x67, 0x7a, 0x63, 0xce, 0x44, 0x4c, 0x9c, 0xf0, + 0xf2, 0x3d, 0x7c, 0x64, 0xc0, 0x28, 0x45, 0xdb, 0x41, 0x45, 0xca, 0xb4, 0x02, 0x48, 0x68, 0xda, 0x1e, 0x5a, 0xaf, + 0xc5, 0xa4, 0xac, 0x98, 0xe2, 0x9a, 0xe6, 0x8a, 0xb7, 0xac, 0x1d, 0x35, 0xb5, 0x6f, 0x3c, 0x93, 0x78, 0x88, 0xe3, + 0xe7, 0x89, 0xaf, 0x13, 0x24, 0x84, 0x48, 0xa9, 0xf0, 0x17, 0x67, 0x5b, 0x3d, 0x7e, 0x49, 0xc5, 0xbd, 0xf0, 0x01, + 0x74, 0x8c, 0xa1, 0x31, 0x9b, 0x0a, 0x71, 0x43, 0x36, 0x43, 0xe2, 0xa8, 0x73, 0x23, 0xd3, 0xdd, 0x66, 0xd5, 0xe1, + 0xc3, 0xc9, 0xf2, 0xd3, 0xec, 0xb1, 0x17, 0x23, 0xc8, 0x60, 0xad, 0xd3, 0x8a, 0xdb, 0xe1, 0xb7, 0xff, 0x5b, 0xae, + 0x48, 0x8f, 0xea, 0xda, 0x22, 0xd1, 0xf9, 0x15, 0x12, 0x4c, 0x8b, 0xa4, 0xc8, 0xea, 0x5d, 0xca, 0x64, 0xdb, 0x2b, + 0x36, 0x5e, 0xcb, 0xe0, 0xb2, 0xb0, 0x38, 0x15, 0xf3, 0xcb, 0x5e, 0x8b, 0xc9, 0xae, 0xaf, 0x8b, 0xaf, 0xca, 0xcc, + 0x39, 0x56, 0x9e, 0x21, 0x8c, 0x85, 0xbd, 0x2e, 0x68, 0x53, 0x5a, 0xd8, 0x74, 0x50, 0x3d, 0x86, 0x29, 0xe3, 0xd1, + 0x6b, 0x47, 0x42, 0x7a, 0xa8, 0x75, 0xff, 0x26, 0xaf, 0xaf, 0xa3, 0xc2, 0x00, 0x10, 0x97, 0x22, 0x2c, 0x3c, 0x9e, + 0xc1, 0xe0, 0x12, 0x83, 0xc2, 0x3b, 0xec, 0xf4, 0xe2, 0x1a, 0xce, 0x3b, 0x37, 0xae, 0xd4, 0x72, 0x8a, 0x2d, 0x1d, + 0x27, 0x5f, 0x48, 0xcf, 0x7a, 0x45, 0x81, 0xbe, 0xa5, 0x66, 0x2d, 0x6e, 0xcd, 0x69, 0x0a, 0xc5, 0x90, 0xb2, 0xab, + 0xf6, 0x74, 0xef, 0xd4, 0xb5, 0x3d, 0x3b, 0x1f, 0xd6, 0x35, 0x45, 0xbb, 0x92, 0xa8, 0xf4, 0x1c, 0x91, 0x18, 0x2b, + 0x86, 0x76, 0xae, 0xad, 0xeb, 0xa2, 0x8e, 0x6a, 0xa8, 0xd6, 0x35, 0x46, 0xaa, 0x6e, 0x29, 0xd5, 0xbf, 0xd4, 0x7a, + 0x5c, 0x7a, 0x6d, 0x30, 0xf4, 0x9e, 0x3c, 0x8a, 0x97, 0x89, 0xba, 0x94, 0xdb, 0x4b, 0x9f, 0xea, 0x75, 0xbb, 0x7d, + 0xeb, 0xbb, 0xff, 0x53, 0xd0, 0xd6, 0xc5, 0x37, 0xf1, 0x3f, 0xc8, 0xff, 0x67, 0x0f, 0x18, 0x5b, 0x7c, 0x7c, 0x38, + 0xae, 0xb4, 0x59, 0x57, 0x36, 0x39, 0x25, 0x8f, 0x9d, 0x6f, 0xfa, 0x8a, 0xa5, 0x83, 0xba, 0xbb, 0x3b, 0x39, 0x0b, + 0x0e, 0x9b, 0x33, 0x47, 0x30, 0x50, 0x94, 0xc9, 0xcd, 0x95, 0xd1, 0xa6, 0xeb, 0x74, 0xa9, 0xc3, 0x2f, 0x4b, 0x93, + 0x90, 0xbd, 0xc6, 0x4b, 0x8c, 0x60, 0x9e, 0x4b, 0x99, 0xd8, 0x02, 0x5e, 0x39, 0x43, 0x51, 0x0f, 0x1d, 0x5b, 0x4a, + 0x30, 0x65, 0xd5, 0x20, 0x3e, 0xcb, 0x14, 0xcf, 0x51, 0x65, 0x1a, 0xd5, 0x73, 0xf7, 0xa6, 0x07, 0x8c, 0xc8, 0xc8, + 0xd9, 0xaf, 0x32, 0xeb, 0x42, 0x07, 0xeb, 0xf6, 0x6c, 0x7f, 0xc4, 0xb3, 0x52, 0x62, 0xc0, 0xbd, 0xb3, 0x01, 0xb1, + 0x43, 0x63, 0x95, 0x43, 0xa1, 0xf8, 0xc7, 0xad, 0x70, 0x99, 0xa8, 0xcf, 0x78, 0xcb, 0x5e, 0xb2, 0xb8, 0x0d, 0xcd, + 0xac, 0x43, 0xbe, 0x33, 0x15, 0x44, 0xec, 0x5d, 0xa7, 0xea, 0x39, 0x42, 0xd6, 0x94, 0x7a, 0xfc, 0x59, 0xa2, 0x2c, + 0x8d, 0xa8, 0xc4, 0xd1, 0xa8, 0x1a, 0x0b, 0xff, 0x77, 0xe6, 0x12, 0xdd, 0xc9, 0xfe, 0x19, 0x61, 0xe6, 0xbe, 0x22, + 0x56, 0x2e, 0xe1, 0x84, 0xe9, 0xd5, 0x36, 0x9d, 0x15, 0x22, 0x28, 0xe0, 0xf3, 0x45, 0xef, 0xcd, 0xa6, 0x2e, 0x04, + 0x8d, 0x77, 0x79, 0xde, 0x85, 0xf9, 0x8c, 0xdc, 0x08, 0xcd, 0x34, 0xac, 0x4d, 0x89, 0x72, 0x10, 0xb8, 0xe8, 0x09, + 0x34, 0xe7, 0x32, 0x30, 0xc1, 0x34, 0x2f, 0xb6, 0x7e, 0xd2, 0xd6, 0x99, 0x1e, 0x48, 0x43, 0x8c, 0x5a, 0x64, 0x9e, + 0xde, 0x95, 0xa6, 0x8f, 0xe9, 0xac, 0xd2, 0x3a, 0x6b, 0x03, 0x2b, 0x7e, 0x40, 0x31, 0x13, 0x41, 0xab, 0x97, 0x24, + 0xa9, 0xa0, 0x39, 0xb4, 0xe8, 0x65, 0xaf, 0x3a, 0x49, 0x41, 0xdd, 0xa9, 0x25, 0xe3, 0x82, 0xb0, 0xaf, 0x6d, 0xf7, + 0x84, 0xcc, 0x51, 0xb4, 0x41, 0x9a, 0x92, 0x4b, 0xbe, 0x47, 0x5c, 0x65, 0x3c, 0xff, 0xbc, 0x50, 0xf8, 0x02, 0x58, + 0x6e, 0x7f, 0x57, 0x96, 0xc3, 0xa2, 0x2e, 0x16, 0xbf, 0x9c, 0x80, 0x35, 0xf2, 0x8f, 0xe1, 0xfe, 0x28, 0x20, 0x1a, + 0x7e, 0x56, 0xb0, 0x3b, 0x68, 0xb3, 0x5f, 0x8c, 0xb3, 0xdd, 0xc7, 0xbd, 0xc5, 0x6e, 0xb8, 0xec, 0xf8, 0xcb, 0x27, + 0xeb, 0xf6, 0xb4, 0x07, 0xae, 0x0d, 0x83, 0xdb, 0x5f, 0x9c, 0xbf, 0xa6, 0xc1, 0xf3, 0x2d, 0x63, 0x37, 0x5b, 0xf9, + 0x90, 0xdf, 0xa3, 0xdc, 0xa9, 0xcb, 0xe5, 0x52, 0xd4, 0x10, 0x90, 0x6a, 0xe6, 0x9c, 0xb8, 0x7e, 0x52, 0x90, 0x16, + 0x68, 0x61, 0x4a, 0x47, 0xb7, 0x24, 0xde, 0x8b, 0x86, 0xac, 0x37, 0x17, 0x60, 0xd3, 0x6d, 0x2f, 0x90, 0x4a, 0x8a, + 0x99, 0x22, 0x2d, 0x26, 0x14, 0x3f, 0xe7, 0xa8, 0x93, 0x3b, 0xb8, 0x2f, 0xa1, 0x4d, 0x64, 0x58, 0x77, 0x93, 0x71, + 0xfe, 0x54, 0xed, 0x09, 0x3d, 0x6e, 0x18, 0xab, 0x43, 0x7e, 0x8b, 0x34, 0xa0, 0xf7, 0xe3, 0x99, 0x14, 0xd9, 0x0f, + 0x83, 0x02, 0xf8, 0xd4, 0x55, 0x40, 0xb5, 0x40, 0xbf, 0xe5, 0xe3, 0x89, 0x4c, 0x19, 0xc4, 0xa8, 0xec, 0x0d, 0xd0, + 0x48, 0x50, 0x64, 0x9b, 0xb2, 0x78, 0x3f, 0x2d, 0x08, 0xe8, 0x43, 0x09, 0x9d, 0xe9, 0x93, 0x0c, 0x11, 0xd5, 0x15, + 0x3c, 0xcc, 0xe9, 0x4e, 0xf8, 0xa6, 0xce, 0x87, 0xcf, 0x9d, 0xb1, 0xe7, 0x2d, 0xed, 0x0a, 0x5b, 0x86, 0x69, 0x68, + 0xa8, 0x82, 0x71, 0xe8, 0x5e, 0xb4, 0xf4, 0x14, 0xb7, 0xa1, 0xe4, 0xe3, 0xf1, 0xe7, 0xf2, 0xcb, 0x44, 0xd4, 0xdf, + 0x26, 0x32, 0xcc, 0x08, 0x7a, 0x66, 0x51, 0x2f, 0xae, 0x70, 0x61, 0x74, 0xba, 0x6a, 0x20, 0x68, 0x79, 0xbf, 0xad, + 0x07, 0xd7, 0xf9, 0x31, 0xdc, 0x38, 0x57, 0x89, 0x36, 0x4e, 0xe3, 0xde, 0x6f, 0x50, 0x5c, 0x2e, 0x71, 0xd0, 0xe5, + 0x05, 0x12, 0xdf, 0x07, 0xd7, 0x76, 0x59, 0xed, 0x6d, 0xaa, 0xf9, 0xcb, 0x7a, 0xe5, 0x0d, 0x09, 0x3b, 0x6f, 0x12, + 0x0e, 0xa4, 0x84, 0x0c, 0x27, 0x1f, 0x91, 0x5c, 0xd8, 0xa0, 0x4b, 0x3e, 0xde, 0xd2, 0xd0, 0x51, 0xc3, 0x8a, 0x68, + 0x17, 0x7e, 0xc3, 0x8f, 0x75, 0x5b, 0x88, 0x20, 0x6e, 0xb0, 0x4c, 0x00, 0xcf, 0x48, 0xe6, 0x44, 0x56, 0x75, 0x98, + 0xc0, 0x34, 0x97, 0x30, 0x0d, 0xec, 0xb6, 0x01, 0x34, 0x3e, 0x99, 0x94, 0xb8, 0x02, 0xdd, 0x2c, 0xd5, 0xce, 0xab, + 0x92, 0x8c, 0xfd, 0x85, 0xa0, 0x9d, 0xeb, 0x0f, 0x8f, 0x32, 0x2f, 0xb7, 0x9b, 0x5d, 0xa4, 0x79, 0x39, 0xc5, 0xd0, + 0x0e, 0x64, 0x76, 0x35, 0x0c, 0x99, 0xba, 0x4b, 0xea, 0x3c, 0x38, 0xa9, 0x2e, 0x0c, 0xc2, 0x21, 0x5c, 0x91, 0xa6, + 0x35, 0x17, 0x84, 0x59, 0x74, 0x6b, 0x0a, 0xef, 0x76, 0xc0, 0xd5, 0x12, 0x01, 0x25, 0x88, 0x38, 0xe9, 0x45, 0x87, + 0x55, 0x3c, 0xb8, 0x1b, 0x75, 0x67, 0x90, 0x96, 0x95, 0x8b, 0x95, 0x62, 0x7c, 0xa1, 0xc5, 0x9d, 0x60, 0x5a, 0x05, + 0xf7, 0x7e, 0x20, 0x46, 0x7b, 0xbe, 0x16, 0x4a, 0x1e, 0x63, 0xa4, 0xa2, 0x3c, 0xfa, 0xf6, 0x43, 0x4a, 0x7e, 0xd4, + 0xf0, 0x58, 0x2b, 0x94, 0x2a, 0x76, 0xea, 0xda, 0x05, 0x9d, 0x95, 0x08, 0xbb, 0x2c, 0x13, 0x2f, 0xa1, 0xdf, 0xd5, + 0xb0, 0xdb, 0x32, 0x1b, 0xbb, 0x40, 0xdc, 0x9e, 0x44, 0x4a, 0xe4, 0x60, 0xad, 0xe1, 0x1d, 0xc9, 0xf3, 0x34, 0x78, + 0x5b, 0x72, 0xeb, 0x97, 0x0c, 0xc5, 0xad, 0xb6, 0x60, 0xf1, 0x43, 0x7a, 0x74, 0x44, 0x01, 0xaa, 0x7f, 0xd3, 0x91, + 0x20, 0x71, 0xcb, 0x8c, 0xdf, 0x55, 0xe5, 0xe6, 0xb9, 0xb9, 0xe1, 0x59, 0x62, 0x55, 0xc4, 0xc2, 0xf9, 0xfb, 0x1a, + 0x20, 0x50, 0x48, 0x67, 0x33, 0xd7, 0x3c, 0x12, 0x75, 0xc5, 0xf5, 0xe0, 0x4e, 0x65, 0x4c, 0xdd, 0xa7, 0x23, 0xd5, + 0x1b, 0xee, 0x6a, 0x6c, 0xa9, 0x25, 0xbc, 0xa9, 0xb0, 0xa5, 0x3b, 0xcd, 0x15, 0x5b, 0x5c, 0xe6, 0x2a, 0xb5, 0x9d, + 0xc0, 0xb4, 0x6b, 0x9d, 0xfe, 0x38, 0x86, 0x1a, 0xca, 0x44, 0xdc, 0x26, 0xea, 0xe0, 0xb2, 0x6c, 0x8a, 0x72, 0x97, + 0x09, 0x4e, 0x92, 0x0d, 0xee, 0x80, 0x48, 0xd5, 0xe2, 0x32, 0xc7, 0x4d, 0x1b, 0x22, 0xc5, 0x77, 0xd2, 0x35, 0x45, + 0x52, 0x9c, 0xa6, 0x17, 0x9e, 0x46, 0x56, 0x6e, 0x76, 0x4a, 0x33, 0xe9, 0x1d, 0x52, 0x64, 0x45, 0x21, 0x71, 0xaf, + 0x22, 0x05, 0x0f, 0xad, 0xfa, 0xb3, 0xcc, 0x29, 0xd9, 0xc1, 0xf4, 0x6e, 0xb9, 0xee, 0xef, 0xc3, 0xc7, 0xf3, 0xf5, + 0x48, 0x44, 0x17, 0xc6, 0x57, 0x4a, 0x48, 0x56, 0x72, 0x90, 0x84, 0xbc, 0xe0, 0x90, 0xce, 0x5e, 0x15, 0x09, 0x38, + 0xd2, 0x2b, 0x17, 0x69, 0x5d, 0xb9, 0x56, 0x05, 0xda, 0xc1, 0x72, 0xca, 0xa8, 0x10, 0x33, 0x63, 0x8d, 0xe2, 0xfd, + 0x38, 0xbc, 0x3b, 0x1e, 0xa4, 0x3b, 0x92, 0x4d, 0xcd, 0x5c, 0x77, 0x28, 0x71, 0x19, 0x2a, 0x38, 0x12, 0x27, 0x2a, + 0x87, 0xe0, 0xe8, 0xcc, 0xf5, 0x1e, 0x0b, 0xeb, 0x0a, 0xe6, 0xcc, 0x79, 0x96, 0x07, 0xab, 0xab, 0xf5, 0x17, 0xee, + 0x7a, 0xfd, 0x7a, 0x22, 0xfa, 0x9d, 0x97, 0x9a, 0x6a, 0x80, 0x87, 0x16, 0xdb, 0x75, 0x3c, 0x8d, 0x29, 0xd0, 0x02, + 0xe9, 0xd5, 0x04, 0x45, 0xc3, 0x27, 0xcd, 0x30, 0x07, 0x3d, 0xd5, 0x37, 0x6f, 0xa3, 0x66, 0xb6, 0x65, 0x9a, 0x56, + 0x18, 0x66, 0x97, 0x06, 0xee, 0x4c, 0x72, 0x0d, 0x31, 0x6c, 0xfd, 0x7a, 0xb6, 0x4d, 0xe4, 0x72, 0xdf, 0xb3, 0x5a, + 0x08, 0xa6, 0xe9, 0x98, 0x23, 0xff, 0x3e, 0xc9, 0x61, 0xc2, 0x71, 0x0c, 0x4a, 0x4f, 0xbc, 0x2c, 0xc5, 0x2c, 0x27, + 0x61, 0x65, 0x5d, 0x5d, 0xc0, 0xf5, 0x64, 0x24, 0x02, 0xf1, 0x28, 0xb5, 0x58, 0x7e, 0x00, 0xd7, 0x54, 0x5e, 0xef, + 0x68, 0x63, 0x0f, 0x04, 0x00, 0xcb, 0xf6, 0xcc, 0x49, 0xaf, 0x1a, 0xf9, 0x2a, 0xa6, 0x90, 0x5c, 0xbe, 0x93, 0x4c, + 0x46, 0x04, 0xfb, 0x2a, 0xbd, 0xbf, 0xa0, 0x1f, 0xd4, 0xde, 0x8e, 0x10, 0xd1, 0x8b, 0x84, 0xfd, 0x72, 0x9b, 0x26, + 0x21, 0x0e, 0x5f, 0x98, 0x88, 0x8d, 0x0b, 0xd8, 0x8a, 0xd0, 0x97, 0xc7, 0x56, 0x26, 0xf3, 0xba, 0xeb, 0xf0, 0xfb, + 0x2d, 0x1f, 0xdc, 0x18, 0xc1, 0x24, 0xb2, 0x25, 0x02, 0x0b, 0x91, 0xca, 0x98, 0x28, 0xbe, 0x08, 0x3f, 0x57, 0xfb, + 0xbe, 0x51, 0x4c, 0x25, 0x9b, 0x3d, 0xdf, 0xf1, 0xee, 0x78, 0xfa, 0xae, 0xf5, 0xeb, 0xac, 0x90, 0x21, 0xd3, 0x5e, + 0xf7, 0x0f, 0x00, 0x3d, 0xf3, 0xa6, 0xbc, 0x9d, 0xcc, 0x77, 0x92, 0x76, 0x95, 0x36, 0xef, 0x34, 0xd1, 0xc0, 0xaf, + 0xbf, 0x11, 0x7a, 0xc5, 0x57, 0x9a, 0x88, 0x7e, 0xd5, 0x0b, 0x36, 0xab, 0xa8, 0x90, 0x67, 0xae, 0xc3, 0x9a, 0xf5, + 0xed, 0x1c, 0x9a, 0xbe, 0x29, 0xa5, 0x39, 0x4f, 0x06, 0x53, 0x87, 0xf4, 0x55, 0x46, 0x75, 0x30, 0xa0, 0xb9, 0x83, + 0x0d, 0xd2, 0xdf, 0x00, 0xcf, 0xb9, 0x83, 0x00, 0x27, 0x8a, 0x24, 0x0d, 0xbf, 0x74, 0xf3, 0x2a, 0x9a, 0x3c, 0x8c, + 0x9a, 0x0c, 0xc5, 0x65, 0x1b, 0xb8, 0x59, 0x0b, 0xca, 0x43, 0x83, 0xa8, 0xb3, 0xf7, 0x88, 0xdd, 0x5e, 0xda, 0x7b, + 0xf0, 0x9f, 0x3e, 0x50, 0xb2, 0x2e, 0x42, 0xc5, 0x60, 0x42, 0xf9, 0x4b, 0xd9, 0x2f, 0x69, 0xcf, 0x4a, 0x57, 0xe6, + 0x42, 0xc1, 0xbc, 0x36, 0xa8, 0xc6, 0x01, 0x2c, 0xa0, 0xbd, 0x48, 0x40, 0xc5, 0x6e, 0x2b, 0x6c, 0xc8, 0x04, 0xdb, + 0x4f, 0x62, 0x5d, 0x89, 0x1f, 0x0a, 0x70, 0xf8, 0x9b, 0x86, 0x90, 0x84, 0x2c, 0xe6, 0x7e, 0x9d, 0x97, 0x6d, 0x5b, + 0xc7, 0x6d, 0xcc, 0xe6, 0x91, 0xbc, 0x8d, 0xb0, 0x9c, 0xf0, 0xe6, 0x7f, 0x98, 0x07, 0xe2, 0xb2, 0xea, 0x6f, 0x6b, + 0xbb, 0xb4, 0xa3, 0xd7, 0x61, 0x58, 0x89, 0xad, 0x62, 0xf9, 0x87, 0xb9, 0xea, 0xb1, 0x03, 0xb8, 0xbf, 0x87, 0xca, + 0xf0, 0x96, 0xe6, 0x86, 0xb7, 0xe3, 0x79, 0xa9, 0x41, 0xf6, 0x99, 0x8a, 0x24, 0x9c, 0xd2, 0xfd, 0x8a, 0x64, 0x48, + 0x13, 0x89, 0x1e, 0x3d, 0xc9, 0x47, 0x1a, 0x0f, 0xab, 0x94, 0x6d, 0xe8, 0xb0, 0xd9, 0xee, 0xa0, 0xf3, 0xf6, 0xb9, + 0xfb, 0xcb, 0xa9, 0xb7, 0x40, 0xb5, 0x4e, 0x61, 0xf3, 0xd2, 0xc3, 0x16, 0x5b, 0xf7, 0x2c, 0xa6, 0x7e, 0x0a, 0xb2, + 0x72, 0x24, 0x1b, 0x62, 0x22, 0xdd, 0x3b, 0x2d, 0x9e, 0x79, 0x94, 0xc0, 0xdd, 0x4d, 0x7d, 0x73, 0xec, 0xe3, 0x79, + 0xc9, 0x1f, 0xb3, 0x33, 0xdc, 0xf3, 0xa1, 0x97, 0xef, 0x59, 0x91, 0x3b, 0x62, 0xa7, 0xa3, 0x78, 0xc8, 0x45, 0x77, + 0x42, 0x59, 0x09, 0xcb, 0xf9, 0xb9, 0x6a, 0xa5, 0x36, 0x06, 0xa5, 0x42, 0x59, 0x96, 0x7b, 0x9f, 0x6c, 0xdd, 0x41, + 0x42, 0x95, 0xef, 0x41, 0x49, 0xe0, 0xf7, 0x49, 0x04, 0x52, 0xfd, 0xa3, 0x54, 0x21, 0x66, 0xcc, 0x4b, 0x33, 0x2f, + 0xd4, 0x9f, 0x50, 0xca, 0x81, 0x87, 0x80, 0x6f, 0x09, 0xb8, 0x34, 0xb4, 0xf5, 0xdf, 0x6d, 0x18, 0xd3, 0xb2, 0x1f, + 0x6b, 0xf4, 0xf7, 0x14, 0xf8, 0xbd, 0x06, 0xee, 0x89, 0x3a, 0x3f, 0x9d, 0x62, 0xf0, 0x68, 0xa1, 0xd7, 0xb7, 0xd3, + 0x7d, 0xc3, 0xd4, 0x78, 0xe5, 0x42, 0xf1, 0xad, 0x4d, 0xe5, 0x8f, 0xa1, 0x76, 0x5d, 0x0b, 0x4d, 0xf6, 0x42, 0x39, + 0x53, 0x8a, 0xb3, 0xc2, 0x9b, 0x06, 0x43, 0x2b, 0x1e, 0x49, 0xb5, 0xc4, 0x39, 0x7b, 0x8f, 0x5d, 0xc5, 0x09, 0xef, + 0x48, 0x03, 0x05, 0x2a, 0x99, 0x05, 0x47, 0x0c, 0x94, 0xf6, 0x65, 0x7d, 0x99, 0xee, 0xf6, 0x63, 0x2d, 0xee, 0xe0, + 0x78, 0x84, 0xaa, 0x22, 0x5f, 0x21, 0x27, 0x1e, 0x39, 0x90, 0x28, 0xf5, 0xfc, 0x26, 0xaa, 0xd0, 0xfc, 0x74, 0xa2, + 0x68, 0x7f, 0x97, 0x0d, 0xad, 0xe8, 0x3f, 0x1b, 0xfe, 0xec, 0x11, 0x20, 0x8f, 0x73, 0xd2, 0xf7, 0x09, 0xb6, 0x4c, + 0x88, 0xc6, 0xe5, 0xd8, 0xb7, 0x35, 0x78, 0x5e, 0x6a, 0xb4, 0x58, 0xf4, 0x72, 0x21, 0xfb, 0x8d, 0xd9, 0x58, 0x89, + 0x39, 0x73, 0xe1, 0x8d, 0x3b, 0xa1, 0xe1, 0x37, 0x0c, 0xa4, 0xf7, 0xb0, 0x9e, 0x84, 0x99, 0x66, 0x01, 0x28, 0x35, + 0xb4, 0xd0, 0x47, 0x8b, 0x9d, 0xeb, 0x3c, 0x39, 0xde, 0xf0, 0xa6, 0x14, 0x01, 0xe3, 0xeb, 0xfb, 0x1b, 0x42, 0x33, + 0x50, 0x24, 0x25, 0x62, 0x3e, 0x01, 0x8c, 0x62, 0x30, 0x6a, 0xaa, 0xd7, 0xe3, 0x01, 0x9f, 0x60, 0x10, 0x5f, 0x6c, + 0x7d, 0x79, 0x33, 0x6e, 0x20, 0xee, 0x86, 0xb3, 0x94, 0x4f, 0xc9, 0x77, 0x23, 0x01, 0x8c, 0x97, 0x7f, 0x02, 0x54, + 0x17, 0x5a, 0x6d, 0xb0, 0xbf, 0x13, 0xdb, 0x71, 0x7e, 0x01, 0x4d, 0xf0, 0x35, 0xd8, 0x05, 0x3f, 0x8e, 0x7f, 0x28, + 0xac, 0x6e, 0xa4, 0xa5, 0x5e, 0x4e, 0x47, 0x7d, 0xb6, 0x99, 0xf9, 0x00, 0x1b, 0xce, 0x37, 0x0f, 0x1b, 0xe5, 0xfa, + 0x8b, 0x09, 0x03, 0xf3, 0xca, 0x09, 0xa5, 0x9b, 0x23, 0x99, 0x5f, 0x32, 0xac, 0xcd, 0x1a, 0xfa, 0x5c, 0x4e, 0x5c, + 0xf2, 0x2d, 0x90, 0x6b, 0xa4, 0xda, 0x6f, 0xf1, 0xf2, 0x1b, 0xa4, 0x1e, 0x96, 0xef, 0x7f, 0x56, 0x4a, 0x17, 0xb1, + 0xa9, 0xcd, 0x7e, 0xe4, 0xb8, 0x4b, 0x9f, 0xcb, 0x93, 0xcf, 0x90, 0xfd, 0xd9, 0x1c, 0xf2, 0x79, 0x7f, 0xb5, 0x7b, + 0xb7, 0xfc, 0x33, 0x9b, 0xed, 0xc5, 0x66, 0xd7, 0xdb, 0xbb, 0xf9, 0x33, 0xf8, 0x3a, 0xfc, 0x1e, 0xc1, 0x6a, 0x6e, + 0x0f, 0x0a, 0x3e, 0x4c, 0xdf, 0xfc, 0xd7, 0x6f, 0xf6, 0x83, 0x81, 0x66, 0x1f, 0xa2, 0x00, 0x57, 0x88, 0xa8, 0x72, + 0x66, 0x5c, 0xc3, 0xae, 0xb8, 0xc7, 0xf6, 0x38, 0xe5, 0x7c, 0x5f, 0x9b, 0x50, 0x6e, 0xb3, 0x98, 0x36, 0x2b, 0x57, + 0x57, 0x38, 0x13, 0xdd, 0xfa, 0x86, 0x5d, 0x08, 0xc9, 0xf2, 0xed, 0x5d, 0xc0, 0x43, 0x31, 0x2a, 0xec, 0xed, 0xb9, + 0xe7, 0xe5, 0xc0, 0x9f, 0xa1, 0xbc, 0xc6, 0x4b, 0xab, 0xdf, 0xfa, 0x5c, 0xec, 0xa1, 0x0f, 0x78, 0x33, 0x7e, 0x2b, + 0xa8, 0xce, 0x7c, 0x16, 0x9a, 0x2c, 0x2e, 0xc4, 0x97, 0xba, 0xc1, 0x25, 0xf4, 0x32, 0xc7, 0x64, 0x03, 0x5f, 0x3a, + 0x5c, 0x55, 0xc8, 0x3c, 0xb4, 0x64, 0x94, 0x47, 0x6c, 0x39, 0x31, 0x73, 0xb7, 0x9a, 0x64, 0x5a, 0x99, 0x1f, 0xdd, + 0xc8, 0xc1, 0x83, 0x12, 0x92, 0x74, 0x65, 0x08, 0xff, 0x18, 0x27, 0xee, 0x45, 0xb0, 0xf1, 0x5e, 0x58, 0x8b, 0x76, + 0xf5, 0x50, 0x35, 0xeb, 0x26, 0x68, 0xd6, 0xa9, 0x1e, 0xef, 0x84, 0xbf, 0xa7, 0x7f, 0x3c, 0xd3, 0x46, 0xf8, 0xf3, + 0x99, 0x56, 0xc2, 0x1f, 0xa7, 0x8a, 0x09, 0x7f, 0x9e, 0xea, 0xcc, 0xd4, 0xfa, 0xc2, 0xbe, 0x7e, 0x63, 0x5f, 0xdf, + 0xd9, 0x63, 0xa0, 0xf6, 0xd0, 0xde, 0xcb, 0x1c, 0xb4, 0x13, 0x7b, 0x5b, 0x6f, 0xc9, 0x21, 0x9f, 0xcb, 0x2a, 0x4b, + 0x36, 0x3f, 0x37, 0xba, 0xfb, 0x9c, 0xca, 0xc2, 0xe3, 0x01, 0xca, 0x1a, 0x8f, 0xa3, 0xba, 0x16, 0x51, 0x31, 0x67, + 0x96, 0xb4, 0x5e, 0x3a, 0xbb, 0x7b, 0x28, 0x9e, 0x4e, 0x1c, 0x43, 0x0e, 0xaa, 0x86, 0x33, 0x52, 0x99, 0x20, 0x7f, + 0x34, 0xfd, 0xd8, 0x28, 0xf7, 0xa2, 0xf1, 0xc2, 0xdd, 0x3d, 0x53, 0x9e, 0xbf, 0x92, 0x46, 0x44, 0xa6, 0x15, 0xf8, + 0xde, 0xc1, 0x34, 0x2c, 0x66, 0x2d, 0x36, 0x3b, 0x20, 0xb3, 0x23, 0x7a, 0x09, 0x05, 0x42, 0xe8, 0xdb, 0x16, 0xfe, + 0xb3, 0x00, 0xa5, 0x62, 0x57, 0x46, 0x89, 0x78, 0x5c, 0x83, 0xa2, 0xd6, 0x5b, 0xd0, 0x20, 0x76, 0x43, 0x99, 0xee, + 0x88, 0x31, 0x87, 0x97, 0x55, 0x5c, 0x41, 0x56, 0xbf, 0x9c, 0x8b, 0x5f, 0xe7, 0xec, 0xe1, 0xf9, 0x46, 0x40, 0x83, + 0xff, 0xd7, 0x64, 0x3b, 0xe8, 0x4f, 0x68, 0x6b, 0x9c, 0x72, 0x49, 0xa5, 0xfd, 0x42, 0xce, 0xdb, 0x73, 0x5f, 0x67, + 0xd7, 0xb7, 0xce, 0x18, 0xc9, 0xcf, 0x39, 0x04, 0x72, 0x57, 0xed, 0xa7, 0xbb, 0x7d, 0x4c, 0x41, 0x56, 0x5d, 0xf7, + 0x9c, 0x60, 0x9d, 0x9d, 0xa9, 0xd4, 0xcd, 0x94, 0xfc, 0xfc, 0xd5, 0xff, 0xb2, 0xbf, 0x4f, 0xc9, 0x87, 0x7d, 0xad, + 0xd7, 0xfc, 0x72, 0x2c, 0xe7, 0x53, 0xaf, 0xf3, 0xf9, 0xfd, 0x17, 0xe5, 0x74, 0x3d, 0xa4, 0xe9, 0x38, 0xdd, 0xf5, + 0x8f, 0xe9, 0x82, 0x7e, 0xe9, 0x3e, 0x9b, 0xfe, 0x7a, 0x4a, 0x3e, 0xe4, 0x1b, 0xbd, 0x7e, 0x72, 0x97, 0x16, 0xff, + 0xa9, 0xe9, 0x72, 0x64, 0x0b, 0x00, 0xe5, 0xf9, 0x51, 0xb2, 0x39, 0x0e, 0x39, 0xd3, 0x3b, 0xd7, 0x15, 0xb6, 0xa8, + 0x5a, 0x0d, 0x8e, 0x5c, 0xac, 0x40, 0x8b, 0x7c, 0xc2, 0x13, 0xd9, 0xf8, 0x06, 0xec, 0x52, 0x66, 0xef, 0xb1, 0x0a, + 0xa4, 0xdb, 0xe6, 0xd3, 0x6c, 0x26, 0xcf, 0x5f, 0xa3, 0x6d, 0x76, 0x0d, 0xcb, 0x4c, 0xda, 0xd2, 0x54, 0x5c, 0x79, + 0xc0, 0x81, 0x03, 0x14, 0x06, 0xab, 0x91, 0x3a, 0x00, 0x46, 0x4e, 0xef, 0x30, 0xf4, 0xd9, 0x38, 0xce, 0xde, 0x6f, + 0x63, 0xc6, 0x52, 0x78, 0xec, 0xc8, 0xa2, 0x19, 0xed, 0xf0, 0x09, 0x37, 0xfd, 0x69, 0x26, 0xd4, 0x8f, 0x06, 0xe0, + 0xc4, 0x59, 0x36, 0x5d, 0x7f, 0xbb, 0x4f, 0x3c, 0xeb, 0x5a, 0xae, 0xac, 0x3f, 0x94, 0xd0, 0xb5, 0x39, 0x3a, 0x93, + 0xdc, 0x25, 0xa8, 0x30, 0xc2, 0x9c, 0xe1, 0xc5, 0x7b, 0xb3, 0x3a, 0xa5, 0x48, 0x7d, 0xa2, 0xd7, 0x82, 0x90, 0xd1, + 0x7f, 0x32, 0x98, 0xc6, 0x91, 0x1c, 0xb9, 0x3e, 0xf6, 0xef, 0x31, 0x43, 0xdb, 0xcc, 0xa8, 0xb7, 0x1a, 0x3b, 0x20, + 0xd2, 0xc0, 0x2a, 0x59, 0x63, 0x7d, 0x4b, 0xfc, 0x6b, 0x90, 0xeb, 0x34, 0x6b, 0x3c, 0xc3, 0xd9, 0x99, 0xb6, 0x27, + 0x3b, 0xdd, 0xcc, 0xdc, 0xaf, 0xb7, 0x3f, 0x8f, 0xdf, 0xdb, 0xef, 0x87, 0x71, 0xe9, 0x48, 0x0b, 0x72, 0xd3, 0x62, + 0xab, 0x7a, 0x6c, 0x9d, 0x4c, 0xcb, 0x0f, 0xd5, 0x8f, 0x0d, 0x0a, 0xc4, 0x18, 0xe7, 0x35, 0xd2, 0x8c, 0xcf, 0xf2, + 0x36, 0x2e, 0xc8, 0x82, 0x0d, 0x71, 0x3e, 0xdc, 0xde, 0x3e, 0x0a, 0xb2, 0x03, 0x4d, 0x7e, 0xf3, 0x0e, 0xdd, 0xd7, + 0x7e, 0xe7, 0x77, 0xa0, 0x98, 0xf5, 0xed, 0x25, 0xd5, 0x06, 0x75, 0x05, 0x7a, 0x03, 0x2e, 0xbf, 0x68, 0x13, 0xe6, + 0xae, 0xe1, 0xbc, 0xfc, 0x19, 0x0b, 0x49, 0x28, 0x5a, 0x29, 0x38, 0x2c, 0x36, 0xcd, 0x28, 0x4a, 0x8b, 0x75, 0xd1, + 0xaf, 0x6d, 0xc6, 0x9b, 0x6b, 0x37, 0x70, 0x6e, 0x10, 0x64, 0x31, 0x6b, 0x45, 0x3f, 0x46, 0xe4, 0x5d, 0xd6, 0xcc, + 0x56, 0xdb, 0x40, 0x0c, 0x2f, 0x71, 0xcd, 0x5a, 0x6c, 0x77, 0xcf, 0x60, 0x40, 0x8f, 0xf8, 0xe8, 0x1c, 0x82, 0x47, + 0x1e, 0x7a, 0x2c, 0x7e, 0x37, 0xa5, 0xc3, 0x3b, 0xc7, 0x5a, 0x0a, 0x91, 0xe5, 0x64, 0xfa, 0xc7, 0xa9, 0xcd, 0xc8, + 0xd3, 0x11, 0x25, 0x43, 0xa2, 0xfa, 0xc6, 0x3e, 0xfb, 0xd1, 0x20, 0xad, 0x3d, 0x9b, 0x35, 0x8e, 0x17, 0x2c, 0xad, + 0x0f, 0x8e, 0x87, 0x71, 0x1c, 0xa4, 0xa8, 0xa9, 0xac, 0xe2, 0x1f, 0x93, 0x61, 0x91, 0xae, 0xd9, 0x7e, 0x57, 0x06, + 0xaf, 0x84, 0x84, 0xae, 0x5c, 0x7b, 0x2d, 0x40, 0xc7, 0x2e, 0x6b, 0xf9, 0x33, 0xa7, 0x0b, 0x01, 0x2a, 0xbf, 0x08, + 0x93, 0x00, 0x43, 0x24, 0xca, 0x13, 0x79, 0xe1, 0x45, 0xd1, 0x47, 0x30, 0x87, 0x66, 0x58, 0x0d, 0xa6, 0xa9, 0xe8, + 0xaf, 0xd7, 0x99, 0x50, 0xc6, 0x01, 0xbc, 0xc8, 0xed, 0xcd, 0xc7, 0x32, 0x74, 0x28, 0xd2, 0x46, 0xce, 0x3c, 0x33, + 0xa7, 0xbc, 0xa1, 0x3e, 0x14, 0xa1, 0xf8, 0x4f, 0x30, 0x48, 0x72, 0x36, 0x00, 0x85, 0xac, 0x3d, 0x8f, 0x00, 0x60, + 0x49, 0x3f, 0x31, 0x03, 0x6f, 0xf8, 0xa7, 0xb3, 0xf1, 0x65, 0x91, 0xb1, 0x5f, 0xfd, 0x9b, 0x4b, 0xd3, 0x2c, 0xdc, + 0x59, 0xd5, 0xb3, 0x7b, 0x8a, 0x20, 0x0a, 0x24, 0x59, 0x28, 0x27, 0xf6, 0x1e, 0xe2, 0x57, 0x06, 0x98, 0x41, 0x48, + 0x06, 0x48, 0xc2, 0x74, 0xa4, 0x75, 0xe6, 0x27, 0xd7, 0x84, 0xad, 0xde, 0x16, 0x4a, 0xb0, 0x88, 0xce, 0xa3, 0xdb, + 0x2a, 0xcd, 0x5a, 0xba, 0x1f, 0xf6, 0xe6, 0x20, 0xe3, 0xe4, 0xcb, 0xca, 0x79, 0xe2, 0x3c, 0x7f, 0x43, 0x22, 0x7b, + 0x11, 0x51, 0x5e, 0x6e, 0x5d, 0x44, 0x7e, 0x05, 0xa5, 0x62, 0x03, 0xa0, 0x1a, 0x09, 0x53, 0x4d, 0xf1, 0xee, 0x17, + 0x77, 0xc0, 0x28, 0x1f, 0x68, 0x4f, 0x28, 0xee, 0x27, 0x2b, 0x63, 0x3d, 0xcc, 0x83, 0xcc, 0x22, 0x2e, 0x57, 0xa3, + 0xcd, 0x4e, 0x68, 0xa4, 0x5e, 0xd1, 0x04, 0x03, 0x1a, 0x96, 0xe1, 0x74, 0x6a, 0xeb, 0x1f, 0x05, 0xcb, 0x6c, 0xba, + 0x4e, 0xcf, 0x71, 0x29, 0x74, 0x5b, 0xf7, 0x49, 0x21, 0x8e, 0x86, 0xd0, 0xed, 0x28, 0x14, 0x46, 0x3f, 0xab, 0xf0, + 0xa2, 0x3e, 0xeb, 0xd7, 0xa2, 0x33, 0xd6, 0x98, 0xa1, 0xab, 0x2e, 0xbb, 0xa1, 0x00, 0x6c, 0xc4, 0xce, 0x10, 0x05, + 0x72, 0x7b, 0xd5, 0x2e, 0xd7, 0x70, 0x2f, 0x62, 0xf2, 0x1f, 0x5a, 0xef, 0x37, 0xda, 0x4f, 0xe0, 0x8f, 0x92, 0xcc, + 0xad, 0x09, 0xac, 0xa9, 0x9c, 0x97, 0xc4, 0x01, 0xd1, 0xe3, 0x7c, 0x3f, 0x08, 0xfe, 0xa4, 0xb5, 0x78, 0x50, 0x6c, + 0x11, 0xd2, 0x4a, 0xa9, 0x13, 0xf5, 0xda, 0x3a, 0x4d, 0xe4, 0x83, 0xc4, 0xa4, 0x62, 0x42, 0x75, 0x5a, 0xfb, 0x69, + 0x5e, 0xab, 0x59, 0xe0, 0x6d, 0xa2, 0x12, 0xaf, 0x30, 0x9d, 0xdb, 0x25, 0x43, 0xd2, 0x1d, 0xc1, 0xa9, 0x2e, 0x2b, + 0x86, 0xbb, 0xdb, 0xd6, 0xec, 0x17, 0x03, 0x5f, 0xd3, 0x35, 0x1c, 0xe3, 0x2e, 0xe8, 0xdc, 0x18, 0x6f, 0x88, 0xed, + 0xc1, 0xe0, 0x61, 0xf5, 0xe4, 0xec, 0xb4, 0x9a, 0x6e, 0x1a, 0x95, 0xb9, 0xb9, 0xd7, 0xd4, 0xd5, 0x42, 0xbf, 0x4a, + 0x60, 0xf9, 0x6c, 0x94, 0x6f, 0xb1, 0x67, 0xae, 0x51, 0x60, 0x6b, 0x89, 0xbb, 0x65, 0xd9, 0xb1, 0x18, 0xbd, 0x1b, + 0x18, 0x15, 0xd6, 0x2e, 0x62, 0xf0, 0xfc, 0x1c, 0xf6, 0xf6, 0xc4, 0x04, 0x42, 0xfd, 0x9b, 0x7a, 0xb2, 0x80, 0x8b, + 0x59, 0x1a, 0xc9, 0xb0, 0x1e, 0x94, 0xbd, 0x27, 0x5a, 0xfa, 0x88, 0xe7, 0x82, 0x60, 0xdb, 0x76, 0x8a, 0xad, 0x6b, + 0x18, 0x03, 0x1f, 0xba, 0xc6, 0x1d, 0x04, 0xd7, 0xec, 0x56, 0x34, 0xcf, 0xe0, 0x31, 0x88, 0x39, 0x30, 0xc2, 0x7c, + 0x5e, 0xaa, 0xba, 0x7d, 0xa2, 0xb3, 0xff, 0x02, 0x42, 0x31, 0xbb, 0xd5, 0xe5, 0xd6, 0x69, 0xe3, 0x21, 0x43, 0x16, + 0x64, 0x2c, 0x49, 0x8a, 0x8c, 0xfc, 0xa6, 0xa3, 0x2d, 0x63, 0xd1, 0x33, 0x97, 0x71, 0x4b, 0x6a, 0x42, 0xa9, 0xce, + 0xa6, 0x21, 0x51, 0x5e, 0x8f, 0xb0, 0xa8, 0x42, 0xec, 0x36, 0x87, 0x54, 0xce, 0x5c, 0x11, 0x99, 0xe2, 0x49, 0xda, + 0x66, 0x67, 0xb0, 0x46, 0x90, 0xa1, 0x60, 0x82, 0xaa, 0xf6, 0xfd, 0xe8, 0xfe, 0x86, 0x79, 0x30, 0xa6, 0xa9, 0x7a, + 0x78, 0xc3, 0x94, 0x89, 0xf0, 0x24, 0x6d, 0x6f, 0x3b, 0x62, 0xdb, 0xba, 0x8e, 0xf3, 0xec, 0x7b, 0xf2, 0x56, 0x8e, + 0x2c, 0xd9, 0x9a, 0xb2, 0x35, 0x15, 0xfb, 0xa0, 0x16, 0x15, 0x65, 0x28, 0x91, 0x48, 0x60, 0x2b, 0xea, 0xed, 0xa5, + 0x3a, 0x1b, 0x88, 0xf4, 0xca, 0x7a, 0xbf, 0x26, 0xcf, 0xe9, 0xda, 0x4a, 0x29, 0x58, 0x40, 0x21, 0x2c, 0x34, 0xf6, + 0xac, 0xef, 0x7f, 0x9c, 0xd7, 0xb0, 0x1f, 0x7e, 0xcc, 0x88, 0x2d, 0xfc, 0xea, 0xfd, 0x7a, 0x82, 0x01, 0x46, 0xdd, + 0x8b, 0xae, 0x48, 0xdf, 0x1b, 0xfa, 0x1e, 0xe9, 0xbb, 0xaf, 0xef, 0x66, 0x3f, 0xd0, 0x35, 0xd6, 0x86, 0x37, 0xee, + 0xdc, 0x42, 0xeb, 0x88, 0x2f, 0xb1, 0xd4, 0x41, 0x7f, 0x5b, 0x7e, 0x9a, 0xa8, 0xb2, 0x5f, 0x5b, 0x49, 0x29, 0x9b, + 0xbe, 0x24, 0x55, 0x1b, 0xba, 0x8c, 0x90, 0xba, 0x57, 0x82, 0xb7, 0x6f, 0x9d, 0xba, 0xba, 0x2d, 0xe5, 0xa7, 0xe3, + 0x62, 0xfc, 0xf2, 0xaf, 0x0e, 0xf1, 0x52, 0xc6, 0x74, 0xe8, 0xca, 0x3b, 0xef, 0xd9, 0x4a, 0x8d, 0x2b, 0xcc, 0x09, + 0xe7, 0x78, 0xb2, 0x90, 0x31, 0xea, 0xa1, 0xdc, 0xb9, 0x03, 0x2e, 0x23, 0x08, 0x7c, 0x45, 0x57, 0x55, 0x8a, 0x59, + 0xea, 0x3b, 0x06, 0x96, 0xf9, 0x3e, 0xd1, 0xe5, 0xf0, 0xf7, 0x02, 0x09, 0x7d, 0xea, 0xaa, 0x72, 0xed, 0x4a, 0x35, + 0x62, 0x6c, 0x8a, 0x24, 0xe7, 0x64, 0xbd, 0xcb, 0x8b, 0xbc, 0xf0, 0xd7, 0xd3, 0xaa, 0xa6, 0x03, 0x52, 0xcc, 0x4a, + 0x2c, 0xca, 0xa9, 0x58, 0x94, 0x22, 0x62, 0xfb, 0x12, 0x86, 0xca, 0x66, 0x12, 0x88, 0xbc, 0xb7, 0x73, 0x91, 0x58, + 0xbe, 0x02, 0x18, 0xac, 0xaa, 0x0f, 0x84, 0xe4, 0x77, 0x76, 0xd9, 0x25, 0x3f, 0xf6, 0x57, 0x4a, 0x26, 0x93, 0x56, + 0x18, 0x02, 0x77, 0xc4, 0x6f, 0x9f, 0x0e, 0x18, 0x13, 0x9c, 0x33, 0xda, 0x18, 0x30, 0xe7, 0xa6, 0x69, 0x48, 0xaa, + 0x9a, 0xb5, 0xca, 0xdd, 0xbc, 0xc2, 0x4c, 0x48, 0x62, 0xa8, 0xca, 0xcd, 0xf0, 0x6b, 0x3d, 0x52, 0x90, 0xf3, 0xf7, + 0x5c, 0x5d, 0x90, 0x31, 0x2a, 0x67, 0x3a, 0x99, 0x04, 0x5f, 0x07, 0xf0, 0x01, 0x73, 0x2b, 0x3e, 0xf8, 0xc7, 0x59, + 0xca, 0x23, 0x1b, 0xd0, 0x03, 0xb5, 0x43, 0x35, 0x56, 0x2d, 0x25, 0x0a, 0x13, 0x09, 0xa1, 0x0c, 0x3e, 0xe2, 0x33, + 0x99, 0x8b, 0x39, 0xab, 0xfb, 0x5c, 0x2c, 0xdb, 0x0e, 0x24, 0x06, 0x44, 0x99, 0x10, 0x49, 0x4e, 0x6a, 0xdd, 0x50, + 0x61, 0x71, 0x74, 0x69, 0xb1, 0x88, 0x13, 0x24, 0xd3, 0x7a, 0x21, 0xf8, 0x97, 0x1d, 0x5b, 0xc9, 0xf1, 0xa6, 0xff, + 0x66, 0x34, 0x57, 0x23, 0x33, 0xd9, 0x45, 0x6b, 0x5e, 0xf4, 0x8b, 0xb4, 0xe6, 0xf2, 0x21, 0x51, 0xe8, 0x1f, 0x68, + 0xdd, 0x5b, 0xd6, 0x10, 0x29, 0x58, 0xd2, 0x15, 0x7d, 0x45, 0xdb, 0x5d, 0x7a, 0x59, 0xe0, 0x71, 0xf7, 0x31, 0x41, + 0x82, 0xef, 0xb6, 0x8a, 0x97, 0xc0, 0x45, 0xb2, 0xc6, 0x9e, 0xfb, 0x44, 0x16, 0x1d, 0xd3, 0x8d, 0xd2, 0xd5, 0x91, + 0x9d, 0xd3, 0x37, 0x88, 0x92, 0x9c, 0x5d, 0x2b, 0x31, 0xf5, 0x3f, 0x47, 0xdd, 0xe4, 0xa2, 0xb2, 0x67, 0x87, 0x1c, + 0xc4, 0xcd, 0xd4, 0x42, 0x98, 0x92, 0xbd, 0x13, 0xd8, 0x08, 0x91, 0x91, 0x62, 0x52, 0x04, 0x25, 0xf7, 0x92, 0x2f, + 0x89, 0x14, 0xf2, 0x47, 0xa5, 0x86, 0xb6, 0x4c, 0xe9, 0x7f, 0xb4, 0x8e, 0xf0, 0xed, 0x0c, 0x27, 0x49, 0xf9, 0xe4, + 0x05, 0xb7, 0xad, 0xdd, 0x8e, 0xd9, 0x20, 0x09, 0xf7, 0xcf, 0x2c, 0x9f, 0xf5, 0xf6, 0x20, 0xff, 0x50, 0x26, 0x04, + 0x78, 0xeb, 0x9b, 0x5e, 0xd5, 0x2f, 0x01, 0xa3, 0x92, 0x6a, 0x51, 0x79, 0x3b, 0x39, 0x97, 0x38, 0xe5, 0xf2, 0x02, + 0x7e, 0xf9, 0x7e, 0xce, 0x81, 0x79, 0xf8, 0x4a, 0x5b, 0x4d, 0x14, 0xec, 0x87, 0xc3, 0x1e, 0x43, 0xc9, 0x0a, 0x1b, + 0xd9, 0xcd, 0xc6, 0xb8, 0x0b, 0x5d, 0x6b, 0xb3, 0x7e, 0x0a, 0xa9, 0x57, 0x77, 0x9c, 0xde, 0xc5, 0x55, 0x8d, 0x34, + 0xb7, 0x69, 0xc4, 0x51, 0xc9, 0x4c, 0x07, 0x72, 0x87, 0x69, 0x3a, 0x66, 0x6f, 0x23, 0xc1, 0xf2, 0xcd, 0x59, 0x5b, + 0x79, 0x2b, 0xef, 0x49, 0x08, 0x98, 0x70, 0xc0, 0x5c, 0xd1, 0xb0, 0x56, 0x0e, 0x82, 0x39, 0xf6, 0x7b, 0xad, 0x90, + 0x98, 0xaa, 0x48, 0x7a, 0x36, 0xf1, 0xb9, 0x56, 0x6b, 0xcf, 0x1a, 0x09, 0x59, 0x3a, 0x05, 0x8e, 0x35, 0x4f, 0x14, + 0x0c, 0x65, 0x6a, 0x56, 0xcb, 0x3c, 0xe0, 0x2a, 0x7b, 0xae, 0xe5, 0x95, 0x40, 0x0c, 0x1c, 0x14, 0x90, 0x9c, 0xf8, + 0x1e, 0xee, 0x49, 0xec, 0x3b, 0x73, 0x86, 0xc6, 0x4c, 0x86, 0xa8, 0xce, 0x4a, 0x15, 0x58, 0xd7, 0xdb, 0xc0, 0x54, + 0x51, 0x6b, 0x6e, 0xe8, 0x9e, 0x0c, 0xd6, 0xd7, 0x38, 0x3c, 0x10, 0xf6, 0xf8, 0x82, 0xdc, 0x87, 0xbf, 0xcf, 0xca, + 0x63, 0x67, 0x0b, 0x68, 0xc0, 0x7a, 0xf3, 0x1c, 0x39, 0xa2, 0xeb, 0x2d, 0x95, 0x2b, 0x5b, 0xd0, 0x65, 0xaf, 0xe9, + 0xfd, 0xd3, 0x41, 0x75, 0xb9, 0xdc, 0xcc, 0x8c, 0xa8, 0xe2, 0xc9, 0x24, 0x09, 0xfa, 0x10, 0x33, 0x28, 0xa9, 0x79, + 0x2a, 0xeb, 0x88, 0x89, 0x7b, 0xb0, 0xbc, 0x23, 0x13, 0xd3, 0x25, 0x98, 0xe3, 0x9c, 0xad, 0x8b, 0x06, 0x87, 0x1c, + 0x0e, 0x58, 0xa2, 0x4b, 0x1e, 0xdc, 0xfb, 0x56, 0x56, 0x6a, 0xd1, 0x97, 0x63, 0xa9, 0x84, 0xdc, 0x00, 0x36, 0x76, + 0xb4, 0x93, 0x0b, 0xe5, 0xd4, 0x4e, 0x10, 0xec, 0xe4, 0xa6, 0xf6, 0x0e, 0x49, 0x06, 0xc8, 0x1e, 0x08, 0x55, 0x19, + 0xf0, 0x79, 0x5d, 0x11, 0x00, 0x9a, 0xe3, 0x12, 0x89, 0x3f, 0x08, 0xe3, 0xe5, 0x37, 0x8a, 0x41, 0x83, 0xe3, 0x6e, + 0x66, 0x38, 0x78, 0x1d, 0xc0, 0x28, 0x8d, 0x12, 0xf3, 0x23, 0x10, 0xe5, 0x62, 0x7f, 0x95, 0x18, 0x1d, 0x29, 0x44, + 0x38, 0xf4, 0x8b, 0xdd, 0x85, 0xb4, 0xf5, 0x16, 0x6c, 0x65, 0x36, 0x2b, 0xb3, 0x5d, 0x03, 0x68, 0x1f, 0xa9, 0x01, + 0xd0, 0x66, 0xc3, 0x43, 0x3f, 0x37, 0xdd, 0x67, 0x3e, 0x43, 0xf7, 0x8e, 0x22, 0xf0, 0xeb, 0x32, 0x35, 0xdf, 0xc2, + 0x05, 0x4c, 0x33, 0xf5, 0x5e, 0x5e, 0x2d, 0xf7, 0x75, 0xb7, 0x63, 0x20, 0xbc, 0x5c, 0x62, 0x8f, 0xf8, 0x29, 0xab, + 0xe2, 0xa0, 0xc7, 0x1c, 0xbd, 0x46, 0x90, 0x66, 0x66, 0x69, 0xc8, 0x73, 0x0b, 0xe5, 0x33, 0x92, 0x03, 0xf9, 0x98, + 0x2c, 0x0f, 0xd9, 0xcb, 0x70, 0xe5, 0xa1, 0xae, 0x23, 0x24, 0x55, 0x90, 0x7a, 0x02, 0xcf, 0xd5, 0x0c, 0xc2, 0xb2, + 0x8f, 0xe7, 0xc4, 0xdc, 0xf3, 0xb7, 0x29, 0x68, 0xe4, 0x40, 0xa5, 0xf3, 0x93, 0xb2, 0x80, 0xdc, 0x43, 0x1d, 0xa6, + 0x98, 0xf1, 0xa0, 0x97, 0x5d, 0x95, 0x64, 0x38, 0x1a, 0xa1, 0xf1, 0x84, 0x82, 0xe4, 0x05, 0xc1, 0xd1, 0x57, 0x4b, + 0xe5, 0xaf, 0x24, 0xe5, 0x4d, 0x56, 0x96, 0x0d, 0xee, 0x25, 0x5e, 0x64, 0x0d, 0x9c, 0x59, 0xd8, 0xd1, 0xa6, 0xac, + 0x19, 0x13, 0x04, 0x80, 0x0f, 0x87, 0xe0, 0xc8, 0x22, 0x62, 0x59, 0x44, 0x13, 0xc5, 0x38, 0x96, 0x3f, 0x7b, 0xf8, + 0xa6, 0x08, 0xae, 0xd7, 0x91, 0xa0, 0x85, 0xc0, 0x27, 0x0e, 0xc0, 0x33, 0x33, 0x88, 0x78, 0xb6, 0xda, 0x3c, 0x02, + 0x13, 0xa9, 0xb5, 0x1f, 0x8d, 0xf8, 0x84, 0x13, 0xe7, 0xb8, 0x44, 0x1a, 0xb9, 0x5d, 0x8b, 0xc3, 0x21, 0x91, 0x89, + 0x9d, 0x39, 0xf2, 0xb1, 0xf1, 0x5d, 0x91, 0xff, 0x65, 0x16, 0x3d, 0x29, 0x51, 0x73, 0x39, 0x52, 0xbc, 0x69, 0x1b, + 0xa2, 0x55, 0xc6, 0xb5, 0xf4, 0x72, 0x98, 0x30, 0x58, 0xc0, 0xfe, 0xdf, 0x7c, 0x30, 0x1a, 0x8d, 0x95, 0xf3, 0x31, + 0xb8, 0xe2, 0x21, 0x3a, 0x6c, 0x38, 0x6a, 0x7d, 0xdd, 0x34, 0x99, 0x93, 0x0f, 0xfb, 0x6f, 0x7b, 0xb3, 0xeb, 0x6e, + 0x23, 0xea, 0x5c, 0x4a, 0x73, 0xe6, 0x85, 0xd0, 0x87, 0x91, 0xd5, 0x8b, 0x35, 0xe6, 0x84, 0xe6, 0x97, 0x8e, 0xa8, + 0x56, 0x1c, 0x9e, 0x3e, 0x08, 0xc5, 0xcb, 0xd1, 0x3e, 0xc4, 0x01, 0xb1, 0xa1, 0x44, 0xd9, 0x33, 0x95, 0x90, 0xc6, + 0x31, 0xb0, 0x5e, 0x85, 0x83, 0x40, 0xe0, 0xb4, 0x61, 0xbb, 0x66, 0xfd, 0x16, 0x2b, 0x4a, 0xc8, 0x21, 0xd5, 0x64, + 0xd9, 0x8c, 0x1f, 0x62, 0x47, 0x13, 0x94, 0x48, 0x29, 0x5a, 0x36, 0xfd, 0xc3, 0xb6, 0x23, 0x07, 0x2f, 0xa1, 0x21, + 0x71, 0x04, 0x2f, 0xbd, 0xf3, 0x87, 0xfd, 0x37, 0xeb, 0x23, 0xcf, 0x51, 0xbf, 0xe5, 0x21, 0xdf, 0xf2, 0x1c, 0xed, + 0x43, 0x1e, 0xf2, 0x21, 0xcf, 0x11, 0x3f, 0xe4, 0x41, 0xb2, 0x38, 0x4f, 0x5f, 0xdb, 0x7f, 0x0e, 0xc7, 0x4c, 0xa1, + 0x5c, 0x9e, 0x89, 0xad, 0xe4, 0xe8, 0x17, 0x1f, 0x32, 0xee, 0xb3, 0x89, 0x94, 0x3c, 0x21, 0x5e, 0x89, 0x12, 0x97, + 0xac, 0x2c, 0x93, 0x02, 0xf8, 0x34, 0xc4, 0xa7, 0x37, 0xdb, 0x77, 0xfc, 0x23, 0xac, 0x51, 0x74, 0x26, 0xe2, 0xc5, + 0x98, 0x8c, 0xd3, 0x3d, 0x73, 0xeb, 0x85, 0xad, 0x89, 0x90, 0x2c, 0x67, 0x04, 0x6d, 0x08, 0x71, 0xc8, 0x88, 0x91, + 0xcb, 0xf9, 0x64, 0xb4, 0xc4, 0xd0, 0xb7, 0xef, 0xa2, 0xd7, 0xcc, 0xfe, 0x1c, 0x03, 0x48, 0x95, 0xe7, 0x06, 0x64, + 0xe0, 0x18, 0x7b, 0xf5, 0x31, 0xd6, 0xa7, 0xe7, 0x45, 0x15, 0x0d, 0xba, 0x26, 0x87, 0x63, 0x4e, 0x90, 0x64, 0xa4, + 0x7f, 0xc5, 0xba, 0xec, 0x2c, 0x5d, 0x36, 0xaf, 0xc2, 0x3d, 0x61, 0xbe, 0x04, 0xb4, 0x28, 0x21, 0xd5, 0xcc, 0x72, + 0x45, 0x34, 0x9a, 0xd3, 0x9e, 0x79, 0xa4, 0xc9, 0x52, 0x6d, 0x97, 0x85, 0xbb, 0xec, 0xf1, 0x0b, 0xfe, 0xe1, 0x9e, + 0xe6, 0x66, 0x91, 0x41, 0xfb, 0x22, 0x67, 0xf7, 0xde, 0x15, 0xb6, 0xb5, 0x06, 0xf3, 0x13, 0x29, 0xd6, 0x22, 0x7c, + 0x35, 0xa6, 0x17, 0xa4, 0x3d, 0x7c, 0x83, 0x22, 0x1a, 0xdf, 0x67, 0x13, 0x5b, 0x86, 0xf6, 0x96, 0x7c, 0x6d, 0xa9, + 0xc9, 0x66, 0xc5, 0x1a, 0x2c, 0xb9, 0xfd, 0xf6, 0x25, 0xb5, 0x43, 0x97, 0xb9, 0x28, 0xb2, 0x49, 0x75, 0x53, 0xac, + 0x4d, 0x81, 0x2f, 0xc9, 0x56, 0x84, 0x8e, 0x40, 0x51, 0xb9, 0xcb, 0xe2, 0x70, 0xb9, 0xaa, 0xe5, 0xd4, 0x94, 0x10, + 0x69, 0xc8, 0x2a, 0xcc, 0xf4, 0x52, 0x7c, 0xbc, 0x38, 0x14, 0x21, 0xe5, 0x28, 0xa1, 0x33, 0xb5, 0x9c, 0xae, 0x6b, + 0xf5, 0xb7, 0x50, 0xe0, 0xe0, 0x4b, 0x5e, 0x43, 0x2c, 0x61, 0xa9, 0x6a, 0x0e, 0x11, 0x67, 0x9e, 0xdd, 0xd0, 0x95, + 0x87, 0xfd, 0xf7, 0x21, 0x04, 0x79, 0xb1, 0xfd, 0x94, 0xae, 0x5d, 0x9f, 0x91, 0x49, 0x20, 0x91, 0x84, 0x02, 0x80, + 0x03, 0x00, 0xae, 0x7a, 0x05, 0xab, 0x02, 0x93, 0x5e, 0xab, 0xc0, 0xc0, 0x14, 0x3c, 0x41, 0x99, 0xa1, 0x1d, 0xe0, + 0xf2, 0x47, 0xa4, 0xf4, 0xda, 0x21, 0x5b, 0x4c, 0x04, 0x34, 0x94, 0xc0, 0x31, 0xa1, 0xdd, 0x16, 0xe3, 0xe5, 0x25, + 0x0a, 0x2f, 0x89, 0xd2, 0x51, 0xdb, 0x02, 0x34, 0x90, 0x57, 0xb3, 0x2c, 0x9c, 0x96, 0x91, 0xe7, 0x6b, 0x13, 0xde, + 0xf2, 0x76, 0x5d, 0x6e, 0x3f, 0xb2, 0x35, 0x4d, 0x21, 0x1b, 0x82, 0x7d, 0xcf, 0xd6, 0x3d, 0x63, 0xa8, 0x10, 0x6f, + 0xb1, 0x1c, 0x7a, 0xe3, 0xba, 0x56, 0x1b, 0xd2, 0x87, 0x3e, 0x7a, 0x28, 0xba, 0x72, 0x37, 0x8c, 0x04, 0x5a, 0x4b, + 0x04, 0xab, 0xe1, 0x39, 0x03, 0xed, 0x26, 0x2f, 0x25, 0x47, 0x41, 0xaa, 0x02, 0x1f, 0xd2, 0xcd, 0x37, 0x2c, 0x66, + 0xb0, 0xeb, 0x36, 0x0b, 0xe4, 0x6a, 0xa0, 0xf5, 0xf1, 0x3b, 0x0d, 0x7b, 0x7d, 0x02, 0x36, 0x96, 0x2e, 0x57, 0xdb, + 0x2e, 0x8a, 0xa3, 0xe6, 0x8a, 0xe6, 0xae, 0xed, 0x14, 0xfa, 0x73, 0xf1, 0x39, 0xdc, 0x9e, 0x27, 0x8d, 0xef, 0xf3, + 0x13, 0xe1, 0x7b, 0x97, 0x35, 0xde, 0x1b, 0x0d, 0xa8, 0x3f, 0xce, 0xc4, 0x58, 0x8b, 0x1c, 0x15, 0x65, 0xe0, 0xa3, + 0x59, 0x2d, 0xee, 0xa0, 0x29, 0x32, 0xde, 0x6b, 0x04, 0x77, 0x9b, 0x5a, 0x65, 0x70, 0xaf, 0xb5, 0x01, 0x7d, 0x8f, + 0x83, 0xd4, 0xbd, 0x36, 0xda, 0xd9, 0xb9, 0x96, 0xa0, 0x16, 0x23, 0xa3, 0x95, 0x66, 0x63, 0xbb, 0x0d, 0xdd, 0xba, + 0xa5, 0x7e, 0x41, 0x9f, 0xca, 0xc9, 0x81, 0xec, 0xac, 0xae, 0x4a, 0xc5, 0xa4, 0x25, 0x78, 0x8b, 0xeb, 0x7b, 0x65, + 0x4a, 0x64, 0xe0, 0x56, 0x75, 0x99, 0x30, 0x12, 0x07, 0xb0, 0xf8, 0xc8, 0x9d, 0xf1, 0xeb, 0x07, 0xa8, 0x14, 0x1e, + 0x9f, 0x8b, 0x52, 0x78, 0xfc, 0x41, 0xf8, 0x4c, 0x7d, 0x05, 0x91, 0x9a, 0xba, 0xff, 0x32, 0x8f, 0x4a, 0xe5, 0x9b, + 0xbd, 0x6c, 0xec, 0x85, 0x79, 0x1b, 0x40, 0xbe, 0x4d, 0x33, 0x31, 0x98, 0xf9, 0x27, 0x27, 0xba, 0xdb, 0x10, 0x65, + 0x73, 0x0f, 0xd1, 0x7b, 0x45, 0x1d, 0x86, 0x8e, 0xa1, 0x43, 0x91, 0x1a, 0xb7, 0x75, 0x5c, 0x5f, 0xb2, 0x02, 0x9e, + 0xf4, 0xdf, 0x79, 0x7c, 0x52, 0x75, 0xfb, 0x0d, 0x4b, 0x9c, 0x49, 0xa4, 0x92, 0x8a, 0x4d, 0x27, 0xee, 0x2a, 0x35, + 0x59, 0xee, 0xbd, 0xed, 0x90, 0xa0, 0x2d, 0x32, 0xc8, 0x54, 0xef, 0x57, 0x24, 0xce, 0xa1, 0x66, 0x94, 0xa6, 0x82, + 0xa9, 0xac, 0xb2, 0xf2, 0x64, 0xde, 0x9c, 0x7f, 0xc4, 0xa7, 0x34, 0x1e, 0xf0, 0x31, 0x2c, 0x66, 0x23, 0xff, 0x7e, + 0xc4, 0xe8, 0xe8, 0xa6, 0x36, 0xac, 0x52, 0x26, 0x98, 0x56, 0x28, 0xe1, 0x63, 0x05, 0xc1, 0x09, 0x7e, 0x10, 0x4c, + 0x8e, 0x9c, 0x94, 0xac, 0x3c, 0x7e, 0xb3, 0xde, 0x62, 0xf8, 0x38, 0x33, 0xb1, 0xf1, 0x55, 0x9d, 0x69, 0x71, 0x87, + 0xee, 0xae, 0xf0, 0x72, 0xac, 0x4a, 0x86, 0x0b, 0xb2, 0x89, 0xb1, 0x8e, 0xc2, 0x67, 0x24, 0x29, 0x39, 0x91, 0xa7, + 0x74, 0x64, 0x32, 0x13, 0x38, 0x05, 0x67, 0x61, 0xfc, 0xa0, 0x36, 0xae, 0xa4, 0x6f, 0xa1, 0xa7, 0x45, 0xba, 0x3d, + 0x23, 0x0f, 0x76, 0x55, 0xef, 0x51, 0x16, 0x44, 0x19, 0x69, 0x30, 0x42, 0xda, 0xb2, 0xc4, 0xb8, 0x26, 0x62, 0x93, + 0x51, 0x18, 0x65, 0x5a, 0x6b, 0x2d, 0xbb, 0x16, 0x7f, 0x37, 0x54, 0x33, 0x4d, 0xbd, 0x5a, 0x9c, 0xfc, 0xc4, 0x24, + 0xad, 0x19, 0x2e, 0x5b, 0x7c, 0x78, 0xa1, 0xf6, 0xa8, 0x54, 0x07, 0x62, 0x6f, 0x67, 0x14, 0x4a, 0xb7, 0xef, 0x69, + 0x88, 0xa7, 0x46, 0xe7, 0xa5, 0xd3, 0x79, 0x75, 0xaa, 0xba, 0x6f, 0x4c, 0xd4, 0xb6, 0xfb, 0x66, 0x9c, 0xe2, 0x19, + 0xd0, 0x7c, 0x62, 0x04, 0x1d, 0xfa, 0x4f, 0x85, 0x06, 0x61, 0xd1, 0x30, 0xf3, 0xd9, 0x03, 0x18, 0xe9, 0xa6, 0x4c, + 0x6b, 0x46, 0x82, 0x7b, 0x8f, 0x60, 0xe0, 0x31, 0x69, 0x1e, 0xd9, 0x98, 0x4e, 0x18, 0x86, 0xa8, 0xa2, 0x93, 0xb3, + 0xec, 0x73, 0xf3, 0xfb, 0x3d, 0xea, 0xba, 0xdd, 0xb0, 0xa9, 0xe5, 0xbc, 0x87, 0xd3, 0xfb, 0xbf, 0xb9, 0xe8, 0xa4, + 0xbf, 0x9c, 0x5d, 0x5b, 0xe8, 0xc2, 0xe2, 0x7d, 0x1d, 0xf6, 0xbf, 0x91, 0xf9, 0xc8, 0x53, 0xa5, 0x77, 0x18, 0x03, + 0x19, 0x3a, 0xb3, 0x26, 0xca, 0x2f, 0x0c, 0xed, 0x28, 0x24, 0xd9, 0x89, 0xdd, 0x54, 0x4d, 0x90, 0x80, 0x48, 0x8c, + 0xa9, 0xef, 0x1c, 0x0c, 0xb4, 0x53, 0x9d, 0xc5, 0xa4, 0x2d, 0x5f, 0x81, 0x72, 0x5a, 0x06, 0x2c, 0x2f, 0x55, 0x78, + 0x76, 0x1d, 0xd4, 0xd4, 0xc7, 0x09, 0xc5, 0x56, 0x70, 0x3d, 0x64, 0x90, 0xaa, 0x12, 0x42, 0xa7, 0x29, 0x02, 0xbb, + 0x38, 0x36, 0xf1, 0xc7, 0x86, 0xf1, 0x03, 0xac, 0x81, 0xa6, 0xb5, 0xd8, 0xc2, 0x41, 0x52, 0xcc, 0xfc, 0x2d, 0x4d, + 0x8f, 0xab, 0xf4, 0xca, 0x3b, 0x05, 0xd6, 0x26, 0x98, 0xb2, 0x25, 0xb7, 0x6e, 0x2d, 0x42, 0x21, 0x8c, 0x59, 0xd7, + 0x90, 0xca, 0x3b, 0x24, 0x7f, 0x46, 0x16, 0xf0, 0x76, 0xbf, 0x54, 0x48, 0x3d, 0x2b, 0xcc, 0xae, 0x13, 0x74, 0x52, + 0x10, 0x47, 0xba, 0x44, 0xfc, 0xff, 0xca, 0x84, 0x10, 0x7c, 0xda, 0xf0, 0x6d, 0x09, 0x4d, 0x52, 0x9c, 0x5d, 0xb9, + 0x0b, 0x78, 0xec, 0x7a, 0xfd, 0x2c, 0x59, 0xa3, 0xef, 0xf0, 0xd9, 0x20, 0x16, 0xd8, 0x88, 0x9e, 0x9a, 0xd4, 0xb0, + 0xfa, 0xea, 0x95, 0xdd, 0xee, 0x11, 0xf5, 0x8d, 0x62, 0x0a, 0x15, 0xce, 0x7e, 0xf6, 0x94, 0x38, 0xed, 0x4d, 0xd3, + 0x5a, 0x92, 0xf2, 0xbf, 0xe4, 0x8e, 0x20, 0xf1, 0xaf, 0x37, 0x04, 0x05, 0x3c, 0x1b, 0x9e, 0x1a, 0x92, 0xfb, 0xfd, + 0x5b, 0x15, 0xaa, 0xbd, 0x0e, 0x66, 0x82, 0x2e, 0xbc, 0x07, 0x09, 0x8e, 0xfc, 0x20, 0xcb, 0xc6, 0x2f, 0x0a, 0x4b, + 0xdf, 0x98, 0x3b, 0xc4, 0x9e, 0x38, 0xd3, 0x93, 0xe6, 0x51, 0x7e, 0x58, 0xc1, 0x24, 0xdd, 0x21, 0x83, 0x7c, 0x7e, + 0x81, 0xb1, 0x77, 0x84, 0x95, 0x53, 0xb7, 0x7d, 0x79, 0x07, 0xb1, 0xac, 0x74, 0xc9, 0xbd, 0xd4, 0x9a, 0x12, 0x46, + 0x6d, 0x38, 0xcb, 0xab, 0x56, 0xf4, 0xe5, 0x76, 0xb6, 0xd1, 0x27, 0x2a, 0x20, 0x56, 0xdf, 0x33, 0x39, 0x2d, 0x91, + 0x61, 0xff, 0xb4, 0x5e, 0x4d, 0x9e, 0x0e, 0x43, 0x5d, 0x6b, 0x87, 0x54, 0xdb, 0xf5, 0x4e, 0x05, 0x0a, 0x8c, 0x2d, + 0xbd, 0xa5, 0x67, 0xe9, 0x70, 0xcc, 0x35, 0x78, 0xb1, 0x8c, 0xe0, 0x79, 0xea, 0x07, 0x78, 0x5f, 0x2d, 0x8f, 0x25, + 0xee, 0x1d, 0xdd, 0xf8, 0x02, 0x3a, 0x98, 0xce, 0x02, 0x0f, 0xbf, 0x1d, 0xb0, 0x4a, 0x36, 0x26, 0x73, 0xaf, 0x89, + 0x60, 0x40, 0x29, 0xfa, 0x60, 0xdf, 0x8d, 0x55, 0x4a, 0x34, 0x41, 0x3f, 0xb2, 0xd8, 0xa2, 0x5b, 0xdf, 0x56, 0x44, + 0x3c, 0xe3, 0x72, 0x54, 0x43, 0xfc, 0x29, 0x67, 0x2f, 0xb1, 0x4c, 0x18, 0xc9, 0xc2, 0xa0, 0x91, 0xbd, 0xe0, 0x23, + 0x4a, 0xcf, 0x0f, 0x2d, 0xed, 0xbe, 0x5d, 0x0f, 0x3f, 0x12, 0xc1, 0x5a, 0x1d, 0x84, 0x03, 0x15, 0x8a, 0xec, 0xd9, + 0xca, 0xcd, 0xc1, 0x0d, 0x19, 0x01, 0x28, 0x57, 0x40, 0x36, 0x16, 0xfc, 0xee, 0x86, 0xb0, 0xb8, 0x95, 0x8c, 0xcb, + 0xc4, 0x3e, 0x6f, 0x66, 0x22, 0x3d, 0x27, 0x57, 0x11, 0xe0, 0xe6, 0xa0, 0x99, 0x34, 0x8f, 0x2d, 0xb7, 0xa8, 0xb8, + 0x02, 0x6a, 0x42, 0x6d, 0xd1, 0x44, 0x54, 0x08, 0xd0, 0xeb, 0x69, 0x1f, 0x11, 0xb1, 0x4e, 0xc6, 0xc0, 0xb6, 0x2d, + 0x99, 0x54, 0x2a, 0xe3, 0x7a, 0xa7, 0x38, 0xdc, 0xb5, 0xdd, 0xfd, 0xdf, 0x64, 0x66, 0xcf, 0x60, 0x19, 0x5a, 0xac, + 0x65, 0x77, 0x7f, 0x14, 0xfb, 0xe3, 0x80, 0x06, 0x32, 0x3f, 0xd4, 0x41, 0xf2, 0xd7, 0x3a, 0x43, 0x5c, 0x4a, 0x41, + 0xf9, 0x10, 0x57, 0xb2, 0xc8, 0x05, 0xe9, 0x4e, 0xba, 0xca, 0x73, 0x99, 0x93, 0x7b, 0x40, 0x50, 0x1f, 0x08, 0x85, + 0x2c, 0x37, 0x90, 0xc6, 0x1b, 0x9c, 0x38, 0x6f, 0xe2, 0x91, 0x84, 0xb6, 0x9e, 0x49, 0x64, 0xb2, 0x68, 0xc7, 0x32, + 0xf0, 0xc9, 0xaf, 0xdd, 0x4f, 0x3e, 0xc6, 0x66, 0xe3, 0x40, 0x9b, 0xe5, 0xc9, 0x32, 0xcc, 0xaa, 0xad, 0x2a, 0x4e, + 0x58, 0x32, 0x99, 0x26, 0xbc, 0xfe, 0x2b, 0xac, 0xdc, 0x1a, 0xbe, 0x82, 0x0f, 0x66, 0xb6, 0x84, 0x4b, 0x9b, 0x6f, + 0x91, 0xa2, 0xc3, 0x30, 0xdd, 0xe4, 0xf8, 0x18, 0x13, 0xd3, 0xc5, 0x66, 0xc5, 0x30, 0x1a, 0x14, 0x89, 0xb7, 0x9b, + 0xaf, 0xf6, 0xef, 0x13, 0x38, 0x58, 0x2d, 0xc8, 0xd6, 0xee, 0xaf, 0xc1, 0x65, 0x0f, 0x59, 0x49, 0xd5, 0x98, 0x20, + 0xb4, 0x42, 0x1a, 0x43, 0xd4, 0x25, 0xfe, 0x55, 0x5f, 0x1e, 0xd2, 0xf5, 0xd7, 0x32, 0xa3, 0xf8, 0x5e, 0xfe, 0x5e, + 0xf8, 0x8e, 0x7f, 0x60, 0x2a, 0x69, 0xf3, 0x1c, 0xe1, 0xeb, 0xa0, 0x4b, 0x83, 0x84, 0xa8, 0x89, 0x7c, 0x5b, 0x32, + 0x40, 0xcc, 0x7a, 0x88, 0x01, 0xdc, 0x65, 0x75, 0xab, 0x24, 0x21, 0x63, 0xc1, 0x30, 0xd8, 0x16, 0x2b, 0xf3, 0x78, + 0x46, 0xea, 0x1d, 0xc6, 0x22, 0x71, 0x3e, 0x0f, 0x16, 0xec, 0xd4, 0x75, 0x26, 0xa6, 0x8b, 0xff, 0x98, 0x60, 0x81, + 0x25, 0x18, 0x6b, 0x2d, 0xfc, 0x74, 0x55, 0xc0, 0x9d, 0xe1, 0x43, 0x88, 0x02, 0xb7, 0xc9, 0x13, 0x3f, 0xd3, 0x3d, + 0xc5, 0x2e, 0x38, 0x95, 0x7a, 0x45, 0xd6, 0x9f, 0x03, 0xad, 0x5a, 0x73, 0xa9, 0xce, 0xee, 0x5c, 0x0e, 0xc2, 0x54, + 0x8b, 0xc2, 0x84, 0xd7, 0x51, 0xe2, 0xab, 0x6a, 0x39, 0x4d, 0x18, 0x5a, 0xbf, 0x0a, 0xf5, 0x27, 0xb9, 0xa8, 0x28, + 0xe1, 0xc4, 0x4d, 0xd2, 0x4d, 0x05, 0x07, 0xd4, 0xef, 0xed, 0xda, 0x84, 0xb7, 0x5e, 0xf0, 0x1c, 0x58, 0x50, 0xe8, + 0x39, 0x62, 0xf0, 0x8c, 0x91, 0xc1, 0xeb, 0xb2, 0x41, 0x07, 0xbd, 0x4c, 0x7f, 0xdb, 0x7e, 0xa9, 0xb5, 0xe7, 0xbb, + 0x59, 0xe4, 0x1c, 0xe4, 0xd9, 0xaf, 0xa6, 0xef, 0xef, 0x39, 0x13, 0xe3, 0xa2, 0x79, 0xd1, 0xd3, 0xe0, 0xb4, 0xdc, + 0xa0, 0xd9, 0x43, 0xd0, 0x31, 0xc3, 0xf6, 0x33, 0x2d, 0x2f, 0xc6, 0xf4, 0x9d, 0x38, 0xa6, 0x3d, 0xec, 0xfa, 0x99, + 0xb9, 0xa7, 0x17, 0x14, 0x68, 0xef, 0x89, 0xb7, 0x9d, 0xde, 0xe9, 0xea, 0xf3, 0xe5, 0x9a, 0x44, 0xdf, 0xac, 0xcb, + 0x75, 0x0b, 0xd0, 0xf5, 0x32, 0x16, 0x8d, 0x56, 0x65, 0x9f, 0x2b, 0xcf, 0xee, 0xf9, 0xbe, 0x30, 0xfd, 0x2d, 0xdc, + 0x4e, 0x86, 0x4c, 0xd2, 0x52, 0xb5, 0x52, 0x45, 0x93, 0x2e, 0x90, 0x58, 0x33, 0x49, 0xcb, 0x35, 0x1a, 0xcc, 0xf7, + 0xdd, 0xe5, 0xca, 0xf2, 0x27, 0x16, 0xa2, 0x52, 0x2f, 0xdf, 0x12, 0xa9, 0xcf, 0x06, 0x8b, 0x54, 0x84, 0x25, 0xca, + 0x8d, 0x67, 0x00, 0xab, 0x5d, 0x01, 0xd4, 0x9c, 0xa2, 0x97, 0x4b, 0x45, 0xc0, 0xc4, 0xe9, 0xa7, 0xfb, 0xcd, 0x14, + 0x86, 0xeb, 0xab, 0xb3, 0xf2, 0xda, 0x2f, 0x1a, 0xb9, 0xc4, 0x9f, 0x4f, 0x1f, 0x0a, 0x41, 0xa3, 0xee, 0x8a, 0xdf, + 0x5c, 0x48, 0x00, 0xe4, 0x10, 0xaf, 0xd5, 0x40, 0xba, 0x79, 0x4b, 0xba, 0x4e, 0x64, 0x5d, 0xbc, 0x4b, 0x05, 0x5c, + 0x59, 0xef, 0x80, 0x6e, 0x21, 0xdd, 0x6a, 0x89, 0x83, 0x84, 0x6e, 0xf8, 0x50, 0x70, 0x02, 0x25, 0xd8, 0xc9, 0x04, + 0x99, 0xbc, 0x53, 0xde, 0x12, 0x5e, 0x4d, 0x4c, 0x51, 0x10, 0xc9, 0xbd, 0x97, 0x68, 0xb7, 0x28, 0x79, 0x6b, 0xb0, + 0x09, 0xb1, 0xdb, 0x91, 0xc7, 0x7e, 0x72, 0xe4, 0xf5, 0xd2, 0xe6, 0x62, 0xa3, 0x32, 0x75, 0xf2, 0x92, 0xd2, 0x00, + 0xdb, 0x5b, 0x0a, 0x68, 0xe1, 0x2a, 0xa6, 0xba, 0x9c, 0xe6, 0x84, 0x16, 0xd3, 0x80, 0x33, 0x94, 0x1c, 0xfd, 0x4f, + 0x24, 0x1d, 0x6c, 0x1d, 0x7e, 0x72, 0xd1, 0x83, 0x17, 0xac, 0x35, 0xcd, 0x4a, 0x68, 0xb5, 0x27, 0xd8, 0x82, 0xe6, + 0x55, 0xf2, 0xa9, 0x51, 0x00, 0x9b, 0x17, 0x20, 0xab, 0x9f, 0x3e, 0xee, 0xc5, 0x23, 0xe7, 0xa7, 0x1c, 0xdc, 0x9e, + 0xea, 0x5b, 0x2f, 0x2c, 0x3b, 0xcd, 0x4a, 0x8a, 0x28, 0xc2, 0x93, 0xed, 0x85, 0xf8, 0xee, 0xcb, 0x48, 0x2e, 0x2e, + 0x13, 0x30, 0x43, 0x02, 0x22, 0xd8, 0xf7, 0xe4, 0x03, 0xdc, 0xa9, 0x81, 0x30, 0xad, 0xdf, 0x79, 0x10, 0x34, 0xad, + 0x33, 0x07, 0xc4, 0x4c, 0xc3, 0xec, 0xd2, 0x18, 0x70, 0xc3, 0xfb, 0xd7, 0x38, 0x77, 0x83, 0x7f, 0xac, 0xcc, 0x8a, + 0x66, 0xd3, 0xc0, 0xa6, 0x75, 0x43, 0x36, 0xc4, 0x85, 0x55, 0x8a, 0xc6, 0x55, 0xc6, 0x42, 0xd1, 0xe8, 0xd9, 0xab, + 0xcc, 0x52, 0xd9, 0x3f, 0x37, 0xad, 0x3f, 0xf6, 0x36, 0xb5, 0x25, 0x31, 0x6b, 0x29, 0x89, 0x86, 0xab, 0xcc, 0xcc, + 0xb1, 0x02, 0x10, 0x99, 0xe9, 0x43, 0x12, 0xd4, 0xe0, 0xeb, 0xf0, 0x85, 0x15, 0x53, 0xe5, 0x25, 0xbb, 0x1f, 0x32, + 0xfe, 0xf2, 0xd0, 0x41, 0xd1, 0x3b, 0x58, 0x85, 0x6f, 0x19, 0xf5, 0x9e, 0x06, 0x5d, 0xbf, 0xb4, 0x5a, 0x51, 0x97, + 0x9a, 0xe5, 0xe9, 0x67, 0xfc, 0x7e, 0x20, 0x9e, 0xc0, 0xfe, 0x54, 0x9c, 0xb1, 0x7d, 0x94, 0x7b, 0xc9, 0xe0, 0x9e, + 0xf4, 0x49, 0xea, 0x27, 0x34, 0x3a, 0x0a, 0xe7, 0x6d, 0xdd, 0xb7, 0x42, 0x5f, 0xb6, 0x27, 0x36, 0x8e, 0xa4, 0xee, + 0x52, 0xf2, 0x81, 0xb4, 0x75, 0xd0, 0x7d, 0x41, 0x90, 0xf0, 0x2b, 0xcb, 0x29, 0x05, 0x02, 0x13, 0x2e, 0x11, 0x47, + 0x08, 0xbc, 0x2e, 0xdd, 0x58, 0x40, 0x95, 0xe8, 0x03, 0xbb, 0xa5, 0x0d, 0xc1, 0xef, 0x40, 0xf8, 0xc5, 0x4e, 0x68, + 0x26, 0x57, 0x85, 0x9a, 0x99, 0x2a, 0x7b, 0x84, 0x36, 0x41, 0xcb, 0x89, 0xf4, 0xa4, 0x27, 0x0d, 0x26, 0xd0, 0x68, + 0xea, 0x95, 0x4f, 0x87, 0x60, 0xe8, 0x6a, 0x57, 0x5a, 0x1c, 0x58, 0x41, 0xc9, 0x40, 0xc3, 0x7a, 0x75, 0x29, 0x9d, + 0x16, 0x18, 0x03, 0x84, 0xe7, 0x5e, 0x5e, 0x36, 0x47, 0x2c, 0x7f, 0x77, 0x4b, 0x96, 0x1b, 0x1c, 0xf8, 0xd6, 0xc9, + 0xad, 0xe6, 0x92, 0x91, 0x9e, 0x9b, 0xbe, 0xed, 0xac, 0x9d, 0x28, 0x28, 0xab, 0xcd, 0x05, 0x0f, 0x01, 0x6a, 0x9a, + 0x7d, 0xd8, 0x62, 0x0b, 0x02, 0xce, 0x7a, 0x11, 0x12, 0xe4, 0x6d, 0x02, 0xbe, 0x7c, 0x3f, 0xc7, 0xde, 0x13, 0x51, + 0xb9, 0xac, 0xec, 0xc9, 0xe7, 0xb7, 0x8b, 0xaa, 0xbb, 0x25, 0x78, 0x56, 0x20, 0xdc, 0xf9, 0xc3, 0x38, 0xef, 0xeb, + 0xba, 0x57, 0x00, 0x58, 0x91, 0xf0, 0x49, 0x21, 0x07, 0x04, 0xa3, 0x99, 0x5e, 0xd5, 0xfd, 0x6d, 0xce, 0xa8, 0x29, + 0x9e, 0x22, 0x9c, 0x1c, 0x10, 0x7c, 0x67, 0x3a, 0x51, 0x9b, 0x95, 0xd6, 0x6a, 0x47, 0x64, 0x08, 0xa5, 0x6b, 0x8e, + 0xbb, 0x72, 0x03, 0x94, 0xbb, 0x48, 0x60, 0x86, 0x57, 0xb9, 0x2f, 0xc4, 0x87, 0x34, 0xbb, 0x6c, 0x19, 0xbc, 0x20, + 0x4f, 0xbb, 0x8a, 0xe5, 0x2e, 0x93, 0x71, 0x5d, 0x0b, 0x5b, 0xcc, 0x90, 0x43, 0xe6, 0x7e, 0xc6, 0x29, 0x6c, 0xb6, + 0x69, 0x9f, 0x27, 0x46, 0x6e, 0x69, 0xc3, 0x98, 0x08, 0x06, 0x2e, 0xb4, 0x26, 0xf2, 0x45, 0xbb, 0xb6, 0xdd, 0x9c, + 0xa1, 0xbc, 0xfa, 0xc9, 0xe0, 0xc1, 0x37, 0xff, 0xea, 0x8b, 0x27, 0xb3, 0xc7, 0x7d, 0x2e, 0x1e, 0x9f, 0x79, 0xff, + 0x74, 0x3f, 0xef, 0x65, 0xbb, 0xc0, 0xf5, 0x4e, 0x5e, 0x53, 0xe0, 0x74, 0x28, 0x25, 0x71, 0xd2, 0x01, 0x14, 0xc1, + 0x6d, 0x3b, 0x96, 0x87, 0x88, 0x75, 0xa2, 0xa0, 0x0b, 0x55, 0xce, 0x34, 0x33, 0x8e, 0xf3, 0xe5, 0x95, 0xb4, 0x35, + 0xb8, 0xfd, 0x3c, 0xa4, 0x1a, 0x08, 0xbe, 0xd0, 0x85, 0x09, 0x0d, 0x26, 0x23, 0x6e, 0x6b, 0xda, 0x12, 0x8b, 0x25, + 0x2e, 0x10, 0x39, 0x43, 0x01, 0xc8, 0x21, 0xd3, 0x05, 0xa5, 0xfb, 0x64, 0x32, 0x3c, 0x52, 0xde, 0x88, 0xcc, 0xc8, + 0x70, 0x40, 0xb2, 0x63, 0x7d, 0xe7, 0x6a, 0x26, 0xc2, 0x24, 0xec, 0x22, 0x3c, 0xfd, 0x4b, 0x96, 0xa4, 0x7c, 0xcc, + 0xd3, 0x7e, 0xae, 0x98, 0x80, 0x79, 0x45, 0xe9, 0x25, 0x45, 0xe9, 0x42, 0x0d, 0x7d, 0xcb, 0xb1, 0x38, 0xa7, 0x01, + 0x43, 0x61, 0xaa, 0x84, 0x51, 0x16, 0xd3, 0x66, 0x22, 0x0b, 0x68, 0xc1, 0x39, 0x0a, 0x96, 0x2b, 0x02, 0x8f, 0x2a, + 0xb9, 0x2e, 0xe5, 0x37, 0x11, 0x15, 0x5a, 0x8e, 0x1d, 0x70, 0xc3, 0xba, 0x63, 0x90, 0x95, 0x09, 0x4c, 0xbe, 0xad, + 0x4a, 0x32, 0x2f, 0x39, 0x62, 0x11, 0xde, 0x2f, 0xe7, 0xdb, 0x6e, 0xd7, 0x38, 0x80, 0xbb, 0x76, 0x48, 0x15, 0x56, + 0x31, 0x28, 0x10, 0x26, 0x8a, 0x17, 0xa5, 0xf1, 0x07, 0x09, 0xb6, 0x3a, 0x46, 0xb4, 0xb1, 0xf4, 0xa3, 0x95, 0xb8, + 0x29, 0x47, 0xf4, 0xb2, 0x46, 0x2b, 0x45, 0xbd, 0xcb, 0x0a, 0x18, 0x6d, 0x91, 0x49, 0x48, 0x80, 0xf3, 0xd5, 0xb9, + 0x9a, 0x5f, 0x1f, 0x3a, 0x6a, 0xdb, 0x00, 0x59, 0x2a, 0x15, 0xa7, 0x68, 0x31, 0x58, 0x46, 0x82, 0x71, 0x5b, 0xb3, + 0x0a, 0x1c, 0xbf, 0x67, 0xf2, 0x00, 0xfa, 0x2d, 0xda, 0xe5, 0xae, 0x6a, 0x20, 0x7c, 0x94, 0x11, 0x5d, 0xb0, 0xcb, + 0x40, 0xde, 0x84, 0xd4, 0x1b, 0xb4, 0x60, 0x9b, 0xb6, 0x5b, 0xeb, 0xb9, 0xa8, 0x0f, 0x7d, 0x01, 0x9b, 0x74, 0x59, + 0x51, 0xa3, 0xb5, 0xa1, 0x86, 0xc3, 0xd5, 0x86, 0x23, 0xbb, 0x41, 0x4f, 0x13, 0x3a, 0x20, 0xf5, 0xb5, 0x9f, 0xde, + 0xae, 0x2c, 0x00, 0xfe, 0x81, 0xba, 0x48, 0xf4, 0xfb, 0x32, 0xbe, 0x81, 0x06, 0x41, 0x19, 0x40, 0xb0, 0x93, 0xae, + 0xad, 0xf4, 0x1c, 0x0c, 0xc2, 0x9a, 0x51, 0x0b, 0x6f, 0xca, 0x8f, 0x29, 0xc8, 0x10, 0x4e, 0x49, 0x6c, 0xf0, 0xa6, + 0xdb, 0xc3, 0xc2, 0x3e, 0xdc, 0xe1, 0xac, 0x36, 0xa5, 0x3f, 0x21, 0x9a, 0x4c, 0x74, 0x00, 0x76, 0x57, 0x4d, 0x6c, + 0x7c, 0xd8, 0x0f, 0x2b, 0x72, 0x42, 0x75, 0xa8, 0xe8, 0x13, 0x65, 0x62, 0x9b, 0x5f, 0x76, 0x24, 0x79, 0xa1, 0xb4, + 0xc4, 0x17, 0x06, 0xfb, 0xa6, 0x8b, 0xb1, 0x50, 0x71, 0x80, 0xc4, 0x1c, 0x32, 0xa6, 0x3b, 0x6e, 0x11, 0x07, 0xd3, + 0x66, 0xa0, 0xec, 0x6f, 0xd6, 0xdb, 0x81, 0xad, 0x01, 0x28, 0x73, 0xcb, 0x4f, 0xfa, 0x5b, 0x14, 0x47, 0xb0, 0xa8, + 0x5f, 0x47, 0xa0, 0x25, 0xd7, 0xb5, 0x4f, 0xe2, 0x2c, 0x67, 0xe9, 0x91, 0x1b, 0x2e, 0xfa, 0x7d, 0x55, 0x24, 0x13, + 0xa2, 0xe9, 0x50, 0xc7, 0x56, 0x7c, 0xac, 0xa3, 0xd8, 0x2a, 0xdc, 0x80, 0xdf, 0x49, 0x43, 0xc4, 0x08, 0x19, 0xe3, + 0xb4, 0x24, 0xd0, 0xa9, 0xe5, 0x3c, 0x6d, 0x04, 0x6a, 0x6b, 0x52, 0xe6, 0x9e, 0xed, 0x4f, 0xa4, 0x83, 0x92, 0x3c, + 0xb2, 0x02, 0x68, 0xff, 0x56, 0x1f, 0x7d, 0x69, 0xa5, 0x20, 0x48, 0xb3, 0x24, 0x32, 0x38, 0xa3, 0xe3, 0x1c, 0x37, + 0x5e, 0x48, 0x90, 0x2c, 0x1e, 0x4e, 0x42, 0x9f, 0xb4, 0x59, 0x6b, 0xf0, 0xa4, 0xbc, 0x28, 0x37, 0x2e, 0x00, 0x75, + 0x7a, 0xc8, 0x16, 0x0d, 0x73, 0x16, 0xc8, 0x4e, 0xbc, 0x87, 0x18, 0x1e, 0xea, 0x52, 0x69, 0x01, 0x73, 0x7a, 0x86, + 0xa4, 0xb9, 0x2c, 0xb2, 0x1a, 0x17, 0x04, 0xfd, 0x66, 0xf2, 0x23, 0xf4, 0x39, 0x26, 0x4e, 0x97, 0xa7, 0x31, 0x55, + 0x23, 0x71, 0x7a, 0x36, 0xcf, 0xc0, 0x3a, 0x62, 0x8f, 0xec, 0x42, 0x2b, 0x86, 0xe8, 0x57, 0x71, 0x29, 0xe1, 0x30, + 0xcb, 0x0b, 0x41, 0x47, 0xf9, 0xc5, 0xc8, 0xd9, 0x8c, 0x09, 0x2e, 0x7d, 0xe2, 0x86, 0x1f, 0x4a, 0xa9, 0xa1, 0x80, + 0xcd, 0x10, 0x82, 0xf4, 0x57, 0x12, 0x7d, 0xb0, 0xd6, 0xc0, 0xf3, 0x9e, 0x2e, 0x26, 0xdc, 0x6b, 0xc2, 0x8c, 0x87, + 0x48, 0x4d, 0x28, 0x74, 0x22, 0x3a, 0x00, 0x43, 0x58, 0x76, 0xd3, 0xad, 0x25, 0xef, 0xc5, 0x3a, 0x0d, 0x9a, 0x83, + 0xa7, 0x0c, 0xc6, 0x1b, 0xb9, 0x1c, 0x47, 0x8c, 0xd8, 0xd7, 0x3d, 0x21, 0x7b, 0x2f, 0x46, 0x10, 0x21, 0x5f, 0x1c, + 0x90, 0x31, 0x45, 0x3b, 0xd5, 0xb4, 0xa4, 0x6b, 0xf6, 0xd9, 0x22, 0xf4, 0xcd, 0xed, 0x71, 0x46, 0x64, 0x4a, 0xaa, + 0x2f, 0x4c, 0x10, 0x11, 0x7a, 0x3a, 0x48, 0xc1, 0x9c, 0xdd, 0x07, 0xaf, 0x28, 0x02, 0x01, 0xd6, 0xdb, 0x6a, 0x78, + 0x52, 0x9d, 0x4f, 0x81, 0xed, 0xba, 0x90, 0x4e, 0xb3, 0x34, 0x0a, 0xb1, 0xe1, 0x3e, 0x56, 0x37, 0x49, 0x0d, 0x63, + 0xba, 0xa8, 0x7c, 0xc0, 0x1f, 0xd4, 0x47, 0xdc, 0xa2, 0xbf, 0x8a, 0xc7, 0x19, 0xf6, 0x92, 0x86, 0x6e, 0x12, 0xdb, + 0x84, 0xa8, 0xaa, 0xc6, 0xba, 0xe6, 0x66, 0xf4, 0xb8, 0x22, 0x03, 0xd7, 0x48, 0xfd, 0x06, 0xad, 0x83, 0x4a, 0x0b, + 0xeb, 0x59, 0x7c, 0x0a, 0xf2, 0xdc, 0x1a, 0x5b, 0xee, 0x4f, 0x90, 0xc4, 0x8b, 0xd1, 0x69, 0x46, 0x7b, 0x86, 0x97, + 0x19, 0x0e, 0x01, 0xf6, 0x9d, 0xe3, 0xdd, 0xae, 0xdd, 0x6f, 0x49, 0xc6, 0x4e, 0xc2, 0x9f, 0x6d, 0x5d, 0x92, 0x34, + 0x06, 0xd4, 0x94, 0x7f, 0x57, 0x3f, 0xe4, 0xb1, 0x17, 0x50, 0x71, 0x1f, 0x23, 0x5d, 0x2f, 0x34, 0x9f, 0xbe, 0x44, + 0x3b, 0xad, 0xdc, 0x3a, 0xbc, 0x45, 0x26, 0xee, 0x3e, 0xc2, 0x00, 0x73, 0x21, 0x77, 0x47, 0xa0, 0xee, 0xad, 0x5f, + 0x10, 0x6f, 0x8a, 0xba, 0xc2, 0x54, 0x4a, 0xb6, 0x1a, 0xbc, 0x96, 0x98, 0x85, 0x9a, 0xcb, 0x95, 0x46, 0xd8, 0xca, + 0x11, 0xa8, 0x83, 0x8e, 0xa4, 0xad, 0xf5, 0xda, 0xc6, 0xac, 0xf2, 0x54, 0x6e, 0x26, 0x0b, 0xfa, 0x1c, 0x49, 0x99, + 0x33, 0x87, 0xce, 0x8a, 0x42, 0x57, 0x25, 0x61, 0xa9, 0xe5, 0xd6, 0xeb, 0xb3, 0x8e, 0x5f, 0xbc, 0xb7, 0x03, 0x88, + 0x05, 0x7b, 0x56, 0x3b, 0x19, 0x1c, 0x76, 0x5b, 0x5c, 0x56, 0xb9, 0xda, 0xa6, 0x44, 0x59, 0x42, 0x60, 0x2e, 0x59, + 0x7d, 0x0d, 0xd0, 0x53, 0x14, 0x45, 0x1a, 0x74, 0xd5, 0x75, 0x41, 0x42, 0xb8, 0x52, 0xf1, 0x77, 0x17, 0x66, 0xe4, + 0x08, 0x97, 0x88, 0xdc, 0x41, 0x57, 0x4a, 0x7e, 0x3c, 0x21, 0x3d, 0x9d, 0x10, 0x09, 0xbd, 0xbc, 0x31, 0x78, 0x97, + 0x83, 0xc7, 0xfe, 0x2e, 0xe4, 0x0a, 0x1f, 0x12, 0x6c, 0x39, 0x0c, 0xa5, 0xdc, 0x14, 0xe1, 0xbe, 0x2f, 0xd0, 0x49, + 0xb9, 0x8a, 0xe0, 0x20, 0xb5, 0x23, 0x9f, 0xab, 0x23, 0x7f, 0x66, 0x73, 0x0a, 0x97, 0xe6, 0x64, 0xd7, 0x28, 0x42, + 0x99, 0x62, 0xef, 0x79, 0x62, 0x60, 0x4a, 0x12, 0x3e, 0xbb, 0x4e, 0x64, 0xad, 0x75, 0x7f, 0xa7, 0x3d, 0x88, 0x17, + 0x4d, 0xa4, 0xfc, 0x20, 0x36, 0x1f, 0x68, 0x71, 0x5d, 0x5e, 0x63, 0xeb, 0x8e, 0x62, 0x06, 0xa0, 0xb9, 0xc9, 0xba, + 0xad, 0x32, 0xb9, 0xc1, 0x2a, 0x20, 0x4f, 0x67, 0xa1, 0xf1, 0x2c, 0xcd, 0x60, 0x9e, 0x9f, 0x38, 0x2b, 0x46, 0x2a, + 0x04, 0x8a, 0xd2, 0x52, 0x9b, 0xd5, 0x49, 0x5c, 0xc9, 0x8e, 0x3d, 0x6e, 0xd9, 0x42, 0x27, 0x20, 0xd5, 0xe3, 0x04, + 0xb4, 0x0d, 0xde, 0x51, 0x4a, 0x76, 0x67, 0x19, 0x07, 0xdb, 0x85, 0x7f, 0x07, 0x66, 0x1d, 0xea, 0xab, 0x08, 0x2a, + 0xd2, 0x26, 0xb6, 0x6a, 0x4a, 0x91, 0x74, 0x42, 0xeb, 0x62, 0x0b, 0x8a, 0xe2, 0x6a, 0x8f, 0xf8, 0xaa, 0x95, 0xe1, + 0xce, 0xec, 0xb6, 0xc8, 0xe6, 0x0c, 0xf7, 0x64, 0xe0, 0x8c, 0x2d, 0xa1, 0xcd, 0xac, 0x25, 0xf6, 0x71, 0x4f, 0x37, + 0xe9, 0xef, 0xb6, 0x92, 0x66, 0xd0, 0x88, 0xa1, 0xa5, 0x65, 0xf2, 0xef, 0x8d, 0xc9, 0xbc, 0x16, 0x43, 0x63, 0x4e, + 0x31, 0xdd, 0x30, 0x70, 0x8b, 0x2a, 0xb5, 0x19, 0xd7, 0x8a, 0x3e, 0xfd, 0x4e, 0x23, 0x39, 0xa4, 0x00, 0x4d, 0x28, + 0x05, 0x11, 0xc8, 0x97, 0x14, 0x82, 0x3b, 0x25, 0xdb, 0x44, 0x96, 0x5b, 0x89, 0xcb, 0xa2, 0xd3, 0xc3, 0xf1, 0x0f, + 0x27, 0xa0, 0x42, 0x5f, 0xae, 0x58, 0xd0, 0x4f, 0xf4, 0x3e, 0x26, 0xea, 0x58, 0xca, 0xc9, 0xf1, 0xe9, 0xd2, 0x55, + 0x55, 0x01, 0x2d, 0x57, 0xaf, 0x8b, 0x0e, 0xce, 0x35, 0x65, 0x80, 0xd4, 0x63, 0x14, 0xb6, 0x10, 0x26, 0x7f, 0x04, + 0xde, 0x4f, 0xef, 0xe5, 0xb8, 0xed, 0x36, 0x45, 0x8f, 0x74, 0x76, 0xa7, 0x48, 0x4d, 0x2a, 0xd1, 0xca, 0xc9, 0x31, + 0x9e, 0x1e, 0xf2, 0x62, 0x0c, 0xd8, 0x31, 0x71, 0xb3, 0x49, 0x0d, 0x18, 0x13, 0x80, 0x23, 0x33, 0x15, 0xdb, 0x54, + 0x5b, 0x2b, 0x13, 0xa2, 0xb6, 0xe5, 0x7c, 0x59, 0x4b, 0xa7, 0x28, 0xef, 0x60, 0x0e, 0x81, 0x79, 0xee, 0x32, 0x6d, + 0xa0, 0x9a, 0x22, 0xb3, 0xa4, 0x1d, 0xd5, 0xf1, 0x52, 0x6c, 0xbc, 0xf8, 0xa9, 0xc0, 0xbd, 0x91, 0xaa, 0x57, 0x56, + 0x0b, 0x6e, 0xce, 0x94, 0x71, 0xb8, 0xc5, 0x55, 0xe1, 0x24, 0xe2, 0x01, 0x8c, 0x3e, 0x63, 0x31, 0x9c, 0x2f, 0xf6, + 0x23, 0x3e, 0x2c, 0x6a, 0x0a, 0x6f, 0xab, 0xb7, 0x72, 0x5c, 0x86, 0x80, 0xea, 0x11, 0xc4, 0xe9, 0x4e, 0x65, 0xc1, + 0xeb, 0x8c, 0x1c, 0x11, 0xbe, 0x95, 0xe2, 0xa8, 0x64, 0x1c, 0xc4, 0x67, 0xb1, 0xe9, 0xc1, 0x31, 0x2d, 0x3c, 0x63, + 0x22, 0x77, 0xc0, 0x3c, 0xa3, 0xf1, 0x3d, 0x3e, 0x73, 0x43, 0x7c, 0xe7, 0xb5, 0xf7, 0xb6, 0x22, 0x3d, 0x37, 0xb3, + 0xf9, 0xc4, 0x9b, 0x86, 0xa8, 0xf3, 0xe1, 0xad, 0x27, 0x3a, 0xe7, 0x15, 0x2c, 0xe2, 0x50, 0xb8, 0x21, 0xcd, 0xe8, + 0x0b, 0xed, 0x1e, 0xb2, 0x79, 0x6a, 0xba, 0x8b, 0x0b, 0xd8, 0xa3, 0xe9, 0x77, 0x67, 0x04, 0xc4, 0x3e, 0x41, 0xc4, + 0x97, 0x3c, 0xb8, 0xbd, 0x75, 0x2b, 0x6d, 0x75, 0x8c, 0x91, 0x6a, 0xd3, 0xdc, 0x02, 0xbf, 0xdf, 0x97, 0x30, 0x7b, + 0x1c, 0x83, 0x77, 0x0d, 0x02, 0xc4, 0x2f, 0x40, 0x58, 0x35, 0x6d, 0x68, 0xc0, 0x77, 0xf8, 0x32, 0x5b, 0xe6, 0x5e, + 0x23, 0xaa, 0x1e, 0xe6, 0xf2, 0xc5, 0xc9, 0xae, 0x36, 0x22, 0x95, 0xdb, 0x7e, 0xe2, 0xcf, 0x0f, 0x86, 0x25, 0x3d, + 0x47, 0x87, 0x71, 0x20, 0x37, 0xe4, 0xcc, 0x28, 0xb1, 0x09, 0xa7, 0xad, 0x9c, 0x87, 0xc6, 0x3c, 0x15, 0x04, 0x64, + 0xf8, 0xff, 0x7a, 0x38, 0x48, 0xcc, 0x5b, 0x37, 0x28, 0x57, 0xd5, 0x06, 0xd6, 0x64, 0x2f, 0x0e, 0x22, 0xa8, 0xf2, + 0x50, 0xa4, 0x58, 0x5f, 0x74, 0x58, 0x97, 0xc4, 0x42, 0x26, 0x82, 0x51, 0x21, 0x49, 0x90, 0xad, 0xa3, 0x5b, 0xa3, + 0xdc, 0x25, 0xbd, 0x4e, 0x40, 0xcf, 0xf4, 0x32, 0xfe, 0x18, 0xc7, 0xa2, 0xac, 0x25, 0x7f, 0xee, 0x49, 0xb6, 0xcb, + 0xe8, 0xae, 0x66, 0xac, 0x23, 0x22, 0x36, 0xb4, 0x1c, 0x1d, 0xe7, 0x65, 0x51, 0x72, 0xdc, 0xb4, 0x27, 0x70, 0x2c, + 0xbc, 0xb3, 0xa2, 0x68, 0xe6, 0x42, 0xae, 0xe9, 0xab, 0x63, 0x8a, 0xd6, 0xe1, 0x31, 0x7b, 0x6d, 0xdb, 0x12, 0x3d, + 0x5c, 0x8e, 0xf1, 0x52, 0x1a, 0x2a, 0x34, 0x87, 0xda, 0x5a, 0x5d, 0xea, 0x39, 0x2c, 0x63, 0xc5, 0x17, 0x85, 0x52, + 0xee, 0xa2, 0xe1, 0xa9, 0x8b, 0x69, 0x40, 0x37, 0x69, 0x44, 0x3f, 0x91, 0x99, 0x53, 0x8d, 0x3c, 0xe9, 0xc7, 0xbe, + 0x51, 0x85, 0x01, 0xd0, 0xf1, 0x8a, 0x91, 0xec, 0xbe, 0x2f, 0x57, 0x87, 0x12, 0x7c, 0x7a, 0xd6, 0x51, 0x2c, 0xb5, + 0x8e, 0xf7, 0x79, 0x2d, 0xc7, 0x77, 0x37, 0x84, 0xd1, 0xba, 0x3d, 0x30, 0x2b, 0x9c, 0x8b, 0x49, 0x31, 0x6e, 0xd9, + 0x0a, 0x13, 0xe6, 0x11, 0x4a, 0xbc, 0x9b, 0xa2, 0xb1, 0x5f, 0x99, 0x12, 0x9d, 0x17, 0xe1, 0x65, 0x73, 0xc5, 0x42, + 0xa9, 0x7a, 0x71, 0x89, 0xfd, 0xc6, 0xbd, 0xed, 0x39, 0xe4, 0xb9, 0x0c, 0x1b, 0x6f, 0x67, 0x79, 0x7a, 0xc4, 0xe4, + 0xfc, 0x08, 0x9b, 0x79, 0x20, 0x7d, 0x2d, 0x04, 0x18, 0xf5, 0x9e, 0x1c, 0xbf, 0x7c, 0xdf, 0xcb, 0xae, 0x71, 0xbc, + 0x30, 0xd2, 0x38, 0xce, 0x17, 0xe4, 0x29, 0xb1, 0x44, 0x69, 0xe6, 0x8b, 0x7a, 0x94, 0x03, 0xf1, 0xdc, 0x0b, 0x76, + 0x3d, 0x6d, 0xc7, 0xbf, 0x17, 0xee, 0x4a, 0x7a, 0x34, 0xfa, 0x04, 0xbe, 0x1e, 0xfe, 0x73, 0x74, 0x58, 0x90, 0x44, + 0x44, 0x4f, 0xe3, 0x48, 0x4f, 0x6d, 0x59, 0xea, 0x3d, 0x3b, 0xd6, 0x44, 0xbd, 0xf1, 0x3a, 0x23, 0x94, 0xb6, 0xa1, + 0x94, 0xb6, 0x83, 0x32, 0x82, 0x25, 0xb0, 0x69, 0x53, 0x08, 0x51, 0x8d, 0xff, 0x82, 0x9b, 0xa7, 0x08, 0x3f, 0xeb, + 0x44, 0x69, 0x36, 0x53, 0x53, 0x74, 0x67, 0x34, 0x00, 0x6b, 0x79, 0x9f, 0x0d, 0xd0, 0xfa, 0xa1, 0xae, 0xbc, 0xc2, + 0xc0, 0x6a, 0x55, 0x57, 0x02, 0xb5, 0x22, 0x50, 0x82, 0x04, 0x4e, 0x78, 0x2f, 0x22, 0xa2, 0x1b, 0x98, 0x54, 0x7a, + 0xb0, 0x65, 0x3b, 0xb7, 0x0d, 0xbb, 0xd7, 0xd2, 0xe7, 0x87, 0xf7, 0x6a, 0xd2, 0x53, 0x57, 0x76, 0xc7, 0x43, 0xe4, + 0x24, 0x39, 0xbb, 0x07, 0x88, 0xe4, 0x51, 0x32, 0xd8, 0xb9, 0x7b, 0x7b, 0xda, 0xda, 0x1d, 0x62, 0x61, 0x5b, 0xf0, + 0xd3, 0x1d, 0xb1, 0x18, 0xa5, 0xdd, 0xec, 0x93, 0x9f, 0x67, 0x70, 0x58, 0x7a, 0x0b, 0xe0, 0x29, 0xd6, 0xdd, 0x5f, + 0xcd, 0xac, 0xe8, 0x1e, 0xff, 0xe2, 0xa1, 0x2b, 0x0a, 0xe9, 0x88, 0x59, 0xdc, 0xe2, 0xb8, 0x2e, 0x3b, 0xab, 0xbb, + 0x45, 0xce, 0x6d, 0x49, 0x84, 0x4a, 0x09, 0xc9, 0xe5, 0x98, 0x95, 0x1a, 0xdb, 0x23, 0xca, 0xe0, 0xb4, 0xb7, 0x97, + 0x7e, 0x63, 0xde, 0xc2, 0xf4, 0x05, 0xa0, 0x26, 0x60, 0xb9, 0x20, 0x1b, 0xef, 0x3e, 0x00, 0xcc, 0xd2, 0xaa, 0xab, + 0x33, 0x06, 0x17, 0xb7, 0xee, 0x7a, 0xc3, 0x22, 0x33, 0x9a, 0x89, 0xba, 0xc9, 0xdd, 0x11, 0x55, 0x8c, 0x16, 0x26, + 0xdb, 0x2f, 0xa5, 0xe1, 0xd3, 0x6a, 0x44, 0x2b, 0x2d, 0x5a, 0x46, 0x87, 0x5d, 0x5f, 0xc9, 0x51, 0x22, 0xb1, 0x5c, + 0x2c, 0xbb, 0xf2, 0x56, 0x98, 0xf8, 0x31, 0xb1, 0x36, 0x66, 0x44, 0x5a, 0xb2, 0x85, 0xc1, 0x11, 0x49, 0x79, 0xd4, + 0xdd, 0xb2, 0x6a, 0x72, 0x1b, 0x67, 0x2b, 0x3c, 0xdd, 0x52, 0xd4, 0x14, 0xaa, 0x43, 0xb4, 0xdd, 0x07, 0x19, 0x24, + 0xd3, 0x46, 0x91, 0xf3, 0xb9, 0xed, 0xb1, 0x88, 0x3a, 0x5d, 0xd1, 0x69, 0x91, 0x88, 0xb9, 0xdd, 0x53, 0xb4, 0x1d, + 0x25, 0xc9, 0x93, 0x94, 0x4c, 0x27, 0x0e, 0x54, 0xd3, 0x86, 0x5c, 0x4b, 0xef, 0x5f, 0x2b, 0x02, 0x71, 0xf1, 0x1f, + 0xf2, 0xb2, 0xed, 0xbb, 0x03, 0x83, 0x08, 0x3a, 0x98, 0x23, 0x09, 0xcc, 0x4b, 0x2d, 0x1d, 0x94, 0x48, 0x12, 0x91, + 0x9f, 0x34, 0xcc, 0xae, 0x4b, 0xd6, 0xe8, 0x83, 0x56, 0xba, 0x33, 0x99, 0x35, 0x24, 0x52, 0xaf, 0x49, 0x6d, 0x6d, + 0xb1, 0x89, 0x11, 0xcf, 0x7c, 0x67, 0x9d, 0x88, 0x22, 0xf1, 0x20, 0x73, 0x62, 0xa9, 0x3c, 0x5b, 0x44, 0x89, 0xaf, + 0x70, 0xf6, 0xb5, 0x5e, 0xec, 0x4e, 0x8b, 0x2c, 0xe6, 0x87, 0x91, 0x5f, 0x0e, 0x37, 0xbb, 0x15, 0x29, 0xea, 0xad, + 0xf1, 0xe5, 0x05, 0xcd, 0x6c, 0x5c, 0x3b, 0x71, 0xcc, 0x19, 0xd2, 0x48, 0x21, 0x48, 0x48, 0x9f, 0x8e, 0xf0, 0x5a, + 0x04, 0x07, 0x36, 0x6a, 0x7a, 0xc7, 0x9e, 0x67, 0x2b, 0x77, 0x57, 0x43, 0xc3, 0xb6, 0x43, 0x22, 0x48, 0xd0, 0x78, + 0x93, 0x59, 0x33, 0x34, 0x3f, 0xec, 0x3a, 0x6f, 0xcf, 0xf5, 0xf0, 0x8d, 0x62, 0x60, 0x69, 0x13, 0x09, 0xe0, 0x52, + 0x51, 0x95, 0xe6, 0xd6, 0x7e, 0x90, 0x43, 0x36, 0xe2, 0x8b, 0x56, 0xfd, 0x8a, 0x80, 0xee, 0x24, 0x21, 0x21, 0x40, + 0xd3, 0xeb, 0xfa, 0x3e, 0x5c, 0x24, 0x2c, 0x0e, 0x08, 0xdf, 0x55, 0xf0, 0xdf, 0x24, 0x4d, 0xaf, 0x4b, 0x13, 0xfa, + 0xb1, 0x28, 0x97, 0x83, 0x83, 0x2c, 0x10, 0x6f, 0x01, 0xd1, 0x10, 0x04, 0x82, 0x42, 0x58, 0x38, 0xa6, 0x12, 0xfa, + 0x17, 0x5a, 0x43, 0xc1, 0x04, 0x98, 0x8e, 0xc6, 0xb9, 0x34, 0x28, 0xaa, 0xad, 0x74, 0x9a, 0x53, 0x36, 0x5c, 0x34, + 0x0c, 0x32, 0xeb, 0x9f, 0xc2, 0x10, 0xa7, 0x98, 0x26, 0xe3, 0xfe, 0x2e, 0xc1, 0x78, 0xba, 0x6d, 0x3e, 0x51, 0xca, + 0x6a, 0x9f, 0xb5, 0x78, 0x42, 0x2b, 0x9e, 0x57, 0xa2, 0x3e, 0xa7, 0xd7, 0xde, 0x7f, 0xf4, 0x86, 0xef, 0xe0, 0xc9, + 0x47, 0x25, 0x7a, 0x1b, 0x27, 0x96, 0x3b, 0x58, 0x04, 0x58, 0xe4, 0x7d, 0xd7, 0x8c, 0xa4, 0x40, 0x86, 0x3a, 0xc0, + 0x5c, 0x63, 0x6e, 0xfb, 0x48, 0x0d, 0x6d, 0x0f, 0xe5, 0xde, 0xe4, 0xda, 0x34, 0xac, 0x7a, 0x58, 0x60, 0x79, 0x75, + 0xdd, 0xe6, 0xe6, 0x00, 0x79, 0xec, 0x5a, 0x8c, 0x08, 0x72, 0x44, 0x86, 0xe3, 0xc1, 0x6d, 0x42, 0x41, 0x80, 0x02, + 0xaa, 0xa6, 0x9a, 0xd6, 0xe1, 0xfe, 0x9c, 0x0f, 0xe2, 0x50, 0xd7, 0x84, 0xd8, 0xa8, 0x3c, 0x42, 0xaf, 0xb9, 0xef, + 0x13, 0xfd, 0x3e, 0xe5, 0x86, 0xc6, 0x1b, 0x24, 0x40, 0x2e, 0xae, 0xce, 0x93, 0x44, 0xdd, 0x18, 0xab, 0xa3, 0x38, + 0x22, 0x0c, 0x50, 0x98, 0x63, 0x38, 0xdc, 0x4e, 0x05, 0x47, 0x4b, 0x02, 0x6d, 0x2c, 0x55, 0x2f, 0xb7, 0xdf, 0x66, + 0x5d, 0xea, 0x1f, 0x14, 0x0c, 0xa2, 0xd3, 0x43, 0x5e, 0x38, 0x10, 0x32, 0xd6, 0xf7, 0xe1, 0xf2, 0x1e, 0x67, 0xb4, + 0x2e, 0xa3, 0x46, 0x30, 0x06, 0x0f, 0x50, 0xce, 0xaa, 0xc7, 0xd1, 0x2e, 0x20, 0x96, 0x87, 0xf4, 0xa1, 0xc9, 0x8c, + 0x90, 0x2d, 0x72, 0xf9, 0xa5, 0x16, 0xf9, 0xab, 0xd0, 0xb5, 0x78, 0xee, 0x01, 0x9d, 0x5a, 0x70, 0x0c, 0x75, 0x83, + 0xaf, 0xba, 0xe9, 0xaa, 0x96, 0xda, 0x36, 0xc7, 0xc8, 0xb9, 0xb0, 0x38, 0xd5, 0xf3, 0x6c, 0x6c, 0xdf, 0x7b, 0x07, + 0x80, 0x98, 0x02, 0x7a, 0x01, 0xb0, 0x1d, 0x5e, 0x0a, 0x3e, 0xf1, 0xe0, 0xb6, 0x3a, 0xec, 0xd8, 0x99, 0xa4, 0x71, + 0x1e, 0x4d, 0xbd, 0x39, 0xc7, 0x5c, 0xe8, 0x71, 0xec, 0xe7, 0x06, 0xd7, 0x9f, 0xac, 0x18, 0xbe, 0x6d, 0x4d, 0x70, + 0x78, 0xae, 0x72, 0x36, 0x24, 0x11, 0x4b, 0xd6, 0x3d, 0x47, 0x5f, 0x48, 0xe4, 0x69, 0x1b, 0xdf, 0x2f, 0xf4, 0xd5, + 0x39, 0x75, 0x91, 0x9d, 0x63, 0x92, 0x09, 0xf4, 0x60, 0xf2, 0x5e, 0x59, 0x1c, 0x1a, 0xab, 0x94, 0x59, 0xfc, 0xd0, + 0xb9, 0xa6, 0xb7, 0xf7, 0xab, 0x75, 0x29, 0xe5, 0x53, 0xad, 0x72, 0x2b, 0xbf, 0x8d, 0x1d, 0x4d, 0x3b, 0x35, 0xa0, + 0xdd, 0xd6, 0x37, 0x74, 0x1a, 0x45, 0x24, 0xe9, 0xee, 0x82, 0x5b, 0x78, 0x06, 0xd3, 0x18, 0x51, 0xb0, 0xe7, 0x53, + 0xeb, 0xf2, 0xb5, 0x97, 0x95, 0x78, 0x45, 0xbc, 0x2b, 0x06, 0x63, 0xe5, 0x84, 0x0e, 0x16, 0x69, 0x1a, 0x68, 0xe0, + 0x24, 0x49, 0xdc, 0xaa, 0x24, 0x7e, 0x6a, 0xf9, 0x17, 0xd4, 0xdc, 0xa8, 0x3c, 0x15, 0xf1, 0x75, 0x49, 0x98, 0x39, + 0x2e, 0xd5, 0xbd, 0x51, 0x79, 0x50, 0x8e, 0x79, 0xba, 0x66, 0x2c, 0x5a, 0xba, 0x9d, 0x22, 0xf3, 0x64, 0xcf, 0x9b, + 0x9b, 0x11, 0x25, 0x4a, 0x84, 0xea, 0x42, 0xaf, 0x72, 0x6d, 0x16, 0x3a, 0xd2, 0x88, 0x4d, 0x6b, 0x35, 0x9b, 0xd8, + 0xfd, 0x70, 0x0e, 0x52, 0x95, 0x3d, 0xc1, 0x35, 0xf4, 0xbc, 0x13, 0x86, 0xcd, 0xb5, 0xae, 0x43, 0x23, 0x86, 0xcc, + 0x80, 0x99, 0x66, 0x01, 0xa6, 0x40, 0x16, 0x71, 0x5f, 0x0d, 0x48, 0x94, 0x31, 0xfd, 0x13, 0xab, 0xf5, 0x7c, 0xab, + 0xad, 0x3a, 0x26, 0xff, 0x32, 0x78, 0x0d, 0x67, 0x00, 0x45, 0x89, 0xe1, 0x44, 0xd3, 0x5e, 0xa9, 0xd5, 0x00, 0x61, + 0x9e, 0x10, 0xa3, 0xb0, 0x82, 0x6d, 0xd1, 0x68, 0xd5, 0x55, 0x30, 0x80, 0x1a, 0xe6, 0xc9, 0x08, 0x8d, 0x22, 0x32, + 0x1a, 0x5f, 0xd8, 0x8d, 0xbc, 0xb2, 0x00, 0xcb, 0x9a, 0xf4, 0x56, 0x39, 0xa5, 0xfe, 0x48, 0x6a, 0xe5, 0x8e, 0x12, + 0x72, 0xc3, 0x0d, 0x9a, 0x36, 0xa9, 0x37, 0x1e, 0x07, 0x7c, 0x6b, 0xc6, 0x99, 0x86, 0x76, 0xdb, 0x5a, 0xb9, 0x0f, + 0xec, 0xc0, 0x0d, 0xb7, 0x0d, 0xdf, 0xa9, 0x6a, 0x27, 0xf3, 0xf5, 0xeb, 0xdd, 0xee, 0x12, 0x6b, 0x42, 0x9b, 0x8e, + 0xb2, 0x06, 0xb6, 0x6d, 0x51, 0xcc, 0xc5, 0x48, 0xd7, 0x78, 0xb7, 0xd8, 0x77, 0x20, 0xdb, 0xf7, 0x60, 0xad, 0x92, + 0x90, 0x93, 0xab, 0x74, 0x7e, 0x8d, 0x7e, 0xd2, 0xe9, 0x2a, 0x91, 0x99, 0x5d, 0xe4, 0x77, 0x99, 0xa9, 0xef, 0x65, + 0xaa, 0xc7, 0xb5, 0x56, 0xa4, 0xc0, 0x56, 0xa8, 0x0a, 0xcf, 0x21, 0x30, 0x5d, 0xb2, 0xf2, 0x7f, 0x20, 0xe2, 0x9c, + 0x8c, 0x2b, 0xa1, 0xbd, 0x51, 0x35, 0x03, 0x18, 0x12, 0x8a, 0xa1, 0x89, 0xe5, 0xf4, 0xb8, 0xd4, 0x20, 0xaa, 0x93, + 0x06, 0x90, 0xe5, 0x81, 0x10, 0xf0, 0x13, 0x05, 0xd4, 0x99, 0x99, 0x30, 0xf0, 0x93, 0xc0, 0x59, 0x5a, 0x4d, 0x91, + 0x7e, 0x31, 0xe0, 0x0c, 0x45, 0xdd, 0x90, 0x7e, 0xc5, 0x94, 0xe8, 0x0e, 0xbf, 0x52, 0x68, 0x7d, 0x6a, 0x66, 0x2e, + 0x98, 0x91, 0x4e, 0x1a, 0xf8, 0x15, 0x2e, 0x6a, 0x0b, 0xfe, 0x32, 0xa5, 0x26, 0x33, 0x45, 0x98, 0xc9, 0x01, 0x5c, + 0x2a, 0xb7, 0xc5, 0xb3, 0xaa, 0x26, 0x30, 0xfb, 0x22, 0x65, 0x74, 0xe2, 0x18, 0x75, 0xdf, 0x6e, 0x38, 0x4a, 0x52, + 0xde, 0xfe, 0x7a, 0x95, 0x35, 0xca, 0x0e, 0x99, 0x59, 0xea, 0x2a, 0xfe, 0xd4, 0x24, 0x77, 0xbd, 0x0c, 0x9d, 0x74, + 0x2b, 0xb8, 0x65, 0x94, 0xf3, 0x0c, 0xcb, 0xdd, 0x18, 0xd1, 0x61, 0x73, 0x2f, 0x5d, 0xdf, 0xa5, 0xc9, 0xce, 0xad, + 0x4a, 0x4c, 0x08, 0x29, 0xb4, 0x5f, 0x9f, 0x9d, 0xfb, 0xe3, 0xd5, 0xf6, 0xdb, 0x51, 0xdf, 0x73, 0xe3, 0x7c, 0x3a, + 0xfe, 0xed, 0x72, 0xdb, 0x1d, 0x4c, 0x33, 0x54, 0x61, 0x5a, 0x3a, 0x08, 0xdd, 0x35, 0x0f, 0xd0, 0xbf, 0x24, 0x3e, + 0xf5, 0xfb, 0x0b, 0x2a, 0x1d, 0xd0, 0x26, 0xb3, 0x35, 0x15, 0xb2, 0x38, 0x28, 0xa1, 0x6c, 0xd3, 0x2e, 0x4d, 0x8b, + 0x29, 0x72, 0xa0, 0x6e, 0x21, 0x03, 0x52, 0xb2, 0x70, 0x99, 0x41, 0xe5, 0x57, 0xf1, 0x3a, 0xf1, 0x75, 0x7e, 0xb5, + 0x31, 0x32, 0xa2, 0x70, 0x55, 0xc8, 0x35, 0x7c, 0x47, 0x8b, 0x79, 0x01, 0xed, 0xa4, 0xda, 0x38, 0xf4, 0x55, 0xa3, + 0x8e, 0x49, 0xa0, 0xe3, 0xc3, 0x4f, 0x3e, 0x53, 0x37, 0x98, 0xdd, 0xad, 0x09, 0xf8, 0xb1, 0x39, 0x7b, 0x71, 0xa3, + 0x87, 0x38, 0xb5, 0x96, 0x7d, 0xbc, 0x50, 0xf6, 0xa8, 0x1a, 0x79, 0x6b, 0x8d, 0x83, 0xdc, 0xa4, 0x61, 0x6d, 0x38, + 0x29, 0x14, 0xe0, 0xe1, 0x52, 0x7e, 0x48, 0x0a, 0x97, 0xde, 0xa8, 0x44, 0x98, 0x07, 0xb0, 0x13, 0xb6, 0xd4, 0xbd, + 0x51, 0x49, 0x0b, 0xa8, 0x1e, 0xe9, 0xc9, 0xa0, 0x98, 0xce, 0x89, 0xc4, 0x98, 0xf1, 0x25, 0xdd, 0xf4, 0x43, 0xb4, + 0xba, 0x66, 0xd8, 0xc3, 0xfb, 0x58, 0x90, 0x20, 0x87, 0x04, 0x1b, 0xd7, 0x19, 0x42, 0xec, 0xa4, 0xc2, 0xf7, 0x7c, + 0x55, 0x6c, 0x99, 0x7f, 0x46, 0xa8, 0x6d, 0xeb, 0xbe, 0xed, 0x11, 0xe5, 0xb5, 0xd2, 0xb7, 0xb9, 0xbf, 0xe2, 0x8c, + 0xf1, 0x72, 0x86, 0xc6, 0x23, 0x2f, 0xfb, 0x39, 0xcc, 0xcf, 0x7e, 0x75, 0x03, 0x16, 0x20, 0x71, 0x6c, 0xc1, 0xb1, + 0xa7, 0xe4, 0x68, 0xae, 0x73, 0x3e, 0xb6, 0x11, 0xcc, 0x92, 0x69, 0x40, 0x64, 0x2d, 0x8b, 0x04, 0xe2, 0xc8, 0x24, + 0x71, 0x91, 0x28, 0xeb, 0x68, 0x27, 0x8f, 0x0e, 0x7c, 0x6f, 0x22, 0xf7, 0x05, 0x2d, 0x46, 0x59, 0xfc, 0xb1, 0xab, + 0xb6, 0xb6, 0x8a, 0x1c, 0x5e, 0x04, 0xea, 0xe6, 0x2c, 0x8f, 0xe3, 0x58, 0x15, 0x50, 0xbe, 0xce, 0x95, 0xd6, 0x52, + 0x5d, 0xa0, 0x8b, 0x43, 0xf7, 0x51, 0x8b, 0x8a, 0x6a, 0x35, 0x18, 0xf7, 0x40, 0xd9, 0x6b, 0x98, 0xd0, 0x03, 0x7e, + 0xb6, 0x0e, 0x63, 0x36, 0x78, 0xe7, 0xcd, 0xb1, 0x1a, 0xd3, 0x45, 0xce, 0x7b, 0x0b, 0xa8, 0x75, 0xbb, 0xde, 0x92, + 0x9a, 0x08, 0xad, 0x71, 0x93, 0x71, 0x58, 0x24, 0x7c, 0x17, 0x75, 0x38, 0x41, 0x21, 0x09, 0x20, 0x36, 0xc5, 0x08, + 0x53, 0xd0, 0x9a, 0x71, 0xb1, 0xa1, 0x85, 0xdd, 0x44, 0x77, 0xac, 0xcd, 0x23, 0xca, 0x38, 0xdc, 0xd1, 0x4c, 0x87, + 0xb9, 0xb9, 0x96, 0xe0, 0x7b, 0x89, 0xe8, 0x6d, 0xaa, 0xa6, 0x1d, 0x15, 0x36, 0xb7, 0x69, 0x64, 0xcc, 0x4c, 0x8f, + 0x77, 0x81, 0x76, 0xe3, 0xc9, 0xe8, 0x27, 0x54, 0xf0, 0xe7, 0x73, 0x5f, 0x24, 0x03, 0xf7, 0xd9, 0xe7, 0x01, 0x62, + 0x68, 0x4f, 0x9d, 0xee, 0x37, 0xb5, 0xac, 0x73, 0xc0, 0x14, 0x9b, 0xc4, 0xec, 0x67, 0x1c, 0xf5, 0x87, 0x1d, 0x6d, + 0x1c, 0x24, 0xc5, 0x10, 0x28, 0x1d, 0x7e, 0xdc, 0xd1, 0xca, 0xeb, 0xb6, 0xec, 0xdd, 0xf6, 0x1a, 0x77, 0xe4, 0x63, + 0xaa, 0x07, 0x93, 0x20, 0x49, 0xcb, 0xb1, 0x08, 0xcd, 0x18, 0xbc, 0x45, 0x5a, 0xb0, 0xb6, 0x47, 0x40, 0xcb, 0x5c, + 0x2f, 0x14, 0x7a, 0xe0, 0xe9, 0x3b, 0x73, 0x27, 0x85, 0xc5, 0x58, 0x2e, 0xe9, 0xe0, 0xd9, 0x04, 0xb3, 0x59, 0xd5, + 0x6a, 0x7d, 0x77, 0x68, 0x7b, 0xea, 0x2d, 0x10, 0x76, 0x5e, 0xea, 0x9b, 0x81, 0x23, 0x3f, 0xb3, 0x96, 0xc1, 0x94, + 0x73, 0xbb, 0xc1, 0xbb, 0xfe, 0xe8, 0x6f, 0xca, 0xe0, 0x63, 0x7f, 0x8d, 0xe3, 0xf7, 0x54, 0xdd, 0xb2, 0x74, 0xc2, + 0x74, 0x65, 0x3e, 0x46, 0x2b, 0x35, 0xf7, 0x39, 0x8c, 0xc9, 0x74, 0x80, 0x12, 0x1b, 0xf9, 0xba, 0x0b, 0x07, 0xd4, + 0x2d, 0xa3, 0xf8, 0x8a, 0x5f, 0xd6, 0x6f, 0xf7, 0x25, 0xed, 0x6d, 0xf7, 0x5b, 0x30, 0x53, 0xaf, 0xac, 0x04, 0x8f, + 0x0a, 0x02, 0x3c, 0x04, 0x95, 0xc9, 0xa3, 0xca, 0x12, 0xf0, 0x45, 0xbd, 0x0b, 0x90, 0x88, 0x3c, 0xad, 0x47, 0x79, + 0x09, 0x1b, 0xd5, 0xb0, 0xed, 0x7a, 0x5a, 0x1d, 0x08, 0x89, 0xd1, 0x1c, 0x4f, 0x9b, 0xb5, 0xe6, 0x5a, 0x19, 0x7e, + 0x89, 0x12, 0x17, 0xcf, 0xc6, 0x51, 0xb5, 0x51, 0x20, 0xe4, 0xaa, 0x16, 0x4a, 0xc4, 0xa2, 0xc3, 0x42, 0xa6, 0xf2, + 0x65, 0x65, 0x2c, 0x7b, 0x71, 0xb4, 0x9c, 0xc8, 0xd7, 0xf6, 0xd2, 0xc2, 0x7e, 0x1f, 0x1a, 0x7f, 0xfb, 0x50, 0x61, + 0xca, 0xea, 0xa7, 0x3d, 0x19, 0x71, 0x8d, 0xf5, 0xb1, 0xf5, 0xf6, 0xa1, 0x7f, 0x7c, 0xaf, 0xa6, 0x66, 0xbc, 0xed, + 0x90, 0xee, 0x96, 0x03, 0xb6, 0xc2, 0xdb, 0xc3, 0x96, 0xfc, 0xef, 0xb7, 0x2f, 0x76, 0xec, 0x82, 0xcc, 0x27, 0x2c, + 0x18, 0x91, 0x14, 0x8f, 0x4d, 0x06, 0x50, 0x6e, 0x19, 0xd0, 0x88, 0x60, 0x5f, 0x37, 0x76, 0xe4, 0x6b, 0xcb, 0x1c, + 0x97, 0xe9, 0xb2, 0x1f, 0x20, 0xcb, 0xbe, 0x0c, 0x81, 0x9d, 0xdb, 0x32, 0xe4, 0x80, 0x59, 0x1c, 0xc8, 0xcc, 0x4c, + 0xfb, 0x8f, 0x5a, 0x5e, 0xa1, 0x53, 0x4a, 0xb6, 0x33, 0x1f, 0xd0, 0xad, 0x31, 0xd9, 0xe8, 0x42, 0xb0, 0x2e, 0x74, + 0xb2, 0x23, 0xdc, 0xb8, 0x37, 0xd2, 0x0f, 0x8e, 0x6e, 0x31, 0xa7, 0x81, 0x11, 0xcf, 0xb4, 0x98, 0x16, 0x68, 0xc4, + 0x4f, 0x72, 0x55, 0x2f, 0xf5, 0x40, 0xd6, 0xe9, 0x5a, 0x8c, 0x2e, 0xdf, 0x08, 0x6c, 0xf6, 0x44, 0x9c, 0xcc, 0xa1, + 0xde, 0x21, 0x20, 0x25, 0x5a, 0xa5, 0xef, 0xd6, 0x01, 0xa1, 0x9d, 0x80, 0x65, 0x5a, 0x62, 0xaf, 0x53, 0x32, 0xda, + 0xf7, 0x6f, 0xfc, 0x49, 0x39, 0x0d, 0xd4, 0x52, 0x89, 0xd1, 0x2d, 0x41, 0x41, 0xcc, 0x71, 0x5c, 0x3a, 0x6f, 0x8a, + 0x48, 0xcc, 0x58, 0x7f, 0x71, 0xf4, 0x3d, 0xa3, 0x1f, 0x40, 0xad, 0xa4, 0xa9, 0x70, 0xcc, 0x8d, 0x9a, 0x91, 0x85, + 0xc1, 0x97, 0x11, 0x62, 0xb7, 0xc5, 0x3f, 0x89, 0x3c, 0x9d, 0xa2, 0x15, 0xd0, 0x3d, 0x55, 0x2d, 0xb2, 0x92, 0x56, + 0x39, 0xd4, 0x29, 0xbf, 0x0a, 0x96, 0xc3, 0xe4, 0x58, 0xc6, 0x75, 0x16, 0x43, 0x98, 0x9c, 0xe5, 0x14, 0x4a, 0x6e, + 0x71, 0x0a, 0x5f, 0xb4, 0xcc, 0x2e, 0xc3, 0x1a, 0x2a, 0x20, 0x64, 0x1c, 0x84, 0xc3, 0x4f, 0xfe, 0x54, 0x68, 0x7f, + 0x33, 0x4b, 0x36, 0x7a, 0xf7, 0x51, 0x98, 0xa0, 0x07, 0xe7, 0x56, 0x31, 0x83, 0xc9, 0x10, 0x3d, 0x57, 0xa1, 0x15, + 0xdc, 0x89, 0xe7, 0xb4, 0xc8, 0xa9, 0x7a, 0xc8, 0xa0, 0x55, 0x37, 0xeb, 0x75, 0x5f, 0x47, 0x29, 0x27, 0x42, 0x48, + 0x23, 0x4e, 0x5a, 0x53, 0x35, 0xd5, 0x12, 0x7c, 0x44, 0x49, 0x46, 0x8a, 0x33, 0x03, 0xe4, 0xec, 0xa4, 0xa2, 0x56, + 0x02, 0xe5, 0x19, 0x4e, 0x2a, 0x66, 0x9a, 0x93, 0x18, 0xb0, 0xde, 0x35, 0xde, 0xcf, 0xa6, 0xe9, 0x02, 0x80, 0xea, + 0x4b, 0xc7, 0x48, 0x7d, 0xd6, 0xf1, 0xa4, 0x1e, 0xfa, 0x62, 0xd9, 0xff, 0xa8, 0x9d, 0x3a, 0x30, 0x1a, 0xc4, 0xb8, + 0xda, 0x8f, 0x30, 0x38, 0x37, 0x23, 0x86, 0xcd, 0xfc, 0x81, 0xad, 0x0e, 0xd8, 0x26, 0xaa, 0xb9, 0x48, 0xa2, 0xa5, + 0xe8, 0x79, 0xa6, 0xde, 0x85, 0x1a, 0x0d, 0xd5, 0x53, 0x77, 0x3d, 0xf2, 0xc8, 0x4a, 0xb7, 0x26, 0x32, 0x88, 0x14, + 0x4b, 0xa4, 0x0b, 0xaa, 0xf3, 0x8d, 0xc0, 0xd9, 0x4e, 0x06, 0xa6, 0x30, 0xd6, 0xa3, 0xb8, 0xa5, 0x09, 0xbf, 0x2b, + 0x19, 0xda, 0x29, 0x73, 0x54, 0xc6, 0x21, 0x07, 0xd7, 0xca, 0x3c, 0xf9, 0xf9, 0xb7, 0x3e, 0xa5, 0x11, 0x07, 0x78, + 0x4c, 0x7d, 0x7e, 0x86, 0xeb, 0xeb, 0x6f, 0x92, 0x5f, 0x8a, 0x5b, 0xe9, 0x27, 0x7c, 0x67, 0x89, 0x38, 0xef, 0xc1, + 0xf0, 0xad, 0xea, 0x71, 0x60, 0x11, 0xba, 0x72, 0x2e, 0xea, 0xc1, 0xf9, 0xd3, 0x0b, 0x82, 0x17, 0xe5, 0x80, 0x09, + 0x90, 0xa9, 0xe6, 0xac, 0x7e, 0x4b, 0xe4, 0x40, 0xc6, 0x45, 0x55, 0x9a, 0x3c, 0x81, 0xbc, 0x04, 0x9c, 0x3b, 0xc9, + 0x60, 0x32, 0x64, 0xdd, 0x8f, 0xb7, 0x9d, 0xc4, 0x77, 0xc0, 0xfa, 0x8f, 0x49, 0xc6, 0xb9, 0x06, 0x81, 0x14, 0x2b, + 0x69, 0x27, 0xab, 0xf4, 0x41, 0x81, 0x07, 0x26, 0x99, 0x9c, 0x97, 0x4d, 0x69, 0x33, 0x4f, 0xa0, 0x33, 0xa0, 0x8d, + 0xad, 0x4d, 0x19, 0x9f, 0x56, 0x80, 0x12, 0x12, 0xde, 0xc8, 0xd6, 0x56, 0x67, 0x90, 0xca, 0xaa, 0xf3, 0xcf, 0xf6, + 0x38, 0x53, 0x85, 0xbe, 0xe8, 0xa2, 0x39, 0x37, 0xef, 0x1d, 0x38, 0x1f, 0xd6, 0xe6, 0xe9, 0xcb, 0x9f, 0x12, 0x55, + 0x70, 0xd7, 0xa4, 0x01, 0xaa, 0xba, 0xe5, 0x25, 0x9d, 0xf1, 0x4f, 0xd8, 0x5f, 0x62, 0x09, 0x53, 0x90, 0xb4, 0x3f, + 0x84, 0x8f, 0x90, 0xf6, 0x11, 0xf2, 0x66, 0xfb, 0x3f, 0x4a, 0x99, 0x1c, 0x0f, 0xb6, 0x9a, 0xfd, 0xb0, 0x29, 0xfe, + 0x2d, 0xb2, 0x06, 0xee, 0xab, 0xf5, 0xc3, 0xaa, 0x32, 0x89, 0x3e, 0xae, 0x8d, 0x17, 0x94, 0x31, 0x86, 0xe9, 0x64, + 0xb1, 0xea, 0xba, 0x8c, 0x1b, 0x52, 0x66, 0x65, 0xf0, 0xd1, 0xe1, 0x03, 0x4d, 0x48, 0x2a, 0x74, 0x3e, 0xc5, 0xbc, + 0x34, 0xf3, 0xeb, 0x26, 0x15, 0xe1, 0x0f, 0x65, 0xce, 0x3b, 0x6f, 0x89, 0xba, 0xeb, 0x7d, 0xd5, 0x2f, 0x0f, 0x68, + 0xb4, 0x4d, 0x4f, 0x28, 0x07, 0x67, 0x70, 0x9a, 0x64, 0xf4, 0xcc, 0x44, 0x3c, 0x22, 0x1f, 0xe2, 0xfa, 0x45, 0x68, + 0xa4, 0x97, 0x87, 0x1c, 0x11, 0xbf, 0xcb, 0xe2, 0xee, 0x15, 0xbf, 0xd1, 0x5f, 0x92, 0x0f, 0x4b, 0x3a, 0x4a, 0x62, + 0xad, 0xdd, 0x0f, 0x73, 0x4c, 0xda, 0x40, 0xc5, 0xff, 0x0f, 0x13, 0xaf, 0x29, 0x8b, 0x2c, 0xa3, 0x25, 0xba, 0xaa, + 0x1d, 0x1c, 0xed, 0xc3, 0x22, 0x45, 0xbe, 0xc8, 0x10, 0x52, 0x44, 0xb7, 0x46, 0x79, 0x08, 0xaf, 0x27, 0xff, 0xa8, + 0x2c, 0xfc, 0x61, 0xd5, 0x4d, 0x4f, 0xa7, 0x91, 0x8a, 0x1f, 0xe9, 0xe8, 0xfb, 0x55, 0xdd, 0xde, 0x4f, 0x7b, 0xb3, + 0xd8, 0x43, 0xc0, 0xec, 0x33, 0x0d, 0x91, 0xbd, 0x59, 0xf6, 0x19, 0x86, 0x49, 0xdc, 0xe2, 0x8a, 0xd7, 0xa0, 0xa7, + 0xca, 0x56, 0xee, 0x0d, 0x38, 0xe3, 0x0b, 0x43, 0x07, 0x19, 0x8f, 0x96, 0x2b, 0x8f, 0xdf, 0xf0, 0x00, 0x4e, 0xaa, + 0xb6, 0xdb, 0xa2, 0x2c, 0xed, 0x19, 0x9c, 0x24, 0x8b, 0x78, 0x92, 0x79, 0xf1, 0x65, 0x4a, 0x2f, 0x29, 0xd9, 0x8c, + 0x12, 0xde, 0xd1, 0x17, 0xa2, 0x42, 0x2a, 0xb5, 0x21, 0x5f, 0x95, 0x92, 0x6d, 0x34, 0xa4, 0x52, 0xca, 0x15, 0x57, + 0xe3, 0x72, 0x1a, 0xaf, 0x8c, 0xed, 0x21, 0xbb, 0x85, 0x57, 0xc5, 0xeb, 0x14, 0x21, 0xbd, 0xbe, 0x46, 0x38, 0x71, + 0x53, 0x64, 0x90, 0xf8, 0x70, 0x56, 0xd2, 0xe5, 0xc9, 0x35, 0x58, 0xf3, 0x9c, 0xa3, 0x1c, 0xcc, 0xba, 0x3e, 0x50, + 0xe6, 0x7c, 0x93, 0xf6, 0xa8, 0xc8, 0x57, 0x4e, 0x9d, 0xab, 0x0d, 0xa8, 0xcb, 0x77, 0x02, 0x66, 0xe1, 0x3e, 0x1e, + 0x47, 0x25, 0xe9, 0x8d, 0x32, 0xe2, 0xc3, 0x9d, 0x20, 0xc5, 0x66, 0x9e, 0x8c, 0xc4, 0x3b, 0xda, 0xd8, 0xb9, 0x68, + 0xa4, 0x8f, 0x42, 0x7c, 0x4a, 0x50, 0x43, 0x1a, 0xa3, 0xd9, 0xc5, 0xee, 0x65, 0x90, 0x60, 0x88, 0x2c, 0xd9, 0x82, + 0x20, 0x44, 0x1e, 0x96, 0x31, 0x58, 0x52, 0x1f, 0x4d, 0xad, 0x60, 0x62, 0x99, 0x2b, 0x3f, 0x9c, 0xde, 0xa2, 0x57, + 0xeb, 0x48, 0x86, 0x5c, 0x27, 0xb1, 0x20, 0x6d, 0xc6, 0xcf, 0x75, 0x79, 0xd4, 0xde, 0x02, 0xab, 0xe9, 0x4a, 0xea, + 0x41, 0x63, 0x7a, 0xbc, 0x4e, 0x49, 0xb1, 0xb1, 0xce, 0x3a, 0x55, 0x15, 0xca, 0x7f, 0x9f, 0xad, 0x8a, 0x8b, 0xab, + 0x96, 0x6f, 0x71, 0x54, 0xef, 0x6c, 0x12, 0x02, 0x00, 0x35, 0x3c, 0xa4, 0xfa, 0x01, 0xc6, 0xb0, 0xdc, 0x33, 0xcc, + 0xb2, 0x0f, 0xd7, 0x1b, 0x34, 0x04, 0x6d, 0xc7, 0xe3, 0xc4, 0x16, 0xf9, 0x46, 0x0c, 0x68, 0xa4, 0xd4, 0x04, 0xd8, + 0x66, 0x85, 0x18, 0x3c, 0xeb, 0xf6, 0x27, 0x8a, 0x82, 0xa8, 0xe0, 0x88, 0x01, 0x10, 0x4e, 0x39, 0x2d, 0x3f, 0x2a, + 0xfc, 0xb0, 0x90, 0x60, 0x2a, 0x5e, 0x0e, 0xe4, 0xd3, 0x12, 0x10, 0x14, 0x83, 0xb2, 0x0c, 0x2d, 0x10, 0x82, 0xbe, + 0x99, 0x89, 0x51, 0x07, 0x67, 0x8c, 0xbe, 0x11, 0x31, 0xe0, 0x94, 0x02, 0x10, 0x8f, 0x39, 0x5d, 0x6b, 0x29, 0x5f, + 0x97, 0x2e, 0xfd, 0x8e, 0x9e, 0xca, 0x49, 0xe9, 0x85, 0xb0, 0x4d, 0xaf, 0x62, 0x5e, 0x8b, 0x4a, 0xa2, 0xeb, 0x65, + 0x73, 0x19, 0x1b, 0x9e, 0x2f, 0x5c, 0x9d, 0x56, 0x6f, 0xb6, 0xf0, 0xe1, 0x35, 0x17, 0x1f, 0x3e, 0x24, 0xb7, 0x2d, + 0xa3, 0xe0, 0xc3, 0x4e, 0xc3, 0x36, 0x72, 0x20, 0x08, 0xf0, 0xb7, 0xf5, 0xf5, 0x64, 0x6b, 0xb2, 0x15, 0x2e, 0x48, + 0x0f, 0xfb, 0x06, 0xdf, 0x0e, 0xc1, 0x9f, 0x68, 0xcd, 0x78, 0xcc, 0xd6, 0x3d, 0x34, 0xe4, 0xee, 0x65, 0x8d, 0x5f, + 0x30, 0x41, 0xe7, 0x67, 0x99, 0x79, 0x1f, 0x12, 0x5a, 0xee, 0x4b, 0xda, 0xe8, 0x11, 0xd3, 0x78, 0x14, 0x5d, 0x21, + 0xae, 0xf1, 0x2c, 0x3b, 0x3f, 0x1a, 0x1b, 0xb1, 0x9c, 0x38, 0x62, 0x3b, 0xcd, 0x2e, 0xdb, 0xe4, 0xd2, 0x52, 0x8d, + 0x6f, 0xef, 0x2a, 0x13, 0x8c, 0xaa, 0xa1, 0x7d, 0x5e, 0xd6, 0x67, 0x95, 0xcf, 0xfc, 0xfb, 0xfc, 0xad, 0x8b, 0x2a, + 0xc3, 0x1c, 0xa2, 0x19, 0x5f, 0xe3, 0x67, 0xa8, 0x4b, 0x28, 0xd2, 0x03, 0xf7, 0x7b, 0x99, 0xdd, 0x58, 0x73, 0x26, + 0x3f, 0xc2, 0x77, 0x4a, 0xb2, 0x0b, 0x6c, 0xc7, 0xbf, 0x8d, 0x7a, 0x2a, 0x94, 0x7e, 0xd4, 0x06, 0x16, 0x7f, 0x90, + 0xa4, 0x16, 0x24, 0x43, 0x09, 0x0e, 0xe2, 0xaa, 0x65, 0xef, 0xe9, 0x76, 0x6d, 0xc5, 0x82, 0x70, 0xe9, 0x6c, 0xed, + 0xe5, 0x8d, 0x69, 0x10, 0xe8, 0x44, 0x0b, 0xa3, 0xcd, 0xd9, 0x88, 0x79, 0xbc, 0xa1, 0x6a, 0x98, 0xbe, 0xa1, 0x34, + 0xb4, 0xc6, 0x17, 0xa0, 0x18, 0x26, 0x98, 0x22, 0xc2, 0xde, 0xb4, 0xf7, 0xf8, 0xc5, 0x86, 0xd5, 0x59, 0x50, 0xe3, + 0x55, 0x19, 0x21, 0x13, 0x97, 0x2b, 0x49, 0xf2, 0xe1, 0x3d, 0x81, 0xeb, 0xf8, 0xa7, 0xdd, 0x88, 0x77, 0x3d, 0xbe, + 0x93, 0x87, 0x65, 0x98, 0x98, 0x6e, 0xd1, 0x3a, 0x10, 0x43, 0x1c, 0x5b, 0xa1, 0x90, 0xa5, 0xfe, 0x58, 0xbd, 0x61, + 0x12, 0x8c, 0x9f, 0x1f, 0xac, 0xde, 0xcc, 0x8e, 0xff, 0x88, 0x06, 0x70, 0xee, 0x62, 0x1c, 0x81, 0x11, 0x66, 0x49, + 0x85, 0x1b, 0x65, 0x68, 0xa1, 0x8f, 0x8a, 0xab, 0xa9, 0x72, 0xe0, 0xc8, 0x12, 0xf2, 0x9a, 0xd2, 0xfe, 0x70, 0x3e, + 0xf3, 0xbb, 0x29, 0xf1, 0x33, 0x9d, 0x6e, 0xdf, 0xad, 0x1d, 0x56, 0x30, 0x1d, 0x07, 0xde, 0x1a, 0x29, 0xc8, 0xb1, + 0x14, 0xac, 0x6d, 0x39, 0x93, 0xe2, 0xb8, 0xa9, 0x3d, 0xeb, 0x55, 0x95, 0x9c, 0xd4, 0xfc, 0x6b, 0x9d, 0xac, 0x4d, + 0x3a, 0x73, 0x5b, 0x67, 0xfc, 0x74, 0x82, 0xbb, 0xf9, 0x5e, 0x69, 0x52, 0xf1, 0x3f, 0xcc, 0xaf, 0xb3, 0x64, 0xb5, + 0xf9, 0x78, 0xa1, 0x15, 0xb6, 0x89, 0x64, 0x80, 0xaf, 0xef, 0x34, 0x7d, 0x53, 0x20, 0x21, 0x6c, 0x57, 0xd3, 0xbd, + 0x0f, 0x0d, 0xd0, 0x9c, 0xb2, 0x13, 0xa4, 0xa8, 0x80, 0xd4, 0x9d, 0x58, 0x61, 0x90, 0x63, 0x60, 0x18, 0x3c, 0xf6, + 0x3e, 0xf5, 0x6e, 0x2d, 0x51, 0x57, 0x78, 0x2c, 0x34, 0x76, 0x63, 0xb0, 0x5a, 0x3e, 0x75, 0xe7, 0xff, 0x88, 0x5e, + 0xc1, 0xdf, 0x92, 0xf9, 0x1e, 0xf0, 0x0f, 0x82, 0x5a, 0xb6, 0x5a, 0x54, 0xde, 0x0a, 0xb9, 0x03, 0xfb, 0x78, 0x80, + 0x4f, 0x73, 0xf9, 0x40, 0x62, 0x6f, 0x8f, 0xcd, 0xdc, 0x75, 0x4d, 0xaf, 0xd5, 0x66, 0x6e, 0x75, 0xb4, 0x0c, 0x31, + 0x3a, 0x00, 0x20, 0x65, 0xc0, 0xf8, 0x29, 0xd6, 0x71, 0x67, 0xfc, 0x93, 0x79, 0xd0, 0xe7, 0x74, 0x7f, 0xf7, 0x3e, + 0x84, 0xdf, 0xd2, 0x12, 0xf1, 0x5d, 0xc4, 0xff, 0x1d, 0x5c, 0xf8, 0xd6, 0x31, 0x51, 0x25, 0x65, 0x07, 0x57, 0xe7, + 0xf0, 0x4d, 0xcf, 0x7b, 0x17, 0x57, 0x31, 0x8e, 0xbe, 0x87, 0x65, 0xf1, 0x47, 0x42, 0xa3, 0x29, 0x7c, 0x2d, 0x62, + 0x93, 0x97, 0xd0, 0x70, 0x33, 0x61, 0xb1, 0x8d, 0x2e, 0xcb, 0x1a, 0xc2, 0xeb, 0x7d, 0xa2, 0xb2, 0x8b, 0x27, 0x93, + 0x89, 0xba, 0xbe, 0x64, 0x29, 0xc0, 0xe5, 0xa6, 0x9a, 0xd1, 0x4b, 0xfb, 0x76, 0x8f, 0xba, 0xf4, 0x74, 0xff, 0xc1, + 0x65, 0x04, 0xaf, 0xd3, 0x66, 0xab, 0x3c, 0x37, 0x7d, 0x6a, 0x23, 0x3a, 0xa2, 0x7d, 0x5b, 0x57, 0xea, 0x05, 0x00, + 0x3a, 0xc0, 0x8b, 0xe3, 0x26, 0xba, 0x6a, 0xfa, 0xc7, 0x11, 0x90, 0xd6, 0xfc, 0x1e, 0x9b, 0x55, 0xb9, 0x91, 0x57, + 0x6a, 0x57, 0x09, 0xca, 0x8e, 0xf3, 0xe3, 0xbb, 0xd6, 0x5b, 0x3d, 0xbc, 0x54, 0x50, 0x29, 0xac, 0x6d, 0x7a, 0x6f, + 0xe9, 0xa4, 0xa7, 0x7d, 0x7e, 0x70, 0x5a, 0x50, 0x37, 0x74, 0xa9, 0xf5, 0x65, 0x07, 0x1e, 0xb5, 0x3e, 0x80, 0x9c, + 0xee, 0x60, 0x84, 0x23, 0x7a, 0x7f, 0x25, 0x6d, 0x09, 0xf0, 0x06, 0x68, 0x57, 0x9c, 0x80, 0xb6, 0x1d, 0x77, 0xe3, + 0xe6, 0x5b, 0xf8, 0xb3, 0x47, 0x90, 0x50, 0x5d, 0x75, 0x6e, 0xc9, 0xb4, 0x6b, 0x41, 0x45, 0x48, 0x2a, 0x24, 0x24, + 0x1c, 0x2e, 0x57, 0x97, 0x82, 0x51, 0x12, 0xd0, 0x57, 0x85, 0xc7, 0x43, 0xd9, 0xdb, 0x6e, 0x37, 0xae, 0x95, 0x91, + 0x64, 0x1a, 0xa8, 0x82, 0xc7, 0xd4, 0x1d, 0x72, 0x1f, 0x8f, 0x52, 0xb5, 0x90, 0x1e, 0xeb, 0x1f, 0x10, 0x24, 0x0d, + 0x0a, 0x1e, 0x99, 0x58, 0xdc, 0xd1, 0x40, 0xd4, 0x4a, 0x87, 0x1a, 0x66, 0xf6, 0x8e, 0x0b, 0x2e, 0xe6, 0xa8, 0x34, + 0xec, 0x32, 0xe0, 0x49, 0x66, 0x96, 0x41, 0x9f, 0x20, 0x77, 0x55, 0x3d, 0x15, 0xa6, 0xc3, 0x72, 0xc2, 0x00, 0xf1, + 0x94, 0xfa, 0x95, 0xdb, 0x5c, 0x37, 0xf8, 0x96, 0x24, 0x07, 0x60, 0xc0, 0xae, 0xb7, 0x42, 0xda, 0x2a, 0xdb, 0xa5, + 0xb2, 0xb1, 0x64, 0x25, 0x6c, 0xb8, 0xec, 0x62, 0x15, 0x01, 0xad, 0x20, 0xfa, 0x71, 0x8d, 0x30, 0x92, 0xfe, 0x42, + 0xa6, 0xd9, 0xb0, 0xfd, 0x39, 0xa6, 0xd5, 0x92, 0xdb, 0xb9, 0x25, 0xda, 0x00, 0x0d, 0xf8, 0x31, 0x86, 0xac, 0x25, + 0xb5, 0x26, 0xf6, 0xd6, 0xc5, 0xe4, 0xf9, 0x86, 0xe1, 0x69, 0x63, 0xd6, 0xcb, 0x64, 0xe3, 0xea, 0xc6, 0xa7, 0xb9, + 0x14, 0x1f, 0x0c, 0xba, 0x28, 0x4c, 0xa9, 0x39, 0x56, 0xe4, 0x5f, 0x02, 0xeb, 0xc2, 0x65, 0x42, 0xb2, 0x99, 0xca, + 0x84, 0x80, 0xc6, 0x6e, 0xcf, 0x08, 0x71, 0xf6, 0x03, 0x71, 0x26, 0xef, 0x2b, 0x5a, 0xd4, 0x20, 0x4f, 0x18, 0x8b, + 0x5f, 0xf6, 0xb0, 0xbb, 0x4d, 0xf3, 0xbc, 0x60, 0xcf, 0xb4, 0x62, 0x9d, 0x68, 0x26, 0x5c, 0x4f, 0xc9, 0xea, 0x1a, + 0x21, 0xe9, 0x53, 0xea, 0xf4, 0xc0, 0x8a, 0xa9, 0xbd, 0x53, 0x0a, 0x2c, 0x53, 0x10, 0x86, 0x76, 0xf2, 0xa8, 0x2c, + 0x29, 0xa9, 0x7a, 0x68, 0xbb, 0xb8, 0xa7, 0x50, 0x90, 0x31, 0xe2, 0xea, 0xb1, 0xcf, 0xcf, 0x00, 0x41, 0x79, 0x3a, + 0x83, 0x32, 0x7d, 0x4e, 0xb8, 0x91, 0xe7, 0x0c, 0x2d, 0xf2, 0x62, 0x62, 0x8e, 0x2a, 0x41, 0xd6, 0x48, 0xff, 0x55, + 0x84, 0x5c, 0x68, 0xf0, 0xf0, 0x48, 0x3a, 0x0d, 0xeb, 0x37, 0xc5, 0x0b, 0x0a, 0xce, 0x9f, 0xb2, 0x86, 0x18, 0xe7, + 0x86, 0x90, 0xe0, 0xfe, 0x70, 0x7f, 0xe6, 0x2e, 0x96, 0x11, 0x5a, 0xa5, 0x30, 0x2a, 0x2a, 0x99, 0x79, 0xe1, 0x87, + 0xb0, 0x0d, 0xf3, 0x62, 0x62, 0x50, 0x78, 0xdf, 0xa5, 0xf5, 0x99, 0x70, 0x88, 0xab, 0x6a, 0x8a, 0x79, 0x87, 0x14, + 0x35, 0x18, 0x4a, 0x6e, 0xf1, 0x5c, 0x33, 0x1a, 0x3d, 0xd6, 0x67, 0x46, 0x43, 0x6d, 0x92, 0xfc, 0x6a, 0x4e, 0xb0, + 0xb1, 0xe1, 0xa5, 0x90, 0xaa, 0x45, 0xc7, 0x01, 0xe1, 0x57, 0x1a, 0xc0, 0x5c, 0x68, 0x9a, 0xa7, 0x1d, 0x10, 0xb4, + 0xd2, 0x52, 0x0d, 0xa3, 0xaf, 0x08, 0x1e, 0x22, 0xa9, 0x1b, 0x83, 0x80, 0x8d, 0x60, 0x38, 0x04, 0xb4, 0xc5, 0x2f, + 0x2f, 0x7c, 0xa4, 0x61, 0xaa, 0x76, 0xec, 0x58, 0xce, 0x21, 0xa7, 0xca, 0xe0, 0x11, 0xff, 0x33, 0x11, 0x4c, 0xda, + 0xdc, 0x48, 0xbc, 0xa5, 0xec, 0xa6, 0x8e, 0xd3, 0xcc, 0x41, 0xfe, 0x96, 0x8e, 0xf6, 0x5a, 0xf9, 0xc2, 0x36, 0x99, + 0xb1, 0x57, 0xa3, 0x79, 0x28, 0x00, 0xb5, 0xff, 0x68, 0xdf, 0x65, 0xd1, 0x24, 0x7c, 0x3e, 0xbb, 0xef, 0x06, 0xf5, + 0x10, 0xd9, 0x99, 0x87, 0x62, 0xa5, 0xfb, 0x7a, 0xba, 0x34, 0x12, 0x1d, 0xc2, 0x35, 0xe6, 0x26, 0x9a, 0xed, 0x13, + 0x3d, 0x75, 0x26, 0xfb, 0xf9, 0xe8, 0x12, 0x2f, 0x67, 0x4e, 0x00, 0xd8, 0x23, 0x9e, 0x17, 0xdc, 0x51, 0xe2, 0x30, + 0xb5, 0xa9, 0x9d, 0x60, 0xa7, 0x3b, 0xda, 0xd8, 0xb5, 0x40, 0x29, 0x08, 0xa0, 0xf3, 0x7c, 0xfa, 0x7c, 0xfa, 0x32, + 0x86, 0xed, 0xd8, 0xc1, 0xe4, 0x64, 0x7e, 0xb1, 0x74, 0xcd, 0x6d, 0x91, 0xe9, 0xb0, 0xa4, 0x9b, 0x26, 0xe4, 0xbe, + 0x47, 0xe7, 0x36, 0xcf, 0xfa, 0xd3, 0xee, 0xda, 0x78, 0xa7, 0x21, 0x09, 0x8b, 0x00, 0xe5, 0xc5, 0x2e, 0x71, 0xe2, + 0xc0, 0x0d, 0xe7, 0xfb, 0x82, 0xc5, 0x82, 0x35, 0x12, 0x31, 0x44, 0x01, 0x19, 0x53, 0xff, 0xfc, 0x84, 0xee, 0xfa, + 0x1d, 0x5f, 0x0d, 0xa2, 0xe0, 0x98, 0x34, 0xd4, 0x9d, 0x57, 0x0f, 0xbb, 0x3e, 0xe6, 0x4c, 0x35, 0xc6, 0x7d, 0xee, + 0xfe, 0x80, 0x7d, 0xd7, 0x5a, 0xd3, 0x5c, 0x8f, 0x79, 0x69, 0x3b, 0xc5, 0x73, 0x0e, 0xe7, 0xf1, 0xe1, 0x7e, 0x1e, + 0xfc, 0x66, 0x78, 0x52, 0x29, 0xb6, 0x5d, 0x8e, 0x3c, 0xc9, 0x41, 0xd7, 0xf3, 0x1d, 0xfb, 0x78, 0x8f, 0xe1, 0x1e, + 0x24, 0x81, 0x0f, 0xaa, 0x54, 0x75, 0x96, 0xfb, 0x16, 0x0f, 0xc4, 0x06, 0x41, 0xe1, 0x75, 0x84, 0x78, 0x4d, 0x27, + 0xbf, 0x67, 0x07, 0xd8, 0x80, 0x2b, 0x20, 0x0f, 0xf8, 0x6c, 0xc5, 0x40, 0x5d, 0xc1, 0x90, 0xd9, 0xb7, 0x5b, 0x72, + 0x96, 0x66, 0x05, 0x3a, 0xe9, 0xf6, 0x26, 0x99, 0x5b, 0x0f, 0x34, 0xb0, 0x14, 0x89, 0x7c, 0xc9, 0xef, 0x58, 0x95, + 0x88, 0x45, 0x11, 0x9b, 0x4d, 0x3e, 0xc6, 0x62, 0x09, 0xf5, 0xfa, 0x52, 0xe4, 0x3d, 0x1f, 0x30, 0x67, 0x59, 0xc7, + 0xea, 0x9f, 0xc6, 0xee, 0x6e, 0x17, 0x31, 0xcc, 0xaf, 0x7f, 0xee, 0x81, 0xba, 0x58, 0x9e, 0xa7, 0xea, 0xcc, 0x30, + 0x82, 0xfd, 0x56, 0x2f, 0xb4, 0x1c, 0xb4, 0x31, 0x8f, 0xa9, 0xc9, 0x2d, 0xe9, 0xe3, 0x0b, 0xca, 0x89, 0x0e, 0xd0, + 0xfd, 0x15, 0x4a, 0xf7, 0x43, 0x47, 0x7d, 0xab, 0xfa, 0x7d, 0xe0, 0xa0, 0xea, 0x1c, 0x54, 0x77, 0x9c, 0x24, 0xb6, + 0x2b, 0x8a, 0x63, 0x58, 0x88, 0x6e, 0x0b, 0x76, 0xf8, 0x8c, 0x35, 0xcd, 0x1f, 0xe0, 0x80, 0xbb, 0x9b, 0x8c, 0x29, + 0x92, 0x4c, 0x3a, 0x9b, 0xd4, 0x1e, 0x00, 0xbd, 0x9f, 0xad, 0x73, 0x90, 0xbe, 0x5f, 0x3b, 0x54, 0xfb, 0xf3, 0xf8, + 0x80, 0xf3, 0x7c, 0xd9, 0xc4, 0x5c, 0x91, 0x38, 0x71, 0x85, 0x14, 0x74, 0x86, 0x50, 0xfa, 0x0b, 0x87, 0xbc, 0xcd, + 0xf3, 0xf4, 0xba, 0x99, 0xa8, 0x72, 0x27, 0xbb, 0x74, 0x82, 0x38, 0x78, 0x03, 0x01, 0x1e, 0x97, 0xfd, 0x5e, 0x6a, + 0xda, 0xe6, 0xc9, 0xed, 0x90, 0xd5, 0xea, 0xca, 0x77, 0xda, 0x07, 0x7c, 0x73, 0x93, 0x91, 0xc6, 0xf9, 0x9e, 0x87, + 0x9e, 0xca, 0xbe, 0x91, 0x35, 0x49, 0xed, 0xb7, 0x40, 0xc7, 0x55, 0x49, 0xc7, 0x18, 0x0d, 0x27, 0xf3, 0xe8, 0xbf, + 0x03, 0x31, 0x1c, 0xae, 0xcc, 0xbe, 0xd1, 0x38, 0x52, 0x74, 0xf8, 0xf2, 0xb0, 0x05, 0x47, 0xec, 0x49, 0x7c, 0x2f, + 0x5e, 0xe5, 0x4a, 0x97, 0xe8, 0x04, 0xb8, 0xed, 0x5d, 0x79, 0x63, 0xd3, 0xe5, 0xf3, 0xbf, 0x8f, 0x06, 0xdf, 0x1c, + 0x11, 0x31, 0x05, 0xaa, 0x24, 0xf6, 0xc1, 0xe6, 0x7b, 0x48, 0x68, 0xb2, 0x4b, 0x54, 0x61, 0xe8, 0x81, 0xb7, 0xd9, + 0xc6, 0x2d, 0x1c, 0x71, 0xf5, 0x55, 0x48, 0x80, 0xbd, 0x5c, 0xf7, 0xcc, 0xe8, 0x1e, 0xfc, 0xd4, 0xb4, 0x52, 0x84, + 0xc4, 0x37, 0x17, 0xf7, 0x0d, 0x1b, 0x8d, 0x58, 0xf6, 0x42, 0x66, 0x5d, 0x3c, 0x4a, 0xd1, 0xd3, 0x2a, 0xc3, 0xe9, + 0xa5, 0x3c, 0x27, 0x26, 0xab, 0x2c, 0xc8, 0x86, 0xae, 0x9e, 0xbe, 0xe5, 0xba, 0xf7, 0x56, 0x43, 0xf6, 0x12, 0xdf, + 0xbd, 0x72, 0x17, 0x20, 0xc7, 0xc4, 0xd3, 0x19, 0xd8, 0x05, 0xa9, 0x68, 0xaf, 0x97, 0x15, 0x36, 0x38, 0x56, 0x89, + 0xed, 0x14, 0x7c, 0x20, 0x36, 0x9f, 0x0b, 0x2e, 0x15, 0xa4, 0x2f, 0xc9, 0xfa, 0xfc, 0x2a, 0xc4, 0x1a, 0x98, 0x24, + 0xf0, 0xfe, 0xb3, 0x2c, 0x66, 0xfb, 0x12, 0x07, 0x08, 0x1c, 0x17, 0xef, 0x7b, 0x40, 0xef, 0x6f, 0x39, 0x92, 0x99, + 0x1c, 0xac, 0xc5, 0x7d, 0xc9, 0xcc, 0x08, 0xfe, 0xeb, 0xc7, 0x3b, 0x6b, 0x85, 0x8a, 0x5c, 0x8f, 0x61, 0x42, 0xb1, + 0xfb, 0x6e, 0xe7, 0x38, 0x37, 0x55, 0x82, 0x33, 0xa8, 0xe5, 0xef, 0xef, 0x78, 0x89, 0x86, 0x24, 0xe3, 0x36, 0x80, + 0xba, 0xac, 0x98, 0x74, 0x01, 0x2e, 0xea, 0xad, 0xc8, 0xd8, 0x51, 0xb0, 0xc7, 0x52, 0x6b, 0x76, 0x9c, 0x03, 0xc9, + 0xae, 0x16, 0x1a, 0x6d, 0x89, 0x22, 0xf7, 0x02, 0x62, 0x97, 0xcc, 0xf7, 0x75, 0xc5, 0x91, 0xee, 0x2a, 0x65, 0x4a, + 0x65, 0x4e, 0x39, 0x79, 0x92, 0x52, 0x7f, 0x63, 0xa8, 0x7a, 0xea, 0x0b, 0xec, 0x99, 0xb9, 0x3c, 0x5e, 0xcf, 0x37, + 0x7e, 0x24, 0x78, 0xbf, 0x56, 0x0c, 0x82, 0x58, 0xa1, 0xd9, 0x2e, 0x61, 0x32, 0x50, 0xa3, 0x3c, 0x39, 0x6e, 0x2c, + 0x37, 0x5e, 0xe2, 0x5f, 0x74, 0x95, 0x58, 0x99, 0x9f, 0xf5, 0x05, 0xb9, 0x0e, 0xde, 0xeb, 0x32, 0x2f, 0x49, 0xad, + 0xff, 0xb0, 0x3d, 0x1e, 0x4e, 0xd4, 0xaf, 0x37, 0xcc, 0xf3, 0xbb, 0x81, 0x54, 0x66, 0xdb, 0x51, 0x94, 0x95, 0x19, + 0x51, 0x0e, 0xed, 0x36, 0x01, 0xed, 0xa5, 0x5b, 0x5c, 0x40, 0xdd, 0xd8, 0xa2, 0x4b, 0x88, 0x61, 0xa0, 0xb8, 0x95, + 0x49, 0xa8, 0xce, 0xc6, 0x21, 0x4d, 0x29, 0x64, 0x8f, 0x88, 0xc5, 0x84, 0x85, 0xfa, 0xa7, 0xc3, 0xd3, 0xac, 0x06, + 0x5a, 0xef, 0x91, 0x6a, 0x8e, 0x15, 0xef, 0x1a, 0xaa, 0xb1, 0xb0, 0xd1, 0x2a, 0xff, 0x22, 0xc7, 0x8d, 0x43, 0x94, + 0x37, 0x5d, 0xe8, 0xe8, 0xc2, 0xbf, 0xa6, 0xd2, 0x49, 0x03, 0x2e, 0xce, 0xc5, 0x91, 0x04, 0xfe, 0xdf, 0xba, 0x24, + 0x42, 0xf1, 0x5b, 0xc4, 0x8a, 0x20, 0xbe, 0x36, 0xad, 0xfc, 0x6b, 0x27, 0x7d, 0x4e, 0xbc, 0xa3, 0x5d, 0xa5, 0x9a, + 0x49, 0x78, 0x31, 0x9c, 0xc8, 0x7c, 0x72, 0xe0, 0xc2, 0x57, 0x3e, 0x99, 0xec, 0xfe, 0x48, 0x28, 0x9f, 0xd8, 0xb3, + 0xc9, 0x71, 0x5a, 0x3b, 0xea, 0xfc, 0xe0, 0x97, 0x62, 0x07, 0xf3, 0xb0, 0x68, 0x53, 0x14, 0x8a, 0x5a, 0x1d, 0x8a, + 0x97, 0x45, 0x24, 0x82, 0x26, 0x14, 0xab, 0x87, 0x09, 0xc0, 0xc7, 0x4b, 0x8c, 0x72, 0x9f, 0xb5, 0x75, 0xa4, 0xfa, + 0xde, 0x84, 0x60, 0x65, 0xa0, 0xf6, 0xe8, 0x1c, 0x68, 0x13, 0x9b, 0x7a, 0xcc, 0xf2, 0x52, 0xab, 0x15, 0xee, 0x9a, + 0xd7, 0x71, 0x19, 0x5a, 0x95, 0xfc, 0x13, 0x64, 0x37, 0x9a, 0x53, 0x0c, 0x01, 0x45, 0x5f, 0x6e, 0x26, 0xb8, 0xe5, + 0xbe, 0x3f, 0x60, 0x38, 0x51, 0x9a, 0x15, 0xed, 0x29, 0x7a, 0x29, 0x12, 0xf3, 0x31, 0x96, 0x8e, 0xdf, 0xb3, 0x39, + 0xad, 0x28, 0x7d, 0x76, 0x67, 0xa0, 0xb8, 0x09, 0x74, 0x59, 0x37, 0x35, 0x4e, 0xd8, 0xb3, 0xb4, 0x6c, 0xf7, 0xdc, + 0xca, 0xb1, 0xa2, 0xad, 0x51, 0xc6, 0x4c, 0xaf, 0x34, 0x4b, 0x12, 0x18, 0xfe, 0xb1, 0xd5, 0x38, 0x0a, 0x07, 0xec, + 0x3a, 0xeb, 0xe1, 0x57, 0x68, 0xd8, 0x66, 0xca, 0x3f, 0xd2, 0xe2, 0xb9, 0xf8, 0x64, 0x6a, 0x30, 0xbf, 0x17, 0xa8, + 0x90, 0xb8, 0xf8, 0x4c, 0x34, 0xfd, 0x3e, 0xda, 0x5f, 0x47, 0x05, 0xc8, 0x98, 0x2a, 0x63, 0xe5, 0xff, 0x2b, 0x6d, + 0xd9, 0x6e, 0xc7, 0x71, 0xcd, 0x90, 0xc8, 0xa0, 0xd2, 0xe3, 0x2e, 0xee, 0xe9, 0xd7, 0xd1, 0x7f, 0x89, 0xa8, 0xae, + 0xdc, 0x79, 0x45, 0x5d, 0xf3, 0x5d, 0x52, 0x8b, 0xcc, 0x5e, 0xbf, 0x7b, 0xd5, 0x2a, 0x75, 0x50, 0x63, 0x5b, 0x6c, + 0xbc, 0xae, 0x2d, 0x7e, 0x7d, 0x00, 0xcd, 0xde, 0xe4, 0x37, 0xb3, 0x5d, 0x75, 0x8d, 0xd4, 0xa9, 0xd1, 0xd8, 0x31, + 0x8c, 0xde, 0xde, 0x0c, 0xbb, 0x0d, 0x3e, 0xb6, 0x46, 0x40, 0xab, 0x98, 0xbd, 0xfe, 0xbd, 0x0a, 0x0a, 0x7d, 0xab, + 0x9f, 0xc7, 0xba, 0xc9, 0xb8, 0xfc, 0xa1, 0x82, 0x40, 0x33, 0x4b, 0xe4, 0x51, 0x1e, 0x37, 0x8f, 0xde, 0x7a, 0xe2, + 0xb7, 0x36, 0xcf, 0xdd, 0xe4, 0x9e, 0x7c, 0xda, 0xaf, 0xe2, 0x36, 0x57, 0xf5, 0x2d, 0xe3, 0x2a, 0xe8, 0xb0, 0xdc, + 0x96, 0xaf, 0x0c, 0xbf, 0x57, 0xd0, 0xe1, 0x14, 0xfd, 0xfb, 0xe4, 0x0f, 0x1b, 0xb6, 0x8f, 0xda, 0x94, 0x50, 0x39, + 0x34, 0xbf, 0x51, 0x1f, 0x12, 0x98, 0x22, 0xb3, 0x8b, 0x3a, 0xe7, 0x5c, 0xb4, 0xa3, 0x65, 0x53, 0x6d, 0xad, 0x21, + 0xa1, 0x24, 0x90, 0x6a, 0x09, 0xc6, 0xad, 0xfd, 0x39, 0x69, 0x8f, 0xb8, 0x56, 0x50, 0x0e, 0x9d, 0xa3, 0xcc, 0x6f, + 0x45, 0xc8, 0xe7, 0x39, 0x6c, 0x6f, 0x71, 0xe0, 0x47, 0x10, 0x9f, 0x2b, 0x5a, 0x07, 0xf2, 0xfc, 0x51, 0x56, 0x2e, + 0x67, 0xb5, 0x6f, 0x27, 0x11, 0x33, 0x55, 0x3b, 0xb0, 0xe5, 0x05, 0xaf, 0xcc, 0x16, 0x76, 0xf7, 0xa4, 0x63, 0xbd, + 0xc1, 0xdf, 0x1f, 0x12, 0xd7, 0xa1, 0x1f, 0x79, 0xc9, 0x61, 0x5b, 0xd6, 0x53, 0xea, 0x56, 0xc7, 0x69, 0x37, 0xc5, + 0x62, 0x3b, 0xeb, 0x44, 0x6f, 0x83, 0xed, 0x6a, 0xe7, 0x51, 0x3e, 0x9d, 0x39, 0xc6, 0x35, 0xe9, 0x72, 0x3f, 0xa6, + 0xe9, 0x54, 0x6b, 0x88, 0x96, 0x2d, 0xa5, 0x7b, 0xac, 0x57, 0x2c, 0x60, 0x6b, 0xf6, 0xfe, 0xa1, 0x68, 0xeb, 0xa2, + 0x2d, 0x25, 0x28, 0x66, 0x6a, 0xf3, 0xd6, 0x22, 0x98, 0x3f, 0x92, 0x37, 0xeb, 0xa4, 0xce, 0x44, 0x5b, 0x53, 0x9f, + 0xfc, 0xa3, 0xa9, 0x47, 0xde, 0x17, 0x2c, 0xc5, 0x42, 0x3f, 0xac, 0x77, 0x0b, 0x8c, 0x25, 0xf4, 0x8c, 0xa1, 0x6d, + 0xce, 0xad, 0x23, 0x97, 0x90, 0xe5, 0x30, 0xe5, 0x8a, 0xc3, 0x69, 0x0e, 0x91, 0x25, 0xdd, 0xff, 0x97, 0xb7, 0x5e, + 0xcb, 0x48, 0xaf, 0x4f, 0xe8, 0xb8, 0x53, 0xf8, 0xf3, 0x32, 0x59, 0x40, 0x39, 0xd6, 0x56, 0x7a, 0x5e, 0xd9, 0x17, + 0x91, 0xf9, 0x28, 0x2e, 0xfc, 0x1f, 0xbe, 0xf2, 0x58, 0xfa, 0x9d, 0x75, 0xfd, 0x98, 0xba, 0xe4, 0xaf, 0xb9, 0x8f, + 0x86, 0x4e, 0x5a, 0x08, 0x99, 0xfc, 0x9f, 0x48, 0xca, 0x8e, 0x2c, 0xc2, 0xa3, 0x03, 0x9c, 0xc0, 0xce, 0x9d, 0x6c, + 0x49, 0x2b, 0x21, 0x19, 0x88, 0xae, 0xd1, 0x1c, 0xcd, 0x40, 0x36, 0x69, 0x03, 0x21, 0x3c, 0x6e, 0xce, 0x7d, 0x97, + 0xb9, 0x44, 0xfa, 0x65, 0x1e, 0xcd, 0xd0, 0x3d, 0x33, 0x64, 0xd1, 0x04, 0xa2, 0x23, 0x29, 0xc3, 0x56, 0xdb, 0xee, + 0xaf, 0x26, 0x76, 0x1f, 0x67, 0xd4, 0xb7, 0x07, 0xdc, 0x67, 0x84, 0x94, 0xdb, 0x51, 0x8e, 0x9a, 0x7e, 0xf0, 0x55, + 0x6b, 0x37, 0x87, 0x50, 0x17, 0x33, 0xe4, 0xb2, 0x00, 0x25, 0xbc, 0x58, 0xef, 0xeb, 0xf3, 0x13, 0xfd, 0xf1, 0x97, + 0x89, 0x21, 0xaa, 0x9a, 0x35, 0x69, 0x8a, 0x01, 0xb8, 0x8d, 0x39, 0xdf, 0xed, 0xbc, 0xf3, 0xe1, 0xdc, 0x6c, 0x41, + 0x98, 0xad, 0xa0, 0x18, 0xdd, 0x7c, 0x6c, 0xb0, 0x20, 0x88, 0xd7, 0x9f, 0x28, 0x51, 0xa4, 0x07, 0xf5, 0xc9, 0xd4, + 0x97, 0xb2, 0x90, 0x41, 0x8a, 0x86, 0x45, 0xd1, 0xad, 0x6e, 0x59, 0xd7, 0x05, 0x7f, 0x0a, 0x43, 0x96, 0x6f, 0x60, + 0x78, 0xb2, 0x49, 0xd2, 0xb9, 0x2e, 0x61, 0x8a, 0x27, 0xf3, 0x32, 0x47, 0x2a, 0xf3, 0x3e, 0x43, 0x3b, 0xbd, 0xfd, + 0xe4, 0x1f, 0xd8, 0x0a, 0x87, 0xfa, 0x32, 0x39, 0xf9, 0xdb, 0x07, 0xfe, 0xfe, 0xac, 0x65, 0xc5, 0xd4, 0x72, 0xb5, + 0x98, 0xac, 0xbc, 0x2a, 0x38, 0xa7, 0x24, 0x2a, 0xb8, 0xb4, 0xa2, 0xf3, 0x03, 0x0f, 0x89, 0x6d, 0xfc, 0xa5, 0x40, + 0xe6, 0xe2, 0x91, 0xbd, 0x67, 0x07, 0xb5, 0x46, 0x20, 0x14, 0xc4, 0x2c, 0xaa, 0x05, 0xbe, 0x33, 0x59, 0x37, 0x66, + 0xf6, 0x92, 0x14, 0x68, 0x31, 0x1a, 0x6c, 0xfb, 0xd1, 0x47, 0x43, 0xbc, 0x2a, 0x85, 0x2b, 0xc9, 0xf8, 0xb3, 0x15, + 0xa6, 0x24, 0x0c, 0x59, 0xb9, 0x83, 0xbb, 0x2c, 0x56, 0xae, 0x45, 0x2e, 0x5f, 0xde, 0x7f, 0x9c, 0xaa, 0xda, 0x7b, + 0x44, 0x2c, 0x78, 0xfd, 0x4c, 0x51, 0xd5, 0x1a, 0x50, 0x26, 0xa3, 0x3b, 0xc6, 0x5d, 0x2c, 0xd4, 0x28, 0x6b, 0x66, + 0x57, 0xa0, 0xe6, 0xd8, 0xa6, 0xa2, 0x10, 0xe0, 0x8f, 0xb7, 0x97, 0xca, 0x05, 0x1e, 0xcc, 0x0d, 0x85, 0x28, 0xa3, + 0x2c, 0xdf, 0x99, 0x8c, 0x85, 0xd1, 0x51, 0x2b, 0xc3, 0xbf, 0x89, 0x62, 0xf5, 0xdc, 0xb3, 0xd7, 0xc7, 0x49, 0xaf, + 0x1b, 0x61, 0xa0, 0xa9, 0x2c, 0x9b, 0x6e, 0xdb, 0xd6, 0x6d, 0x85, 0x6f, 0xaa, 0x15, 0xc8, 0x53, 0x80, 0xd6, 0x55, + 0xd8, 0x08, 0x38, 0x83, 0x63, 0xf6, 0x65, 0x80, 0x1e, 0x1a, 0x18, 0xab, 0xbf, 0xb4, 0x62, 0xb8, 0xb5, 0x21, 0xa8, + 0x07, 0xf1, 0xcb, 0x5c, 0x20, 0x64, 0x60, 0x81, 0x1d, 0x8d, 0xdc, 0x89, 0xdf, 0x76, 0x7a, 0x9f, 0x7e, 0xaf, 0x97, + 0x8d, 0xb6, 0x46, 0xc8, 0xac, 0xac, 0xb0, 0xdc, 0xe9, 0xde, 0xe9, 0x21, 0x6a, 0x94, 0x58, 0xe7, 0x2c, 0x34, 0x97, + 0xdd, 0x59, 0x35, 0x08, 0xae, 0x7e, 0x30, 0x28, 0x90, 0x1c, 0x0c, 0xc5, 0x76, 0x19, 0x41, 0xd0, 0x10, 0xd4, 0x47, + 0x79, 0x09, 0xb0, 0x66, 0x90, 0x43, 0x2b, 0xa3, 0xc5, 0xbf, 0xea, 0x8b, 0xfe, 0xd3, 0xff, 0xb1, 0xe8, 0x5d, 0x13, + 0x60, 0xd9, 0x1e, 0xae, 0x67, 0x67, 0x78, 0xc1, 0x0c, 0x1a, 0x15, 0xa3, 0x3d, 0x84, 0x53, 0x73, 0x9a, 0x88, 0x41, + 0x2d, 0x85, 0xd8, 0xfe, 0xc4, 0xa3, 0xe5, 0xe4, 0xc8, 0x43, 0xfe, 0xdb, 0x87, 0x12, 0x16, 0x9d, 0xe6, 0xcb, 0x73, + 0x04, 0x77, 0x05, 0x4e, 0x71, 0x82, 0xd9, 0xc2, 0xfe, 0xc9, 0x97, 0x4f, 0xa5, 0x89, 0x39, 0x74, 0x81, 0xa1, 0xcc, + 0xd5, 0x33, 0x22, 0x6f, 0x17, 0x99, 0xd1, 0xaa, 0x54, 0x09, 0x2d, 0x90, 0x43, 0xa6, 0xfc, 0x3c, 0x66, 0xc1, 0xa8, + 0x61, 0x3f, 0xd7, 0x8d, 0x64, 0x1f, 0x02, 0x33, 0x62, 0x8b, 0xda, 0x5c, 0x14, 0x83, 0x70, 0x85, 0xb8, 0xc9, 0x46, + 0xeb, 0x82, 0x96, 0x9e, 0xd2, 0x2e, 0xa9, 0xc8, 0x4d, 0x3c, 0xee, 0xc7, 0x51, 0xb8, 0xd5, 0xf2, 0x56, 0x8c, 0xde, + 0x83, 0x53, 0x59, 0xef, 0x1f, 0xe3, 0x0f, 0x86, 0x03, 0xc4, 0x51, 0x5c, 0x71, 0x62, 0xf7, 0xc3, 0xc5, 0x5f, 0xce, + 0xdc, 0x9e, 0x02, 0x97, 0x47, 0x6a, 0xf9, 0x72, 0xc5, 0xff, 0x13, 0x1f, 0xdd, 0x85, 0xd4, 0x0c, 0xe5, 0x07, 0xc1, + 0x03, 0x8f, 0xbc, 0x84, 0x8f, 0xfe, 0x0c, 0x3a, 0xfc, 0x6a, 0xe5, 0xb7, 0x53, 0xbf, 0x09, 0xef, 0x2d, 0xdc, 0x5e, + 0x6b, 0xae, 0xd6, 0x35, 0x56, 0x09, 0xe3, 0x0b, 0x6d, 0x3d, 0xfe, 0x92, 0x34, 0x26, 0x8c, 0xb3, 0x73, 0x0e, 0x0b, + 0x8d, 0x20, 0xc1, 0x2c, 0xb8, 0x49, 0x8f, 0x0f, 0x5c, 0xa6, 0x15, 0x51, 0x82, 0x10, 0x92, 0xcc, 0xf7, 0x63, 0xe8, + 0x2a, 0xb1, 0x1d, 0x89, 0x64, 0x54, 0x16, 0x87, 0x46, 0x9c, 0xaa, 0xf8, 0x69, 0x8a, 0x75, 0xc2, 0xa7, 0x9a, 0x81, + 0x87, 0x38, 0x89, 0xbc, 0xef, 0xec, 0xe6, 0x3d, 0x40, 0x2b, 0x0a, 0xd5, 0x0a, 0xfa, 0x2a, 0x9a, 0xb6, 0xfe, 0x7a, + 0x7b, 0x8f, 0x6d, 0x97, 0x3c, 0xa1, 0xd7, 0x73, 0xfc, 0xab, 0xda, 0xd5, 0x5a, 0x39, 0xa5, 0x6b, 0xfd, 0x74, 0xbb, + 0xb0, 0x98, 0x35, 0x7a, 0xbc, 0x98, 0x21, 0x6f, 0x05, 0xde, 0x6a, 0x7c, 0x8a, 0x36, 0x68, 0x82, 0xfb, 0x56, 0x6d, + 0x76, 0xc8, 0x4a, 0xb8, 0xfe, 0xac, 0x9e, 0x54, 0x92, 0x70, 0xa0, 0x31, 0x70, 0x58, 0x74, 0x19, 0xb4, 0x99, 0x3b, + 0x35, 0x70, 0x57, 0x24, 0x87, 0xd6, 0x1f, 0x58, 0xd1, 0x6c, 0xb5, 0x85, 0x0e, 0x34, 0x30, 0x0d, 0x7e, 0x1c, 0x99, + 0x79, 0x2c, 0xff, 0xd0, 0xf3, 0x5f, 0x06, 0x89, 0xae, 0xe3, 0x13, 0xec, 0x43, 0xf2, 0x45, 0x05, 0x4d, 0x87, 0x27, + 0x14, 0x2c, 0x3a, 0xc9, 0xd5, 0x26, 0x77, 0x19, 0x73, 0x28, 0xcb, 0x5d, 0xf5, 0x37, 0x23, 0xd1, 0xe9, 0xab, 0xf7, + 0x7c, 0x47, 0x3a, 0x2d, 0x6c, 0x08, 0x25, 0xce, 0x2f, 0x51, 0x5f, 0x71, 0x70, 0x66, 0xb1, 0x9e, 0xfe, 0xeb, 0xb5, + 0x4e, 0x58, 0x7b, 0xf0, 0x70, 0x58, 0xde, 0xb8, 0x20, 0xbf, 0x30, 0xd2, 0x92, 0x86, 0x01, 0x34, 0x5c, 0xb4, 0x38, + 0x35, 0xcb, 0x39, 0xf0, 0xc8, 0x2b, 0x73, 0xa2, 0x06, 0xaa, 0x3b, 0x7d, 0x1a, 0x1d, 0x94, 0xe0, 0x29, 0x5e, 0x9a, + 0xb2, 0x16, 0xd3, 0xf2, 0xa4, 0xd5, 0x1a, 0x4a, 0xbf, 0xb2, 0x24, 0x5d, 0x2b, 0x7c, 0x3a, 0xd7, 0xb4, 0x6a, 0xa8, + 0xc1, 0xbc, 0x0b, 0x02, 0xac, 0x28, 0x89, 0x5b, 0x9b, 0xe5, 0xdb, 0x6f, 0x7f, 0xd9, 0xe1, 0x18, 0x26, 0x3f, 0x7f, + 0x59, 0xc4, 0x6f, 0x1f, 0x6a, 0x98, 0xc7, 0x3c, 0x10, 0x17, 0x4f, 0x27, 0xfa, 0x39, 0xf5, 0x04, 0xcf, 0xa4, 0x5d, + 0xc4, 0x86, 0xd1, 0x80, 0xf3, 0xb7, 0x75, 0xab, 0x58, 0x58, 0x3b, 0x80, 0x61, 0x2e, 0xe8, 0x6b, 0x00, 0x18, 0x8e, + 0x50, 0x76, 0x21, 0x5a, 0x85, 0xea, 0xbd, 0xd1, 0x20, 0x6d, 0x6c, 0xb5, 0x27, 0x5a, 0x44, 0x10, 0x51, 0xbc, 0x8c, + 0x51, 0x0a, 0x8d, 0x41, 0x5e, 0xe2, 0x52, 0xb5, 0xc2, 0xce, 0xcb, 0x16, 0xfc, 0xb9, 0x70, 0x2a, 0x10, 0x44, 0x4d, + 0x64, 0x16, 0xb2, 0x91, 0x0d, 0x55, 0xda, 0x94, 0xd7, 0x59, 0xad, 0x46, 0x5c, 0xf3, 0xe1, 0xcd, 0x89, 0x05, 0xe4, + 0xec, 0xc0, 0xb6, 0x20, 0x0e, 0xab, 0x66, 0xc8, 0x89, 0x3e, 0xa7, 0x2d, 0xa3, 0xe7, 0x5b, 0xad, 0xb1, 0x53, 0xde, + 0x15, 0xea, 0x7e, 0xce, 0x47, 0x2c, 0x0c, 0x48, 0xed, 0xce, 0xff, 0x27, 0x68, 0x67, 0xdf, 0x97, 0x2b, 0x1d, 0xfe, + 0xe6, 0x6d, 0x31, 0x97, 0xdc, 0x8b, 0xa3, 0xc8, 0x15, 0xc7, 0x54, 0x08, 0x63, 0x5c, 0x84, 0x17, 0xfb, 0xe1, 0x55, + 0x67, 0x50, 0xe7, 0x65, 0x40, 0x43, 0x8e, 0x07, 0xf6, 0xde, 0x83, 0xa0, 0xc5, 0x17, 0x7b, 0x8b, 0xc6, 0x1a, 0x94, + 0x17, 0xc5, 0xb6, 0x0f, 0x20, 0xc3, 0xa6, 0xdc, 0xff, 0x8f, 0x9b, 0x17, 0xb9, 0x4b, 0x36, 0x12, 0x2f, 0x71, 0x29, + 0x5c, 0xb5, 0xf1, 0x77, 0x49, 0x05, 0x9f, 0x36, 0x62, 0x10, 0xcd, 0xb5, 0x93, 0x7f, 0x83, 0x65, 0xcb, 0xea, 0x2e, + 0xe5, 0xe1, 0xde, 0x81, 0x09, 0x8f, 0x6f, 0x6e, 0xbc, 0x0a, 0x0b, 0x7b, 0x38, 0x0c, 0x33, 0xde, 0xe3, 0x2e, 0x76, + 0xbd, 0xad, 0xaa, 0xd8, 0x2e, 0x32, 0xe6, 0xa2, 0xa9, 0x35, 0x46, 0x33, 0xb8, 0xb9, 0xa1, 0x85, 0xed, 0xdf, 0x4a, + 0xe8, 0x68, 0xf1, 0xb0, 0x75, 0x17, 0xe2, 0xe5, 0x75, 0xa1, 0x76, 0x14, 0x5c, 0xb2, 0x91, 0x94, 0x2c, 0xc8, 0xb0, + 0xee, 0x3b, 0xce, 0x01, 0x34, 0x85, 0xab, 0x11, 0xb7, 0x6b, 0xd9, 0x7e, 0x2d, 0xfd, 0xcb, 0xf2, 0x71, 0x33, 0xe8, + 0x90, 0x73, 0xa0, 0x10, 0x5f, 0x08, 0xd9, 0x9c, 0x10, 0x9d, 0xb4, 0x6d, 0xf5, 0x5e, 0x84, 0x23, 0xbf, 0x52, 0x64, + 0xea, 0x9f, 0x37, 0x33, 0x4c, 0xb5, 0x1e, 0xc1, 0xc0, 0x0e, 0x09, 0x57, 0xbf, 0xc5, 0xd0, 0xba, 0x65, 0xc0, 0xc6, + 0xaf, 0xe8, 0x6d, 0x3c, 0xab, 0x45, 0x0a, 0xf9, 0x05, 0x51, 0xdf, 0x5a, 0xd1, 0x04, 0xd7, 0xdd, 0x0b, 0x6b, 0x08, + 0xf3, 0x7b, 0x1a, 0x72, 0x0f, 0x6a, 0x03, 0xf9, 0x34, 0xdf, 0xef, 0x52, 0xf3, 0x07, 0x1c, 0xc1, 0x18, 0xe7, 0x1c, + 0xec, 0x9a, 0x7b, 0x6e, 0x8d, 0xa6, 0xaa, 0x6d, 0x03, 0x7b, 0x4b, 0xd7, 0xa3, 0x16, 0x1e, 0xbf, 0xed, 0xde, 0x3a, + 0xcb, 0x0e, 0xe3, 0x4d, 0xb9, 0x80, 0x8a, 0x45, 0x7b, 0xa1, 0xf2, 0x2c, 0x97, 0x31, 0x2e, 0x55, 0x20, 0x4e, 0x60, + 0x41, 0x4a, 0x6a, 0x6e, 0xca, 0x70, 0xcb, 0xd6, 0x65, 0x70, 0x34, 0x21, 0xdd, 0xfa, 0xf3, 0xcc, 0xe7, 0x66, 0x72, + 0x54, 0xd1, 0xa7, 0x08, 0xcc, 0x12, 0x7d, 0x5a, 0xc0, 0x51, 0xa6, 0xaf, 0x4b, 0x11, 0x24, 0xe2, 0xdd, 0xa0, 0xcf, + 0xb5, 0x02, 0x45, 0x21, 0x31, 0xf2, 0x2e, 0x7a, 0xb4, 0x40, 0x3f, 0x78, 0x1a, 0x8c, 0x49, 0x7c, 0xfa, 0xef, 0xb2, + 0x57, 0xf1, 0x29, 0x59, 0xca, 0xfa, 0xde, 0x90, 0x4a, 0xe4, 0x24, 0x0d, 0x91, 0x74, 0x7e, 0xaa, 0xc0, 0xd4, 0x71, + 0xee, 0xcd, 0x5f, 0xa1, 0x1f, 0x7a, 0x2b, 0x26, 0x08, 0xd8, 0x8f, 0x71, 0x11, 0xd7, 0xe5, 0x0b, 0xfb, 0x98, 0x0e, + 0xcc, 0x02, 0xa3, 0xd5, 0x19, 0xf1, 0x40, 0xd2, 0x49, 0xb0, 0x94, 0x5d, 0x33, 0x97, 0x3a, 0x00, 0xe4, 0x6b, 0x93, + 0xcb, 0xde, 0x11, 0xe2, 0x4b, 0x76, 0x7d, 0x47, 0x10, 0x29, 0x53, 0xad, 0xa2, 0x1d, 0xb9, 0x47, 0x9a, 0x08, 0x96, + 0xea, 0x14, 0x2d, 0x6d, 0xb3, 0xed, 0xed, 0xec, 0x78, 0xee, 0xef, 0x72, 0x5f, 0x61, 0x8a, 0x16, 0xd4, 0xdf, 0xc5, + 0x45, 0x52, 0x55, 0x10, 0x21, 0x66, 0x70, 0x83, 0xb3, 0x31, 0xe2, 0x52, 0xa9, 0xe4, 0xcf, 0xf6, 0x40, 0x73, 0xd3, + 0xab, 0xd4, 0x54, 0xb8, 0x1a, 0x0b, 0x9b, 0xd8, 0x52, 0x3b, 0xb0, 0x44, 0x82, 0x07, 0x4f, 0x6f, 0x71, 0x5f, 0x96, + 0xbb, 0x13, 0xc1, 0x69, 0xd1, 0xd2, 0x13, 0x0f, 0xcb, 0x44, 0xbe, 0x93, 0xdd, 0xee, 0x9a, 0x22, 0xcd, 0x1e, 0x37, + 0xe9, 0x0e, 0x47, 0x29, 0x63, 0x55, 0x69, 0xde, 0x81, 0x6b, 0x2e, 0x81, 0x8b, 0x8e, 0x11, 0xad, 0x87, 0x26, 0xf5, + 0x69, 0x00, 0x5a, 0x40, 0xb5, 0x16, 0x39, 0x0e, 0xea, 0x80, 0x89, 0x2b, 0x1a, 0x06, 0x97, 0x00, 0xb1, 0xf3, 0x19, + 0xb2, 0xf3, 0x59, 0xc8, 0xb7, 0x94, 0x9b, 0x6d, 0x90, 0x44, 0xbe, 0x6a, 0x7d, 0x28, 0x36, 0x23, 0xf1, 0xa1, 0xa1, + 0x0f, 0xcf, 0x35, 0xfa, 0xf1, 0xcd, 0x55, 0xbf, 0xe2, 0xad, 0xe3, 0x9c, 0x06, 0x1f, 0x93, 0x74, 0xd1, 0x0a, 0xcf, + 0x65, 0x79, 0xa7, 0x85, 0xa7, 0xf1, 0x14, 0x86, 0x53, 0x65, 0xfd, 0x6a, 0x73, 0xd5, 0xf2, 0xd4, 0x46, 0x51, 0x5f, + 0x49, 0xf1, 0xef, 0xce, 0xc4, 0x6a, 0x89, 0x98, 0x63, 0x52, 0xae, 0x79, 0x5a, 0x4d, 0x4b, 0xc7, 0xff, 0xfc, 0xd0, + 0xf9, 0xa5, 0xec, 0x04, 0xc0, 0x54, 0xc6, 0x18, 0x61, 0xc5, 0x7b, 0x19, 0x35, 0x43, 0xec, 0x25, 0xb3, 0xc3, 0x58, + 0xa3, 0xda, 0x3f, 0xdd, 0x7a, 0x14, 0xb6, 0x25, 0xe9, 0xe1, 0xde, 0x42, 0xf0, 0x85, 0xac, 0xf4, 0xef, 0xb6, 0x8e, + 0xb4, 0xe4, 0xc5, 0x45, 0x8c, 0x92, 0x20, 0x91, 0x8e, 0xa3, 0xb6, 0x56, 0x73, 0x13, 0x16, 0x1a, 0xc6, 0x52, 0x5b, + 0xa7, 0xac, 0x16, 0xb6, 0x14, 0x63, 0x8e, 0x64, 0x3c, 0x32, 0xcf, 0x48, 0xcf, 0x11, 0xde, 0xe5, 0xd6, 0xd1, 0xa0, + 0xea, 0xee, 0xb9, 0xf6, 0xc2, 0x8b, 0xf6, 0x25, 0xe7, 0x9f, 0x5b, 0x49, 0x0d, 0xc5, 0x9d, 0x0c, 0x8d, 0xea, 0x89, + 0xc3, 0xec, 0xda, 0xe4, 0x03, 0x41, 0x13, 0x27, 0x2b, 0x9d, 0xe1, 0xe7, 0xdc, 0xf2, 0x17, 0xc7, 0x53, 0x13, 0xad, + 0x81, 0x6d, 0x47, 0x16, 0x6a, 0x03, 0xfc, 0x06, 0x6f, 0x42, 0xb3, 0x80, 0x96, 0xb0, 0x18, 0xe2, 0xd4, 0xcd, 0x98, + 0x34, 0x49, 0x3a, 0x5d, 0x20, 0xfc, 0x74, 0x9b, 0x99, 0x8e, 0x65, 0x0f, 0x77, 0x39, 0x90, 0xa9, 0xa1, 0x14, 0x8f, + 0xa0, 0xb9, 0x3c, 0x89, 0x6e, 0xc6, 0x54, 0xc2, 0x37, 0x6c, 0x9f, 0xb3, 0xf4, 0x1e, 0xbd, 0x42, 0x9b, 0x9e, 0x05, + 0x2b, 0x8f, 0x1b, 0x41, 0x8b, 0x4c, 0x18, 0x20, 0xbb, 0xe7, 0x00, 0x96, 0x16, 0xdb, 0x8b, 0xa6, 0xa3, 0x23, 0x91, + 0x2d, 0xc6, 0xd6, 0x38, 0xc7, 0xe6, 0x2a, 0xd4, 0x82, 0x9d, 0xe5, 0x25, 0x50, 0x36, 0xb2, 0xc3, 0x3b, 0xc6, 0x9f, + 0xbc, 0x21, 0x01, 0x4c, 0x69, 0xea, 0xd3, 0x2e, 0x7a, 0x15, 0x6c, 0xef, 0xfa, 0x58, 0xc4, 0x81, 0x39, 0x70, 0xc3, + 0x58, 0x1a, 0x3b, 0x53, 0x75, 0xcd, 0x03, 0x5a, 0xa9, 0xaa, 0x2b, 0x06, 0x21, 0x09, 0xc6, 0xbc, 0x3c, 0x15, 0x52, + 0xb3, 0x50, 0x2d, 0xdd, 0x74, 0x6a, 0x9b, 0xa0, 0xb0, 0x38, 0x9e, 0x9a, 0x3d, 0x0c, 0x32, 0x5c, 0xbf, 0x7f, 0x66, + 0x06, 0x48, 0x12, 0xae, 0x04, 0x94, 0xd1, 0xb0, 0xb3, 0x6d, 0xd6, 0x43, 0xbf, 0xdd, 0x24, 0xa2, 0xdd, 0xae, 0x1c, + 0x33, 0xea, 0x83, 0xea, 0x85, 0xe1, 0xf4, 0xce, 0x08, 0x8d, 0x84, 0x04, 0xd8, 0xc8, 0x8f, 0xfa, 0x0b, 0x52, 0xb1, + 0xc4, 0x45, 0xdb, 0x79, 0x61, 0xd6, 0xef, 0xf3, 0x40, 0x66, 0xf0, 0x04, 0x9b, 0xe6, 0x2c, 0x82, 0x6a, 0x24, 0x0b, + 0x12, 0x04, 0x44, 0xd5, 0xb6, 0x4f, 0x55, 0xb5, 0x15, 0x34, 0xce, 0xe3, 0x17, 0x9c, 0x9e, 0x7e, 0x6d, 0x10, 0x54, + 0xe2, 0x5f, 0xd9, 0xbc, 0xc5, 0x6b, 0x60, 0x8b, 0xeb, 0x91, 0xf2, 0x45, 0x5d, 0x96, 0x3f, 0xba, 0xb0, 0x4a, 0xfa, + 0xf7, 0xd6, 0x58, 0x88, 0x2a, 0x7f, 0xba, 0x42, 0x21, 0xa9, 0x3c, 0xba, 0xf3, 0x46, 0xf0, 0x25, 0x3b, 0x89, 0xc6, + 0xa2, 0x9d, 0xf4, 0x84, 0x1d, 0xae, 0x4a, 0x23, 0x88, 0x14, 0xff, 0x62, 0x45, 0x90, 0xb8, 0x2a, 0x5a, 0x76, 0x32, + 0x68, 0x25, 0x7b, 0xa0, 0xce, 0x89, 0x1b, 0xf3, 0x72, 0x6d, 0xca, 0xb7, 0x77, 0x27, 0x3a, 0xc8, 0x1a, 0x93, 0xe0, + 0x51, 0x83, 0x7d, 0xcb, 0x64, 0xbb, 0xec, 0x38, 0xf5, 0xa6, 0xa7, 0xef, 0xb9, 0x19, 0x09, 0x69, 0x09, 0x10, 0xf9, + 0xda, 0x8d, 0xc4, 0xec, 0xd6, 0xe3, 0x6d, 0x47, 0x2c, 0xe6, 0x76, 0x22, 0x4a, 0xa3, 0x8e, 0x6b, 0xf3, 0x10, 0x85, + 0x60, 0x85, 0xb1, 0x44, 0x97, 0x5f, 0x49, 0xe4, 0x16, 0x0b, 0x1b, 0xfb, 0x58, 0x7d, 0xca, 0x61, 0x93, 0x03, 0x84, + 0x70, 0xa9, 0x5b, 0xfd, 0x2b, 0xf4, 0x36, 0x7b, 0x42, 0xbf, 0x62, 0xe4, 0xe0, 0xa1, 0x0c, 0xd6, 0xad, 0x79, 0xd7, + 0x82, 0xc7, 0x2a, 0x2a, 0xf7, 0x61, 0x11, 0x21, 0x14, 0xf7, 0xfb, 0xd1, 0x50, 0xed, 0x5a, 0x62, 0x44, 0xf4, 0x28, + 0x19, 0x48, 0x6d, 0x3a, 0x85, 0x2b, 0x51, 0xc4, 0xe5, 0xa5, 0x1d, 0xcf, 0x67, 0x3b, 0xbb, 0x1b, 0x8d, 0x34, 0xf6, + 0xdd, 0xc0, 0xf1, 0x72, 0x2b, 0xcd, 0x1a, 0x8b, 0xb6, 0xef, 0x67, 0xb7, 0xb6, 0x05, 0xa2, 0x30, 0x62, 0xc4, 0xdc, + 0xb6, 0xf3, 0x09, 0xe9, 0x60, 0x27, 0x9f, 0xb1, 0x8f, 0x0d, 0x8c, 0x67, 0x30, 0x9b, 0xaa, 0xbe, 0xf6, 0xee, 0x67, + 0xf6, 0xbf, 0x0b, 0x6a, 0x23, 0x3f, 0x5f, 0xae, 0x44, 0x08, 0x68, 0x5c, 0xe8, 0x45, 0x86, 0x88, 0x1e, 0x4d, 0xb2, + 0x73, 0xd7, 0xe8, 0x25, 0xf6, 0xba, 0x90, 0x61, 0x87, 0xe3, 0x8f, 0xd6, 0x66, 0x4f, 0x68, 0x8e, 0xbf, 0x9f, 0x5d, + 0x1b, 0xfb, 0x5a, 0x8d, 0x93, 0x2c, 0x58, 0x95, 0x89, 0xd3, 0xf5, 0x7b, 0x8e, 0x28, 0x3e, 0xd3, 0xa9, 0xfb, 0x8e, + 0x36, 0x9e, 0x49, 0xd9, 0x48, 0x2a, 0xdd, 0x48, 0xa0, 0x32, 0x2b, 0x93, 0x77, 0x7b, 0x01, 0x50, 0x6d, 0x04, 0x58, + 0x34, 0x17, 0x9a, 0xa9, 0xec, 0x8b, 0xce, 0xd3, 0x43, 0xa4, 0x1c, 0x0f, 0x6f, 0x8e, 0x96, 0xa1, 0xc5, 0xeb, 0xd3, + 0x9c, 0xfd, 0x9b, 0x5b, 0x0d, 0x1d, 0xed, 0x1d, 0x15, 0x49, 0x73, 0xc1, 0xf4, 0xff, 0x62, 0xc5, 0x34, 0x00, 0x84, + 0x83, 0x06, 0x9c, 0xae, 0x72, 0x83, 0x0b, 0x92, 0x17, 0x98, 0xe6, 0xe2, 0x4c, 0xa0, 0xb5, 0x0d, 0x44, 0x54, 0xb4, + 0xe6, 0x47, 0x97, 0x71, 0xf1, 0x88, 0x76, 0x8e, 0x41, 0x54, 0xd4, 0xdf, 0xf6, 0x99, 0xb4, 0x82, 0xd9, 0xe7, 0x1c, + 0xb8, 0x5e, 0x33, 0x95, 0x12, 0x14, 0x83, 0xed, 0xb6, 0xfd, 0x36, 0x65, 0x10, 0x6a, 0xf4, 0x77, 0x52, 0xa1, 0x03, + 0xa3, 0x20, 0x47, 0x08, 0xe4, 0xdd, 0x1e, 0xf4, 0x59, 0x50, 0xd4, 0x33, 0xe2, 0x80, 0x9d, 0xbf, 0x76, 0x1c, 0xce, + 0x50, 0x7c, 0x9d, 0xfb, 0xf1, 0xdb, 0xba, 0x68, 0x1e, 0xdc, 0xf8, 0x43, 0xff, 0xfe, 0x11, 0x6d, 0x8b, 0xf6, 0x09, + 0x36, 0xc7, 0xcf, 0xe8, 0xf0, 0xb6, 0x19, 0x9c, 0x79, 0x7b, 0x42, 0x23, 0x10, 0x5b, 0x90, 0x3c, 0x17, 0xe8, 0x9c, + 0x43, 0x13, 0x9e, 0x67, 0xcd, 0x4c, 0x7e, 0xfa, 0x26, 0x55, 0xee, 0xa3, 0xec, 0xf8, 0x58, 0x74, 0xbb, 0xc4, 0x33, + 0x94, 0x6e, 0xe7, 0x56, 0x72, 0xdb, 0x87, 0x60, 0xfe, 0xe1, 0xb7, 0xbb, 0xdd, 0x63, 0x65, 0xfe, 0xcf, 0x4f, 0xd6, + 0x44, 0x3f, 0xdb, 0x45, 0xf3, 0xcb, 0x2a, 0x08, 0x0a, 0x5f, 0xee, 0x3a, 0xbd, 0x2c, 0xd0, 0xb8, 0x43, 0xd5, 0xb5, + 0x86, 0xab, 0xb9, 0x9a, 0xb9, 0x67, 0x2e, 0xee, 0x38, 0x9f, 0x91, 0x97, 0xd5, 0xe2, 0x7a, 0x40, 0xbf, 0x7d, 0x35, + 0xe6, 0x3f, 0xbf, 0x84, 0x26, 0xe5, 0xdc, 0xfd, 0x89, 0xf0, 0x7f, 0x83, 0x6b, 0x7a, 0xe7, 0x8e, 0xa2, 0xe0, 0x8c, + 0xeb, 0x9a, 0x61, 0x9b, 0x9c, 0x73, 0xe1, 0xb8, 0x4e, 0x39, 0xf0, 0xea, 0x10, 0x9b, 0x80, 0x30, 0xad, 0x8c, 0x67, + 0x4e, 0x9f, 0x46, 0xf2, 0x67, 0x0c, 0x18, 0x76, 0x1d, 0x82, 0x86, 0x60, 0x40, 0x89, 0xc5, 0xe8, 0xd4, 0x9d, 0x8c, + 0xa9, 0x26, 0x42, 0xd6, 0x80, 0x2d, 0x81, 0x12, 0xad, 0x6f, 0x81, 0x00, 0x5a, 0x3a, 0x81, 0x97, 0x8d, 0x8a, 0x1e, + 0x2d, 0x59, 0xc3, 0xe0, 0x60, 0xfe, 0x47, 0x1c, 0x8e, 0xe0, 0x7c, 0x8b, 0x84, 0xb8, 0x9b, 0x5b, 0x8f, 0xd2, 0xc6, + 0xf4, 0x89, 0xbe, 0x66, 0x1f, 0xf5, 0x14, 0xa4, 0xf9, 0xaf, 0x8b, 0x01, 0x82, 0x61, 0x38, 0x4e, 0x38, 0x5e, 0x25, + 0xf3, 0x0b, 0xa2, 0x76, 0xb1, 0xfe, 0xe5, 0x0c, 0xc6, 0xf6, 0x6f, 0xbc, 0xb6, 0x91, 0xff, 0x7f, 0x83, 0x25, 0xb7, + 0xbf, 0xe4, 0xe6, 0xcb, 0xbf, 0x96, 0xc7, 0x5f, 0xbe, 0xe6, 0x5b, 0xbe, 0x9b, 0x26, 0x78, 0xa7, 0xeb, 0x5e, 0xc9, + 0x50, 0xf3, 0xf3, 0x15, 0x46, 0xb8, 0x86, 0xc1, 0xd1, 0x58, 0x00, 0x1f, 0x2a, 0xda, 0x1c, 0x3d, 0x1b, 0x28, 0x13, + 0xfb, 0x35, 0x1e, 0x51, 0xbd, 0x5e, 0x81, 0x8f, 0x5d, 0x8d, 0x6b, 0x99, 0x5e, 0x26, 0x5a, 0xf3, 0x5c, 0xd9, 0xa5, + 0x86, 0x8a, 0x3b, 0xda, 0x02, 0xbe, 0x41, 0x5f, 0x57, 0xbe, 0xc4, 0x3a, 0xb2, 0xd9, 0x94, 0x27, 0x09, 0x4a, 0x3c, + 0x70, 0xd1, 0xf0, 0xd5, 0x33, 0xdb, 0x62, 0x7d, 0xf8, 0x73, 0xd1, 0x54, 0x13, 0x88, 0x7a, 0x54, 0x63, 0x36, 0x62, + 0xad, 0x55, 0x46, 0xcb, 0xab, 0x61, 0xe8, 0x48, 0xc6, 0xc5, 0xac, 0x32, 0xa1, 0x0c, 0xe8, 0x07, 0x4c, 0xd6, 0x2b, + 0xfa, 0x47, 0x3b, 0x45, 0xbe, 0x54, 0x9f, 0x52, 0xd4, 0xc3, 0xe3, 0x09, 0x8e, 0x53, 0x1f, 0x35, 0xfc, 0xa6, 0x49, + 0x5c, 0x85, 0x6b, 0x38, 0x37, 0x59, 0x73, 0xe6, 0x65, 0x6b, 0x97, 0x3a, 0x98, 0xd3, 0x84, 0xfd, 0x5b, 0xdb, 0x62, + 0xf1, 0x79, 0x12, 0x68, 0xbb, 0x68, 0x26, 0x97, 0xca, 0x5a, 0x10, 0x65, 0xfa, 0xf0, 0x06, 0x12, 0x88, 0xce, 0xb9, + 0x06, 0xea, 0xdb, 0xd4, 0x71, 0x38, 0xb7, 0x68, 0xab, 0x16, 0x2e, 0xad, 0xde, 0x6c, 0xa6, 0x70, 0xc2, 0x67, 0x97, + 0xf6, 0x22, 0x99, 0x68, 0xde, 0xe5, 0x89, 0x64, 0xfa, 0x6e, 0xec, 0xb3, 0x01, 0x71, 0x8b, 0x41, 0xcf, 0xc2, 0x44, + 0x19, 0xae, 0x29, 0xd8, 0x75, 0x95, 0x18, 0xc7, 0x01, 0xe0, 0xec, 0xd3, 0x90, 0x1b, 0x70, 0x78, 0x5d, 0x1b, 0x43, + 0x27, 0xba, 0x5b, 0xf4, 0x4a, 0x0a, 0x42, 0x50, 0xe5, 0xb0, 0xc4, 0xe6, 0x3d, 0xd9, 0x0b, 0xcb, 0x37, 0x78, 0xd8, + 0xb9, 0x53, 0x32, 0xe1, 0x4e, 0xe7, 0x09, 0x53, 0x56, 0xd1, 0x3b, 0xe4, 0x66, 0xc4, 0xeb, 0x0a, 0x8c, 0x29, 0x5c, + 0x01, 0x1b, 0x74, 0x88, 0xba, 0xf1, 0xdb, 0xef, 0x7a, 0xb9, 0xd9, 0xbb, 0x2d, 0x36, 0x33, 0x9e, 0xd1, 0x5a, 0xef, + 0x60, 0xed, 0x2c, 0xbd, 0xb6, 0x33, 0xb2, 0x50, 0x20, 0xc0, 0xc9, 0xdd, 0x66, 0x3d, 0x38, 0x46, 0x34, 0x97, 0xff, + 0x3b, 0x8b, 0x5d, 0x55, 0x4e, 0xbd, 0x31, 0xf4, 0x8a, 0x64, 0xe4, 0x10, 0x80, 0x64, 0x9c, 0x35, 0x1d, 0xa3, 0xd9, + 0xbb, 0xda, 0x76, 0x0e, 0xd3, 0xec, 0xdb, 0x34, 0xd7, 0xef, 0x4f, 0xe6, 0x5e, 0x20, 0x3f, 0x03, 0x37, 0xca, 0xd5, + 0x27, 0x8c, 0x75, 0x0f, 0xb4, 0x34, 0xb3, 0xfa, 0x46, 0x9e, 0xab, 0x19, 0xcb, 0x6d, 0xb0, 0xef, 0x06, 0x15, 0x0f, + 0xb0, 0xa0, 0x3f, 0xc9, 0xcb, 0xf3, 0x77, 0xaa, 0xe6, 0x6e, 0x7a, 0x84, 0x8d, 0x95, 0xcb, 0x70, 0x12, 0x2f, 0x87, + 0xfe, 0x05, 0x47, 0xcf, 0x89, 0xf9, 0x5f, 0x56, 0x24, 0x5d, 0x31, 0xcf, 0x30, 0x99, 0x18, 0xa5, 0x21, 0xc5, 0x19, + 0x2a, 0xe8, 0x7f, 0x58, 0x70, 0xbb, 0x6e, 0xd8, 0x40, 0x8a, 0x7f, 0xe3, 0x3e, 0x3b, 0x9e, 0x7b, 0xab, 0xcc, 0xa4, + 0x97, 0xcd, 0x71, 0x4b, 0xae, 0x6a, 0x5a, 0xcd, 0x7c, 0x56, 0x90, 0x4c, 0xdb, 0xcd, 0x63, 0x5e, 0x19, 0xbf, 0x74, + 0x13, 0xc9, 0x64, 0x4f, 0x67, 0x37, 0x40, 0x6b, 0x07, 0xda, 0xa7, 0xc4, 0xe9, 0x49, 0xa7, 0xab, 0x37, 0x3b, 0xa3, + 0x68, 0x9b, 0x5c, 0xa7, 0x1f, 0x68, 0xbd, 0xa0, 0xa3, 0xdb, 0x59, 0x2b, 0xc9, 0x73, 0x9f, 0xd8, 0x24, 0xc1, 0x4f, + 0xae, 0x63, 0xc1, 0xb1, 0x69, 0x40, 0x3f, 0xce, 0xcb, 0xf3, 0x4d, 0xde, 0x45, 0x06, 0x7c, 0xa6, 0xb0, 0xd9, 0xec, + 0x86, 0x31, 0xc7, 0x86, 0x18, 0x21, 0x48, 0x97, 0xe7, 0xed, 0x69, 0xdd, 0x09, 0x8d, 0xb7, 0x2c, 0x5c, 0x9b, 0x93, + 0x8b, 0xc2, 0xcf, 0xf4, 0xda, 0x34, 0x5c, 0xd0, 0x14, 0xda, 0xe7, 0x7e, 0x9a, 0x84, 0xe9, 0x1e, 0x93, 0xa8, 0x1a, + 0xee, 0xa6, 0x85, 0xfa, 0xb0, 0x50, 0x9e, 0x37, 0xe0, 0x05, 0x2b, 0xdc, 0xf3, 0x39, 0x39, 0x16, 0xf6, 0xe6, 0xbd, + 0xdb, 0x07, 0xb0, 0x5a, 0xcb, 0x3d, 0x66, 0xdc, 0xf1, 0xbe, 0xa3, 0x95, 0xfd, 0xd7, 0xb2, 0xe4, 0x58, 0x87, 0xad, + 0x9a, 0x65, 0xf0, 0x15, 0xa1, 0x39, 0x52, 0x03, 0x4d, 0x7c, 0x40, 0xa0, 0x0a, 0x84, 0x3b, 0x32, 0x6d, 0x67, 0x3c, + 0x65, 0xd4, 0xd1, 0x86, 0xa3, 0xa9, 0x13, 0x3b, 0x1e, 0x9e, 0x14, 0x5b, 0x0c, 0xbf, 0x35, 0xbd, 0x01, 0xcf, 0x7d, + 0x0f, 0xb4, 0xdd, 0xbd, 0x0e, 0x53, 0x7c, 0x65, 0x56, 0xd8, 0xc1, 0xaa, 0xd5, 0x18, 0x84, 0xd8, 0xb4, 0xca, 0xdc, + 0x15, 0x53, 0x02, 0x0c, 0xe7, 0xd4, 0xf8, 0xe2, 0xe0, 0x36, 0x34, 0x72, 0x6f, 0x32, 0x05, 0x4a, 0x1c, 0x5d, 0x0b, + 0x78, 0x92, 0xc6, 0x7c, 0x5a, 0x55, 0xc0, 0xaf, 0x8d, 0x7a, 0x15, 0x06, 0x74, 0xf1, 0x2e, 0x89, 0x1e, 0xf4, 0x90, + 0x22, 0x8e, 0xa7, 0x22, 0x5b, 0x13, 0xc6, 0xba, 0xce, 0xf6, 0xa1, 0x36, 0xf0, 0x7b, 0x45, 0xb5, 0x1f, 0xe0, 0xdd, + 0x06, 0x95, 0x95, 0x77, 0x87, 0xdf, 0x36, 0xb7, 0x24, 0xee, 0x25, 0x87, 0xe2, 0xba, 0xfa, 0x2a, 0x22, 0x03, 0x1e, + 0x94, 0x6a, 0x4d, 0x17, 0xc5, 0xf1, 0x53, 0x0a, 0xc6, 0x32, 0x45, 0x75, 0x32, 0xd3, 0xf6, 0x62, 0x84, 0x68, 0x74, + 0xec, 0x68, 0x73, 0x67, 0xe6, 0xcf, 0x27, 0x44, 0xe4, 0x04, 0xdb, 0xff, 0x54, 0x5e, 0x9c, 0x8d, 0x48, 0x18, 0xec, + 0xdf, 0x36, 0x43, 0x6a, 0x9f, 0xaa, 0x49, 0x9d, 0x86, 0x48, 0xd9, 0x0f, 0x6b, 0x5a, 0xff, 0xbf, 0xf8, 0x5c, 0x18, + 0x0d, 0x44, 0xef, 0xd4, 0x5b, 0x57, 0x4a, 0x60, 0xbb, 0xda, 0xa5, 0xf2, 0x52, 0xfa, 0xbc, 0x6f, 0x75, 0x13, 0x93, + 0xb2, 0xe0, 0x4d, 0xed, 0xcf, 0xd1, 0xfa, 0x49, 0xab, 0x0d, 0xb9, 0x77, 0xfa, 0xd4, 0xe3, 0x10, 0x23, 0x29, 0x27, + 0x88, 0xb1, 0xd4, 0x86, 0x10, 0x81, 0x47, 0x59, 0x83, 0xbb, 0xfe, 0xc9, 0x44, 0x6d, 0x93, 0x06, 0x68, 0x94, 0xe7, + 0x6d, 0xb1, 0xf5, 0xea, 0x4e, 0xe9, 0x8a, 0xdb, 0xe1, 0xca, 0xd6, 0x25, 0x60, 0x3a, 0x31, 0xf8, 0x74, 0x47, 0x77, + 0x0a, 0x78, 0x13, 0xec, 0x26, 0x13, 0xd0, 0x10, 0xc3, 0xd9, 0x7c, 0xf1, 0xcf, 0x96, 0x63, 0x7e, 0xe9, 0x07, 0x6c, + 0x0d, 0x92, 0x06, 0x70, 0x66, 0x00, 0x5b, 0x9f, 0x37, 0x57, 0xaa, 0x6f, 0x0f, 0xff, 0xda, 0x32, 0x7e, 0x47, 0x6e, + 0xf8, 0x7a, 0xdb, 0xd5, 0xc1, 0xf2, 0xc2, 0xb0, 0x43, 0xa0, 0x33, 0xa8, 0x4b, 0x0a, 0x93, 0xde, 0x05, 0xd4, 0xb9, + 0xd7, 0xb8, 0x81, 0x2b, 0x23, 0x84, 0x61, 0xc8, 0x83, 0xca, 0xb9, 0xbd, 0xc2, 0xbd, 0xf5, 0x3d, 0x81, 0x16, 0x20, + 0x3c, 0x54, 0xa1, 0x6d, 0x91, 0x49, 0xe7, 0xde, 0x60, 0xbb, 0x84, 0x75, 0x29, 0xe5, 0x54, 0x6b, 0x4e, 0xd7, 0x21, + 0xf9, 0xb8, 0xad, 0xfa, 0x16, 0x03, 0x72, 0x04, 0xa1, 0xd4, 0x33, 0x64, 0xd7, 0x70, 0x91, 0x5e, 0x6e, 0x8e, 0x74, + 0xca, 0xd7, 0x02, 0x62, 0x9b, 0xba, 0xdb, 0xa2, 0xfb, 0x7c, 0x2b, 0x0f, 0x41, 0x66, 0x9a, 0x4b, 0x40, 0xa2, 0x31, + 0x05, 0xf5, 0xc3, 0x30, 0x29, 0x97, 0xff, 0x5e, 0x23, 0x5e, 0xe7, 0xf1, 0xfa, 0xe7, 0x27, 0xab, 0xea, 0xc3, 0xf6, + 0x07, 0x3f, 0xd0, 0x7b, 0xb1, 0x69, 0xad, 0xde, 0x5e, 0xac, 0xbe, 0x9b, 0x15, 0xd4, 0xcf, 0x2c, 0x99, 0x41, 0x18, + 0x4b, 0x9d, 0x9d, 0xf1, 0x41, 0x5c, 0xf3, 0x1b, 0xb6, 0x5c, 0xde, 0x23, 0xf5, 0x2e, 0x93, 0x34, 0x99, 0xa6, 0xac, + 0x3e, 0xad, 0x4f, 0x3b, 0x45, 0xa0, 0x8d, 0x3a, 0x7a, 0x0d, 0xa7, 0x1c, 0xb8, 0x68, 0xd3, 0xa2, 0xbb, 0xcb, 0x3f, + 0x0b, 0x96, 0x85, 0x6e, 0x7f, 0x5d, 0x0e, 0xd6, 0x0d, 0x9f, 0x57, 0x74, 0x2e, 0x9c, 0xc8, 0xa1, 0x85, 0x05, 0x56, + 0x3b, 0x65, 0x70, 0xa6, 0xde, 0x64, 0x8b, 0x13, 0xdd, 0x44, 0x07, 0x79, 0x65, 0xac, 0xe3, 0xd4, 0x4b, 0xc3, 0x01, + 0xba, 0x66, 0x85, 0xcf, 0xf9, 0xa8, 0xcf, 0xfb, 0x13, 0x3a, 0x53, 0x90, 0xb5, 0xdc, 0x59, 0x14, 0xcb, 0xbb, 0x90, + 0x57, 0x51, 0x7d, 0xb9, 0x18, 0x59, 0x21, 0x94, 0x05, 0x6c, 0x7b, 0x7d, 0x1c, 0x87, 0x22, 0xf7, 0xa4, 0xc4, 0xd3, + 0xce, 0x39, 0x52, 0xee, 0x12, 0xe4, 0xee, 0x8a, 0xc5, 0x69, 0x24, 0xf5, 0xba, 0x8d, 0xe0, 0x52, 0x62, 0x86, 0xae, + 0x28, 0x72, 0x8b, 0x21, 0x04, 0x1c, 0x0d, 0x6f, 0x6f, 0xb4, 0x6d, 0xa4, 0x0c, 0x12, 0xc5, 0xce, 0x08, 0xe9, 0x97, + 0xb9, 0xa1, 0x7c, 0xb3, 0x0f, 0xa6, 0x8c, 0x41, 0x04, 0x04, 0x2a, 0xc8, 0x00, 0x2c, 0xfb, 0xca, 0xb9, 0x1a, 0x06, + 0xe3, 0x0c, 0x46, 0x02, 0x2b, 0xa0, 0xd7, 0x45, 0x93, 0x6e, 0xf8, 0x53, 0x78, 0x9a, 0x2a, 0x9e, 0xa6, 0x85, 0xa2, + 0xd1, 0x51, 0x79, 0x36, 0xa5, 0x6b, 0x9e, 0x06, 0x0b, 0x52, 0x4f, 0x9a, 0x1c, 0x36, 0x06, 0xf3, 0x68, 0x24, 0xe1, + 0x9e, 0x9a, 0x8c, 0x62, 0x65, 0x68, 0x1c, 0xfd, 0xb3, 0x3b, 0xc4, 0x39, 0x74, 0x50, 0x0b, 0xde, 0xcc, 0xe8, 0xe1, + 0xcc, 0x0f, 0x41, 0x6a, 0xd8, 0x5c, 0x85, 0xf9, 0x45, 0xa6, 0x4e, 0x75, 0xca, 0x28, 0x4b, 0x8c, 0xb3, 0x60, 0xed, + 0x18, 0x72, 0xa4, 0x7e, 0xc0, 0x76, 0x03, 0x79, 0x5a, 0x73, 0xb6, 0xf4, 0x9a, 0x49, 0xf7, 0xba, 0x76, 0xf4, 0x69, + 0xde, 0xd2, 0xf5, 0x5f, 0xcc, 0x6c, 0xdd, 0x8e, 0x39, 0x7f, 0xe9, 0xe7, 0xbb, 0xe9, 0x43, 0x1f, 0xf3, 0x66, 0xec, + 0x0c, 0x33, 0xba, 0xfe, 0x22, 0x2d, 0x1e, 0x14, 0x0d, 0x8a, 0x7c, 0xa9, 0x31, 0x8e, 0xb4, 0xbf, 0x1f, 0x9a, 0x46, + 0xbb, 0xdb, 0x3b, 0x49, 0x1a, 0x61, 0xcb, 0x11, 0x7a, 0x23, 0x38, 0x76, 0xc5, 0x7f, 0x5c, 0x55, 0xfe, 0x77, 0x4f, + 0xfd, 0xbe, 0x3d, 0x08, 0x5f, 0xd5, 0x4d, 0x1f, 0x46, 0x01, 0x73, 0xd6, 0xba, 0x5d, 0x7d, 0x96, 0x50, 0x43, 0xfa, + 0x6b, 0x82, 0xfa, 0x8d, 0x63, 0xf5, 0x8f, 0x69, 0x4a, 0xfe, 0x62, 0x57, 0xc1, 0xc6, 0x6c, 0xfd, 0x58, 0xd8, 0xa3, + 0x5a, 0x39, 0x3e, 0x77, 0xa7, 0x2d, 0x19, 0xed, 0x49, 0xf9, 0x56, 0x77, 0x78, 0xda, 0x49, 0x59, 0xca, 0xe6, 0x3d, + 0xb5, 0x98, 0x4c, 0x57, 0xdb, 0x4a, 0x1c, 0x91, 0x1e, 0xe4, 0x1b, 0x73, 0x46, 0xe9, 0xf8, 0x7d, 0xe5, 0x8f, 0xbb, + 0x93, 0xd8, 0xcc, 0xd3, 0x93, 0x70, 0x0b, 0xb4, 0x2b, 0xfb, 0x7e, 0x2b, 0xee, 0x3b, 0x29, 0xfe, 0x76, 0xd9, 0xaf, + 0x73, 0x95, 0x02, 0x1e, 0xf7, 0xbe, 0x60, 0xfb, 0xf9, 0x3a, 0x52, 0xd8, 0x8e, 0x14, 0x43, 0xb6, 0xf9, 0xaa, 0x4b, + 0xa2, 0x0a, 0x59, 0xf0, 0x06, 0xf9, 0x20, 0xae, 0x05, 0x20, 0xe7, 0xb4, 0x45, 0x2d, 0xfb, 0x8e, 0x25, 0x51, 0xbe, + 0xab, 0x40, 0x2d, 0x79, 0x76, 0x51, 0xd1, 0xa9, 0x3b, 0xe1, 0xab, 0x53, 0xcf, 0xd2, 0x9c, 0x86, 0xe8, 0x7a, 0x58, + 0xf2, 0x12, 0x95, 0xac, 0x9a, 0xde, 0x4d, 0xf0, 0x2b, 0xf6, 0xda, 0x13, 0x94, 0xbc, 0x23, 0xa5, 0xa1, 0x90, 0x51, + 0xac, 0x41, 0x7d, 0xeb, 0xdc, 0x25, 0x96, 0x74, 0xb4, 0x3c, 0xea, 0x55, 0xf8, 0x62, 0xee, 0xe3, 0xd6, 0x38, 0x2a, + 0xc7, 0x9c, 0x23, 0xd8, 0x93, 0x2a, 0x9d, 0x4c, 0x95, 0x03, 0xc0, 0x9a, 0x66, 0x11, 0x31, 0x48, 0xa9, 0x5d, 0x8e, + 0xbb, 0xf8, 0x16, 0x6c, 0x67, 0x31, 0xc8, 0xd2, 0xd7, 0x36, 0x19, 0x95, 0xce, 0x63, 0x27, 0xba, 0x1f, 0xbb, 0xda, + 0xc0, 0xc3, 0x60, 0x7b, 0xd6, 0x29, 0x57, 0x32, 0x56, 0x1c, 0x65, 0x57, 0x62, 0x6a, 0x79, 0xb6, 0x74, 0xbd, 0x45, + 0x54, 0xac, 0x95, 0x35, 0xbd, 0x0d, 0xd3, 0x53, 0xc1, 0x73, 0x3f, 0x3e, 0x61, 0x71, 0x64, 0xe4, 0x38, 0x93, 0x58, + 0xd5, 0x21, 0xcc, 0x4d, 0x3a, 0x82, 0x27, 0x48, 0x2d, 0x47, 0x32, 0x4f, 0x39, 0xa5, 0x50, 0xa1, 0xff, 0xa5, 0xf1, + 0x08, 0x55, 0x73, 0x75, 0xd3, 0x5b, 0x85, 0xef, 0x10, 0x84, 0xfe, 0x21, 0xba, 0x05, 0xe3, 0x82, 0xf7, 0xef, 0x25, + 0x22, 0xd5, 0x52, 0x28, 0x59, 0x5e, 0x5d, 0xd6, 0x98, 0xa1, 0xcd, 0x3b, 0x4a, 0xc9, 0x50, 0xa0, 0x0a, 0xd3, 0x17, + 0x7c, 0x6e, 0x10, 0x0e, 0xb9, 0x6b, 0x1d, 0x05, 0xb1, 0x22, 0xd8, 0x0d, 0x9d, 0x20, 0xa1, 0xae, 0x0a, 0xb1, 0x2f, + 0xc4, 0x1c, 0x9f, 0xcb, 0xac, 0xd0, 0x03, 0xfe, 0xe4, 0x37, 0xb1, 0xa4, 0xfe, 0x06, 0x46, 0xfe, 0x0d, 0xab, 0xb4, + 0xa1, 0x4f, 0xf9, 0x41, 0xec, 0x73, 0x21, 0x6f, 0x6a, 0xa6, 0xd9, 0x8e, 0xcb, 0x7e, 0xe6, 0xef, 0x4d, 0x78, 0xec, + 0x2d, 0xb1, 0xf3, 0x58, 0x50, 0xb9, 0x8c, 0x21, 0x06, 0xaa, 0x9b, 0xfc, 0x89, 0xd2, 0x3f, 0x9c, 0x4e, 0xfc, 0x66, + 0xbe, 0xb3, 0xb6, 0x3f, 0x5f, 0x85, 0x42, 0xec, 0x75, 0x86, 0x96, 0x30, 0x84, 0xf0, 0x64, 0x3e, 0xbb, 0x30, 0x27, + 0x21, 0x49, 0x45, 0x8b, 0x12, 0xce, 0x70, 0x7f, 0x03, 0x20, 0x03, 0x0d, 0x56, 0xa2, 0x54, 0xd4, 0x8b, 0x3d, 0x82, + 0x49, 0x3e, 0xdb, 0x7a, 0xd8, 0x8b, 0x3c, 0x5a, 0x48, 0xa3, 0x5c, 0xc1, 0x06, 0x30, 0xd5, 0x73, 0x1b, 0x89, 0xc5, + 0x48, 0x2f, 0x5a, 0x4b, 0xbe, 0xd4, 0x12, 0xea, 0x62, 0xe7, 0x61, 0xb0, 0xae, 0x1a, 0x54, 0x76, 0x1e, 0x0b, 0x66, + 0x3a, 0x07, 0x72, 0x5c, 0xa0, 0x51, 0x37, 0x26, 0xc7, 0x14, 0xb3, 0x6a, 0x85, 0x1f, 0xaa, 0x98, 0xb7, 0x74, 0x29, + 0xd8, 0xbc, 0x56, 0x77, 0xc7, 0xbb, 0x86, 0x09, 0x75, 0x68, 0x60, 0x96, 0x24, 0xa8, 0x61, 0x09, 0xeb, 0x0b, 0x3e, + 0x8f, 0xc1, 0x3c, 0x60, 0x9a, 0xb7, 0xd9, 0xed, 0x18, 0xf5, 0x5b, 0x35, 0x9f, 0x7a, 0xab, 0x3c, 0x6f, 0xb9, 0xc3, + 0x2b, 0x35, 0x37, 0xa7, 0xc5, 0x3c, 0x42, 0x44, 0xaf, 0xdb, 0x90, 0xf0, 0x9c, 0xb9, 0x97, 0xb8, 0x90, 0x78, 0xd2, + 0x67, 0x5e, 0x1f, 0x1f, 0x77, 0x22, 0xc7, 0xa8, 0xf4, 0xd6, 0x45, 0xc8, 0xec, 0x83, 0xb2, 0x72, 0x77, 0x78, 0x72, + 0xe9, 0xb4, 0x84, 0x4a, 0xd6, 0xf5, 0x9b, 0x4f, 0x13, 0xa8, 0xc2, 0x60, 0x86, 0xe2, 0x14, 0x9b, 0xea, 0xd1, 0x78, + 0x83, 0x79, 0x04, 0xe3, 0x22, 0x12, 0x6a, 0x30, 0x0f, 0x2e, 0xd1, 0x34, 0x12, 0x31, 0x67, 0xac, 0x6e, 0x07, 0xb0, + 0xe7, 0x4b, 0x4f, 0x31, 0x7b, 0x78, 0x6d, 0x2f, 0x99, 0xe3, 0xf6, 0x25, 0x70, 0x5b, 0xee, 0x4e, 0xb1, 0x9a, 0x3d, + 0x56, 0x49, 0x8d, 0x03, 0xd8, 0x48, 0xa3, 0x2b, 0x3b, 0xd3, 0xa5, 0x1c, 0x76, 0x7f, 0x82, 0x27, 0x80, 0x63, 0x04, + 0x83, 0x13, 0x70, 0x1b, 0xf9, 0x8d, 0x5b, 0xb1, 0xf2, 0xcd, 0x27, 0x81, 0x0d, 0x41, 0x64, 0x0a, 0x2b, 0x44, 0x4c, + 0x95, 0x21, 0xfc, 0xbc, 0x8b, 0xb3, 0xaf, 0x2e, 0x13, 0x4d, 0xb5, 0xd1, 0x08, 0xc5, 0x3c, 0xbc, 0x86, 0x9b, 0x79, + 0x60, 0xaa, 0xc7, 0x9a, 0x4e, 0x11, 0xd5, 0x4e, 0x62, 0x6a, 0xf5, 0xac, 0x63, 0xad, 0x5c, 0x6d, 0x8b, 0xc9, 0x27, + 0xea, 0x95, 0x3c, 0x40, 0x23, 0x9a, 0xc9, 0x84, 0x6f, 0x39, 0x33, 0x1f, 0xa6, 0xec, 0x11, 0x7e, 0x1d, 0xc3, 0x87, + 0xc7, 0x8d, 0xc0, 0x28, 0xcf, 0xc9, 0xce, 0x16, 0x32, 0x2e, 0x1c, 0x58, 0xfc, 0x69, 0xc8, 0x98, 0xfb, 0xa2, 0xe8, + 0xa9, 0x4c, 0x2f, 0x26, 0x2f, 0x7f, 0x56, 0xf3, 0x64, 0x1e, 0x08, 0x5b, 0xa3, 0x8a, 0xf4, 0x4b, 0xa3, 0xc5, 0x62, + 0x90, 0x7a, 0x18, 0xc6, 0x40, 0x8a, 0x85, 0x8a, 0x81, 0xa3, 0x4b, 0x75, 0xc0, 0xff, 0x4f, 0x41, 0x8f, 0x21, 0x7b, + 0x46, 0xdb, 0x6d, 0xce, 0xb7, 0xac, 0x27, 0x73, 0x63, 0xdf, 0xd9, 0x76, 0xf2, 0xc6, 0x03, 0x4e, 0x16, 0xfb, 0x85, + 0x59, 0xfc, 0x2e, 0x9f, 0x07, 0x4a, 0xec, 0x25, 0x41, 0x32, 0x0a, 0x90, 0x39, 0x9f, 0x85, 0x87, 0xd0, 0x6b, 0x5e, + 0x09, 0x51, 0xd0, 0x95, 0xe2, 0x4a, 0xa0, 0xf5, 0xe0, 0x34, 0x40, 0xa6, 0xc2, 0x15, 0x04, 0x32, 0x6f, 0x7e, 0x5c, + 0xee, 0x6e, 0x02, 0x69, 0x7b, 0x85, 0x0c, 0x66, 0x6e, 0xf5, 0x12, 0xa9, 0xf3, 0x81, 0x59, 0xbe, 0x64, 0x6f, 0x6c, + 0x59, 0xf7, 0xe0, 0x4c, 0x3f, 0x73, 0x74, 0x1a, 0xb2, 0xb2, 0x16, 0x0a, 0x9b, 0xec, 0xf4, 0x24, 0xaa, 0x2a, 0x45, + 0xf2, 0x8a, 0x72, 0x51, 0x50, 0x54, 0x08, 0xd3, 0xeb, 0x1f, 0x02, 0x42, 0x8e, 0x52, 0x06, 0xed, 0xb1, 0xe5, 0x2b, + 0x8c, 0x08, 0x81, 0x71, 0x21, 0x1a, 0x56, 0x90, 0x29, 0xec, 0xb2, 0x8a, 0x71, 0x9c, 0x9e, 0xa8, 0xba, 0x8b, 0x53, + 0x88, 0x3d, 0xef, 0x86, 0xcc, 0x12, 0x74, 0x6e, 0xb4, 0x35, 0xc6, 0x31, 0xf4, 0x33, 0xd1, 0x3f, 0x82, 0x1b, 0x9d, + 0xc3, 0x1a, 0x15, 0x9c, 0x1a, 0xfb, 0x9c, 0xc5, 0x3b, 0xbe, 0x0a, 0xf7, 0x7b, 0xda, 0x92, 0xde, 0x8e, 0x5d, 0x33, + 0x2b, 0x3f, 0x82, 0x2c, 0xa5, 0x2d, 0x43, 0x12, 0xd5, 0xb5, 0x97, 0x8e, 0x41, 0x53, 0x22, 0xb7, 0x9e, 0xcf, 0xc9, + 0xe8, 0x1b, 0x4c, 0x7e, 0xbc, 0x39, 0xdd, 0x97, 0x84, 0x0e, 0x56, 0x8b, 0x67, 0x2e, 0xbe, 0xc4, 0x26, 0xd0, 0x82, + 0x07, 0x00, 0xce, 0xdf, 0x89, 0xeb, 0xdf, 0x45, 0x0f, 0x0e, 0x63, 0x04, 0xb0, 0x10, 0x6f, 0xc5, 0xb0, 0xf2, 0x36, + 0xa2, 0xb4, 0x02, 0x20, 0xd7, 0xa6, 0x2a, 0xc0, 0x1b, 0x86, 0x6f, 0xb2, 0x64, 0x9f, 0xe3, 0x38, 0xdb, 0xb3, 0x42, + 0xe2, 0x1d, 0xfe, 0x59, 0x1f, 0x5e, 0x99, 0x70, 0x2e, 0xd8, 0x2d, 0x1d, 0xe7, 0x55, 0xdb, 0x88, 0xa4, 0xed, 0x62, + 0x10, 0xe3, 0x0c, 0x12, 0xa9, 0x0f, 0xd2, 0xf7, 0x57, 0x61, 0x21, 0x1a, 0x02, 0xef, 0x8b, 0x0a, 0xec, 0x75, 0x14, + 0x46, 0x93, 0xda, 0x23, 0x1a, 0x41, 0x4f, 0x57, 0x27, 0x24, 0x03, 0x95, 0x42, 0x6c, 0x98, 0x44, 0xfd, 0x4c, 0xb5, + 0xdd, 0x50, 0x7d, 0x53, 0xad, 0xa6, 0x50, 0xe3, 0x13, 0x30, 0x75, 0x28, 0x29, 0x18, 0x73, 0x6d, 0xd8, 0xa7, 0x8f, + 0x1d, 0x51, 0x6b, 0x0d, 0x4e, 0xef, 0x5f, 0x85, 0x81, 0x61, 0xb9, 0xd9, 0x7c, 0xf1, 0x15, 0x76, 0x9d, 0x73, 0x2f, + 0x50, 0x7d, 0x1f, 0xfd, 0x38, 0xae, 0x2e, 0xcb, 0xef, 0x7a, 0x37, 0xb5, 0x10, 0xef, 0xa9, 0xa3, 0x86, 0xfd, 0x94, + 0xc8, 0xf9, 0x2e, 0xc5, 0x7d, 0xdc, 0x51, 0x01, 0xf4, 0x79, 0xc8, 0x48, 0xab, 0x09, 0xc5, 0x05, 0x20, 0x5d, 0xc6, + 0x34, 0x2b, 0x0d, 0x54, 0xa8, 0xd9, 0x68, 0x2f, 0x23, 0xab, 0x0c, 0xc2, 0x41, 0xfb, 0xd5, 0x23, 0x28, 0x96, 0x55, + 0xba, 0xc9, 0x23, 0x80, 0xcd, 0xfa, 0x95, 0xdc, 0xfc, 0x17, 0x01, 0x1b, 0x0c, 0x0b, 0x36, 0x2f, 0xea, 0x25, 0xcf, + 0x79, 0x04, 0x98, 0xe4, 0x8c, 0xeb, 0x41, 0xb4, 0xfe, 0x6b, 0xd3, 0x7c, 0xb0, 0xe5, 0xde, 0x3b, 0x02, 0x49, 0xbd, + 0xac, 0x0d, 0xb0, 0xb2, 0x18, 0x52, 0xef, 0x39, 0x3f, 0x35, 0x32, 0x25, 0x20, 0x20, 0xfe, 0xc2, 0xd7, 0xf5, 0xeb, + 0xb0, 0x5e, 0xab, 0x74, 0xe7, 0x53, 0xd3, 0x5a, 0x15, 0x52, 0x9e, 0xcc, 0x5a, 0x45, 0x3e, 0x71, 0x55, 0x33, 0xc3, + 0xb4, 0x36, 0xaf, 0x4e, 0x4f, 0xfa, 0x0b, 0xca, 0x7d, 0x91, 0x33, 0x00, 0xe1, 0x35, 0x8f, 0x0f, 0x56, 0xef, 0x66, + 0xdf, 0x36, 0xae, 0x56, 0xb6, 0xfb, 0x17, 0x6d, 0x7c, 0x9a, 0xb1, 0x5b, 0xc3, 0x18, 0x54, 0xce, 0xcb, 0xc9, 0x6f, + 0x4a, 0x4a, 0x23, 0x2d, 0xa3, 0xcd, 0xb1, 0xcb, 0xa9, 0xf6, 0xe5, 0xea, 0xdd, 0x1a, 0x84, 0x15, 0x22, 0xb3, 0x07, + 0xcf, 0x08, 0x1f, 0x6f, 0xfd, 0x50, 0x08, 0x55, 0x69, 0xc7, 0x58, 0xde, 0x2c, 0x86, 0xa1, 0xf9, 0x5c, 0x8a, 0xcb, + 0x11, 0xe6, 0x5b, 0xe7, 0xa6, 0x29, 0x64, 0x6d, 0x22, 0xa9, 0xa3, 0xc0, 0xa4, 0xb8, 0x8c, 0x34, 0xe7, 0x5f, 0xc6, + 0x89, 0xe4, 0x5e, 0x29, 0x9f, 0xd4, 0x53, 0xea, 0x2d, 0xd8, 0x08, 0xc2, 0x9f, 0xd4, 0x2d, 0x7b, 0xa1, 0x41, 0xb3, + 0x4d, 0x92, 0xc1, 0xc9, 0xe7, 0x07, 0xbf, 0xc9, 0x5d, 0x7a, 0x0d, 0x91, 0x10, 0x4c, 0xf9, 0xdc, 0x36, 0xdd, 0x64, + 0xac, 0x04, 0xa4, 0xab, 0x1a, 0x6d, 0xd8, 0x4c, 0xd1, 0xf5, 0x79, 0x3f, 0xc8, 0xfb, 0xa1, 0x23, 0xb2, 0x18, 0x5b, + 0x94, 0xf0, 0x0b, 0x47, 0x62, 0x4e, 0xdd, 0x14, 0xa5, 0x35, 0xf4, 0xf6, 0x61, 0x76, 0xbd, 0xdb, 0x6b, 0xb9, 0x24, + 0x1d, 0x4e, 0x2b, 0xd2, 0x2c, 0x00, 0x21, 0xea, 0x14, 0xaa, 0x09, 0x38, 0x50, 0xe6, 0xe5, 0x2f, 0x91, 0x40, 0xca, + 0x0f, 0x70, 0x21, 0xbd, 0xcc, 0xe7, 0x28, 0x82, 0xf3, 0x50, 0xa5, 0x05, 0x17, 0x66, 0x8f, 0x81, 0xdf, 0x75, 0x4d, + 0xbf, 0x05, 0x7f, 0x66, 0x8a, 0x97, 0xc2, 0xc9, 0xf3, 0x72, 0xaf, 0xe1, 0x21, 0x06, 0x1f, 0x9c, 0x55, 0x5f, 0xf4, + 0x72, 0x1a, 0xd7, 0x19, 0xd8, 0xbd, 0xec, 0x81, 0xf9, 0x30, 0xf3, 0x56, 0x00, 0x99, 0xd3, 0xb8, 0xaa, 0xc0, 0x73, + 0x5e, 0xcd, 0xb5, 0x46, 0x39, 0xbd, 0x5f, 0x88, 0x31, 0x0d, 0xa5, 0x62, 0x6b, 0x98, 0x8a, 0x03, 0xc5, 0x45, 0xc9, + 0xbd, 0xac, 0x29, 0x4e, 0x9b, 0xf3, 0x86, 0xa4, 0x7c, 0x47, 0x03, 0x6d, 0x5e, 0xcd, 0x93, 0x7b, 0xfc, 0x8b, 0x7a, + 0xde, 0x7b, 0x1c, 0xbe, 0xbc, 0x99, 0x16, 0x83, 0x9c, 0x0f, 0x90, 0x53, 0x03, 0xc7, 0x6f, 0x4d, 0xf8, 0x63, 0x6e, + 0x69, 0x35, 0xc6, 0x1a, 0x9a, 0xf8, 0xc8, 0x56, 0x00, 0x29, 0xe3, 0x4d, 0x52, 0x2b, 0x7c, 0xa9, 0x43, 0xb1, 0x98, + 0x2c, 0xbf, 0x0b, 0x34, 0xb8, 0xc1, 0xd2, 0x33, 0x1c, 0x52, 0xd4, 0x8b, 0xa2, 0xa5, 0x3f, 0x51, 0x7e, 0x37, 0xee, + 0x3f, 0xed, 0x77, 0x4b, 0x60, 0xee, 0x71, 0x5b, 0x69, 0xb1, 0x91, 0xd0, 0x4d, 0x5b, 0xc9, 0xab, 0x0d, 0x55, 0x77, + 0xe7, 0xae, 0x89, 0xac, 0xe6, 0xba, 0x46, 0xa5, 0x06, 0x61, 0xfc, 0x5e, 0xbb, 0xb1, 0xcd, 0xb4, 0xb5, 0xd2, 0x41, + 0x9f, 0x21, 0xe9, 0xa5, 0xa2, 0xdd, 0xa0, 0x63, 0x4f, 0x4f, 0x68, 0xc7, 0x12, 0xf1, 0x1e, 0x21, 0x71, 0x86, 0x85, + 0x62, 0x5c, 0x98, 0x47, 0xf4, 0x56, 0x7a, 0xc5, 0x61, 0x63, 0xd2, 0xd3, 0x6c, 0x2c, 0xcb, 0x2b, 0x9c, 0xba, 0x2f, + 0x52, 0x40, 0xf1, 0xcf, 0xd1, 0x01, 0xfc, 0x33, 0x5d, 0x83, 0xd6, 0xe0, 0x54, 0x2e, 0x77, 0xf5, 0xba, 0xc4, 0x87, + 0x76, 0xc7, 0x13, 0x09, 0xdd, 0x0e, 0xd1, 0xf8, 0x86, 0xae, 0x70, 0xa3, 0x78, 0x95, 0x51, 0xc7, 0xca, 0xb4, 0x66, + 0xe4, 0xc3, 0x8a, 0xec, 0x5f, 0x20, 0xf2, 0xaa, 0x10, 0x85, 0x20, 0xad, 0x45, 0x34, 0x31, 0xd9, 0x7f, 0x9a, 0xe4, + 0x35, 0xa5, 0x99, 0x6d, 0xf4, 0xa7, 0x4d, 0x4d, 0xdb, 0x11, 0x70, 0xb7, 0x73, 0x13, 0x5d, 0xec, 0xcc, 0xa5, 0xb0, + 0x57, 0x50, 0xda, 0x42, 0xa2, 0x90, 0x75, 0x21, 0x5a, 0x6e, 0x97, 0x5a, 0xf9, 0xad, 0xf2, 0x14, 0x00, 0x10, 0x05, + 0x4d, 0xe8, 0xca, 0x6e, 0xb6, 0x77, 0xc5, 0xd2, 0xd5, 0x43, 0x04, 0x1f, 0x90, 0x12, 0x1c, 0xa0, 0x8b, 0x3c, 0xae, + 0xbb, 0x77, 0xb3, 0x8d, 0xc8, 0x46, 0xb6, 0x68, 0xad, 0x0e, 0xbc, 0x5c, 0xbf, 0xde, 0xe5, 0xff, 0xdf, 0xf7, 0xd8, + 0xfc, 0x05, 0x1c, 0x50, 0xb8, 0x0f, 0x1f, 0x4c, 0x0b, 0x4f, 0x1c, 0x6a, 0xfd, 0xd7, 0x86, 0x12, 0x05, 0x4f, 0xa2, + 0xf1, 0x73, 0xcd, 0x3a, 0x3d, 0x62, 0x14, 0x89, 0x8f, 0xd4, 0x03, 0x89, 0x5a, 0xad, 0x35, 0x3d, 0xae, 0xae, 0xd3, + 0xfa, 0x2f, 0x86, 0x7d, 0xb5, 0xab, 0x18, 0xe2, 0x4e, 0x2f, 0x6e, 0x23, 0x89, 0x42, 0xa1, 0xcf, 0x26, 0x3e, 0x61, + 0xa0, 0xb2, 0xe6, 0x0b, 0x0f, 0x29, 0x96, 0x97, 0xcb, 0xd0, 0x74, 0xa1, 0xc5, 0x6d, 0x81, 0x1a, 0x0f, 0x06, 0x3d, + 0x6b, 0x97, 0x20, 0xcd, 0xae, 0xff, 0x8c, 0x33, 0x9c, 0xb4, 0xd2, 0xea, 0xf3, 0x62, 0x08, 0xd9, 0xc7, 0x3b, 0xa9, + 0x55, 0xa1, 0x8c, 0xb3, 0xc7, 0xaa, 0xac, 0x0d, 0xbf, 0x86, 0x6a, 0x74, 0x8e, 0x55, 0xd4, 0xa6, 0xae, 0xad, 0x76, + 0xc6, 0xa0, 0x54, 0xc7, 0x81, 0x60, 0xd3, 0xd2, 0xc5, 0x20, 0x2d, 0xe2, 0x40, 0x1f, 0x48, 0xf2, 0xb8, 0xa4, 0xf9, + 0x2e, 0x15, 0xf9, 0x72, 0xd1, 0x10, 0x9a, 0xeb, 0xaa, 0xc2, 0x36, 0xe5, 0xb1, 0xa6, 0xa8, 0x8b, 0x9b, 0x1d, 0xeb, + 0xf0, 0x1a, 0x30, 0x8c, 0x17, 0xe0, 0x4a, 0x80, 0x93, 0xf2, 0xd5, 0xa7, 0x06, 0x3b, 0x66, 0x0a, 0x3d, 0x17, 0xfe, + 0xf8, 0xb4, 0x26, 0x13, 0x2b, 0x5e, 0xb5, 0x42, 0x89, 0xab, 0xc4, 0x53, 0x1d, 0x96, 0x4b, 0x37, 0xb1, 0x46, 0xc8, + 0x67, 0xe2, 0xaf, 0xd1, 0x22, 0xec, 0x0d, 0x64, 0x0d, 0xcb, 0x64, 0x32, 0x25, 0x24, 0x4c, 0x90, 0x73, 0xc0, 0x98, + 0x3a, 0xc9, 0x17, 0x97, 0xfe, 0x2c, 0xdc, 0xa1, 0xcf, 0xa6, 0xb5, 0x74, 0xdf, 0x49, 0x85, 0xee, 0x7b, 0x32, 0xe9, + 0x50, 0x4f, 0x1c, 0xc4, 0xcc, 0x14, 0x5c, 0x1d, 0xb7, 0xd8, 0x68, 0x30, 0x38, 0x3a, 0x3b, 0xd6, 0x62, 0x8b, 0x21, + 0x61, 0x03, 0xe6, 0x13, 0xb5, 0x7b, 0x1f, 0x3e, 0x93, 0xf4, 0xcb, 0xd7, 0x4b, 0x84, 0x90, 0xc1, 0xee, 0x44, 0xa9, + 0x19, 0x33, 0xd4, 0x34, 0x33, 0x05, 0xcd, 0xfc, 0xe8, 0x24, 0xd2, 0x1d, 0xde, 0x4c, 0xe8, 0x95, 0x06, 0xb7, 0xee, + 0xa9, 0xbe, 0x5c, 0x91, 0x46, 0x68, 0x8d, 0x39, 0x50, 0xf9, 0x70, 0x5a, 0x17, 0xd7, 0x2b, 0xdb, 0xf5, 0x03, 0x7e, + 0x68, 0xb5, 0x3b, 0x78, 0xd2, 0x20, 0xe3, 0x43, 0x93, 0xe4, 0xbc, 0x06, 0x2b, 0xc0, 0x43, 0x83, 0xa5, 0x68, 0x89, + 0x37, 0x45, 0xcf, 0x26, 0xfb, 0x68, 0xb4, 0x18, 0x8b, 0xb5, 0xfc, 0xfd, 0x9d, 0xa7, 0x7d, 0x21, 0xd5, 0x28, 0x7b, + 0x18, 0xc8, 0xee, 0x13, 0x28, 0x99, 0xa3, 0xd0, 0x9d, 0xcd, 0x50, 0xc5, 0x68, 0x9e, 0x00, 0x63, 0x05, 0xbf, 0xb8, + 0x66, 0x31, 0x9d, 0x33, 0xc7, 0xf9, 0x1a, 0x4e, 0x12, 0x47, 0x4d, 0x9c, 0x5b, 0x4f, 0x04, 0xe5, 0xd5, 0x62, 0x49, + 0xf4, 0x92, 0xa2, 0xa3, 0x8e, 0xd5, 0xf8, 0x8f, 0x89, 0xf5, 0xd4, 0x9c, 0x8e, 0x76, 0x3e, 0x8d, 0x15, 0x54, 0xd2, + 0x02, 0x6a, 0x72, 0x2c, 0xfb, 0x12, 0x0a, 0xdc, 0xff, 0xa7, 0x9a, 0x4a, 0xc5, 0xcb, 0x74, 0xb8, 0x59, 0x73, 0x60, + 0x03, 0xea, 0x08, 0xa0, 0xba, 0x35, 0x23, 0xa3, 0x6f, 0x7c, 0x7f, 0xa1, 0xee, 0xc6, 0x98, 0x03, 0x1d, 0xb4, 0x2d, + 0xfa, 0x1b, 0xe8, 0x91, 0x6c, 0x97, 0x83, 0x78, 0x83, 0xf2, 0x16, 0x4f, 0x02, 0x2f, 0x11, 0x4d, 0xd4, 0x6c, 0xac, + 0xe3, 0xb7, 0xd9, 0x3a, 0x0e, 0xde, 0x3a, 0x00, 0xbf, 0xb0, 0x6c, 0xc6, 0x99, 0x8d, 0xfa, 0x5e, 0x41, 0x0c, 0xf8, + 0x51, 0xec, 0x6c, 0xfc, 0xe9, 0x84, 0xda, 0x93, 0x1d, 0x4e, 0x39, 0x36, 0xca, 0x39, 0x61, 0x7b, 0xa6, 0x12, 0x00, + 0x29, 0x74, 0x51, 0x67, 0xc5, 0xc9, 0x4f, 0xbc, 0x15, 0x3b, 0xe2, 0x31, 0x12, 0x57, 0x88, 0x84, 0xe0, 0x82, 0x2a, + 0xb6, 0x6a, 0xb5, 0xea, 0xdc, 0xfc, 0x4c, 0x86, 0x4d, 0xc6, 0x04, 0x8b, 0x05, 0xbd, 0x7f, 0x46, 0x24, 0x84, 0x69, + 0x43, 0x48, 0x96, 0x26, 0x90, 0x1a, 0x8f, 0x5b, 0xf3, 0x24, 0x6d, 0xba, 0x04, 0x7e, 0x5d, 0x5d, 0x50, 0xa8, 0xb4, + 0xa2, 0xe0, 0xe7, 0x35, 0x1c, 0x7b, 0x8d, 0x30, 0xf6, 0x0c, 0x40, 0x1f, 0x49, 0xa0, 0xcc, 0x48, 0x61, 0x31, 0xb5, + 0xa7, 0x38, 0x82, 0x5a, 0x79, 0xd9, 0xd9, 0xae, 0xef, 0xaa, 0x1e, 0x26, 0xf0, 0x57, 0xcc, 0x81, 0x17, 0x50, 0xe6, + 0x48, 0x73, 0xba, 0x5f, 0xfe, 0x01, 0x20, 0xdc, 0x78, 0xfe, 0x8a, 0x08, 0xe9, 0x50, 0xed, 0x03, 0x74, 0x8d, 0xca, + 0x5e, 0xc5, 0x8c, 0x72, 0xb0, 0xaa, 0x1b, 0xb3, 0x4d, 0xe2, 0xeb, 0xf8, 0xba, 0x06, 0xe6, 0xf7, 0xc4, 0xcf, 0x40, + 0xee, 0x61, 0x64, 0xd9, 0x3a, 0x99, 0xd2, 0x5b, 0x6d, 0xb7, 0xc1, 0x95, 0x12, 0x69, 0xc3, 0x64, 0x59, 0x93, 0x1a, + 0xe8, 0x69, 0x05, 0x16, 0xc1, 0xbf, 0xd1, 0x3b, 0xf6, 0x8d, 0x30, 0x9d, 0x63, 0xdb, 0x35, 0x9c, 0x4d, 0x4a, 0x8f, + 0x73, 0x95, 0x4e, 0x4a, 0x48, 0x4d, 0xc5, 0x97, 0xcc, 0xb8, 0x52, 0x1e, 0x13, 0xce, 0x71, 0x35, 0x18, 0x32, 0x0c, + 0xa9, 0xa0, 0xfe, 0x36, 0x59, 0x4a, 0x53, 0x72, 0x96, 0x08, 0x9f, 0x62, 0xf9, 0x33, 0xaa, 0x25, 0xb7, 0x6d, 0xe0, + 0x98, 0xf6, 0x9a, 0xe5, 0x62, 0xc1, 0x8b, 0xf9, 0x13, 0x04, 0x03, 0x1f, 0x5f, 0x51, 0x4b, 0x9e, 0xcb, 0x83, 0x9d, + 0xc4, 0x48, 0xc6, 0xbf, 0x67, 0xda, 0x0d, 0x91, 0xcb, 0x52, 0x85, 0xc8, 0x4c, 0x7f, 0xe6, 0x61, 0xf5, 0x61, 0x39, + 0xd8, 0x4c, 0x11, 0xd3, 0xbf, 0xdf, 0x3f, 0x35, 0x18, 0xc1, 0x0f, 0x3d, 0x39, 0x6d, 0x46, 0x08, 0x7a, 0x16, 0x1d, + 0x55, 0xd8, 0x23, 0x72, 0xa2, 0x00, 0xc9, 0xb2, 0x47, 0xb1, 0xbe, 0xa4, 0x8e, 0x8e, 0xb1, 0x1e, 0x87, 0x13, 0x56, + 0xf6, 0x1c, 0xc9, 0x73, 0x50, 0x57, 0xcd, 0x92, 0xea, 0xd8, 0x63, 0xc0, 0xea, 0x6f, 0x38, 0x95, 0xae, 0xb9, 0xbb, + 0x41, 0x04, 0x5b, 0x22, 0xed, 0x38, 0x72, 0x09, 0xa0, 0x13, 0x4c, 0x61, 0xc8, 0x79, 0x3e, 0x1e, 0xaa, 0x97, 0xbf, + 0x25, 0x3a, 0x2a, 0x71, 0x43, 0x89, 0x74, 0x4b, 0x94, 0x4e, 0x2f, 0x63, 0xcf, 0xee, 0x96, 0x4a, 0xff, 0xc0, 0x03, + 0x4c, 0xd7, 0x29, 0x04, 0x39, 0xe4, 0x24, 0xe1, 0xad, 0x4c, 0x85, 0xb3, 0x7c, 0xd3, 0x1d, 0xdc, 0xb3, 0x88, 0xf1, + 0x10, 0xee, 0xed, 0x0e, 0xb8, 0x0d, 0x2c, 0x46, 0x8d, 0x66, 0x32, 0x70, 0x31, 0xc5, 0x18, 0x8e, 0x38, 0xa4, 0x9c, + 0x23, 0x96, 0x95, 0x3d, 0xee, 0xe6, 0xec, 0x94, 0x41, 0xb4, 0x88, 0x2e, 0x43, 0x95, 0x36, 0x49, 0x8f, 0x1d, 0xb2, + 0x90, 0xf6, 0x29, 0x9e, 0x11, 0xb6, 0x51, 0xd5, 0x69, 0xa2, 0xef, 0x9e, 0x82, 0xe3, 0x19, 0x3a, 0xc3, 0xae, 0x3d, + 0x3f, 0x51, 0xd8, 0x1b, 0x86, 0x50, 0x7e, 0xc9, 0xaf, 0x32, 0x7f, 0x5d, 0x4d, 0x01, 0xac, 0xd8, 0x86, 0xf9, 0x84, + 0x20, 0xce, 0x5c, 0x71, 0x58, 0xe7, 0xdf, 0x6c, 0x66, 0x2b, 0x37, 0xd6, 0xf0, 0x42, 0xa7, 0x14, 0x6e, 0x1b, 0xfd, + 0x1c, 0xe8, 0xec, 0x8a, 0x84, 0x1f, 0x3d, 0xc7, 0x01, 0x64, 0x23, 0x95, 0x64, 0xae, 0xb8, 0xa7, 0xf6, 0xb7, 0x20, + 0xae, 0x1e, 0xc8, 0xc9, 0xa3, 0x17, 0x24, 0xe8, 0x25, 0xf4, 0xe7, 0xf3, 0xe8, 0x5c, 0xa2, 0x61, 0x6f, 0x94, 0x4f, + 0xde, 0x9f, 0xb1, 0x98, 0x5b, 0xa4, 0xc3, 0xb4, 0x94, 0xbe, 0x87, 0xc9, 0x1c, 0x27, 0x14, 0x49, 0xd5, 0x37, 0x8c, + 0x24, 0x78, 0xf1, 0x4c, 0xa2, 0x73, 0x21, 0x37, 0xe7, 0x34, 0x62, 0xb9, 0x67, 0x95, 0xd5, 0x6b, 0x47, 0x51, 0xde, + 0x71, 0x35, 0x4b, 0xba, 0x49, 0xcd, 0x52, 0xc7, 0x56, 0x15, 0x66, 0x34, 0x32, 0xbe, 0x70, 0xab, 0x17, 0x49, 0x39, + 0x64, 0x83, 0x94, 0x0d, 0x6c, 0x1a, 0x93, 0x85, 0x51, 0x73, 0xb3, 0xf3, 0x45, 0xdc, 0xb1, 0x5d, 0x05, 0xad, 0x52, + 0x25, 0xe9, 0xa0, 0x7e, 0x50, 0xed, 0xfd, 0x40, 0xdb, 0x26, 0xc9, 0xdf, 0xd5, 0xac, 0x8b, 0xef, 0xa1, 0xab, 0x9c, + 0x56, 0x3b, 0x10, 0xe6, 0xa3, 0xa2, 0x1d, 0x03, 0x03, 0x45, 0xf1, 0x60, 0x7b, 0x0a, 0x5d, 0xde, 0x6f, 0x8d, 0x4a, + 0x26, 0x9d, 0x02, 0x9b, 0xb2, 0x2a, 0x98, 0xa6, 0x0a, 0xab, 0xf3, 0x1a, 0xb3, 0xbf, 0x3d, 0x2e, 0x3b, 0x09, 0xec, + 0x25, 0xe3, 0x1e, 0x9f, 0x82, 0xdf, 0x50, 0xbc, 0xc6, 0x1a, 0x72, 0x71, 0x1a, 0x98, 0x49, 0x98, 0x45, 0x69, 0xfd, + 0x76, 0xad, 0x7b, 0xfb, 0xb7, 0x5d, 0x3f, 0xa4, 0xf0, 0x81, 0x1e, 0x27, 0x46, 0xcd, 0xfc, 0xa3, 0xfc, 0x5a, 0x4a, + 0x7c, 0xe9, 0xeb, 0x96, 0xbb, 0xbf, 0x52, 0x27, 0xb1, 0x86, 0x39, 0x0c, 0xad, 0xd8, 0x1a, 0xc9, 0x7e, 0x41, 0x4c, + 0x6f, 0xd7, 0x3b, 0x49, 0xc4, 0x80, 0x73, 0x3d, 0x47, 0x96, 0xf9, 0xcc, 0x15, 0xf4, 0x4c, 0x22, 0x34, 0x4c, 0x57, + 0x33, 0x8e, 0x5a, 0x86, 0x5a, 0x4c, 0x1d, 0xc3, 0x1f, 0x1e, 0x32, 0x19, 0x3b, 0x94, 0xd1, 0x8f, 0x75, 0x7c, 0x9b, + 0x1a, 0xcb, 0x86, 0xaf, 0xa1, 0xdc, 0xb3, 0xcb, 0x3f, 0x3a, 0xd4, 0xe6, 0x71, 0x8f, 0xc7, 0xea, 0x20, 0x5e, 0xad, + 0xf6, 0xec, 0x0d, 0x8a, 0x58, 0x9f, 0xd6, 0xb0, 0x3a, 0xb8, 0xcc, 0xe6, 0xfc, 0x1c, 0x6c, 0x12, 0x5c, 0xbd, 0xd5, + 0xcd, 0x2f, 0x43, 0x74, 0x6b, 0x5c, 0x43, 0x72, 0x7e, 0x76, 0xbe, 0x87, 0x8f, 0xf8, 0xc9, 0xcf, 0xb4, 0x64, 0x9e, + 0xad, 0x13, 0xd8, 0x64, 0xd9, 0xb5, 0x65, 0x7e, 0x94, 0x3b, 0xbc, 0x2f, 0xfe, 0x67, 0x7d, 0x61, 0x03, 0x04, 0x84, + 0xf3, 0x4b, 0xfa, 0xc5, 0xe1, 0x39, 0xdb, 0x20, 0x76, 0xbe, 0xf9, 0xbe, 0x48, 0x73, 0x95, 0x06, 0x77, 0xfe, 0xd8, + 0x19, 0x82, 0x63, 0x04, 0xd8, 0x8b, 0xa0, 0x11, 0x21, 0x99, 0xde, 0x91, 0x52, 0x74, 0xb3, 0xc6, 0x69, 0x25, 0x65, + 0x2d, 0xdc, 0x57, 0xda, 0xd4, 0x5d, 0xb9, 0xbb, 0xa8, 0x88, 0xd9, 0xa5, 0x09, 0x0e, 0x05, 0x16, 0x23, 0x93, 0xb2, + 0x24, 0x85, 0xa6, 0xbc, 0xf8, 0x47, 0x82, 0x1d, 0x01, 0x93, 0x6b, 0xb4, 0x0a, 0xc1, 0x38, 0x9f, 0x77, 0x56, 0xa5, + 0xa7, 0x91, 0x21, 0xa8, 0x1e, 0x79, 0x65, 0x4b, 0x35, 0x8b, 0xa6, 0xa6, 0x7c, 0x97, 0x5a, 0x37, 0x62, 0xd8, 0x7b, + 0xd5, 0x72, 0x82, 0xbc, 0xab, 0xc4, 0xc9, 0x4d, 0x0e, 0xe3, 0x92, 0xf9, 0x1a, 0x41, 0x89, 0x34, 0xb3, 0x77, 0x09, + 0x93, 0x4d, 0xa6, 0x9c, 0xb3, 0x60, 0x5b, 0xc7, 0x20, 0x91, 0x38, 0x96, 0x1d, 0x12, 0x72, 0x95, 0xf6, 0x4d, 0x96, + 0xa2, 0x3a, 0x73, 0x2a, 0x43, 0x3f, 0xed, 0x78, 0x39, 0x47, 0x4c, 0xc7, 0xd9, 0x22, 0x1d, 0x23, 0x06, 0x10, 0xc6, + 0x8c, 0x27, 0x48, 0xd5, 0xbc, 0x94, 0xd1, 0xea, 0xe2, 0x19, 0xae, 0x51, 0x7b, 0x10, 0x98, 0xe8, 0x18, 0xc4, 0xbe, + 0x4e, 0x68, 0x9c, 0x88, 0xe6, 0x44, 0x79, 0xaf, 0xb5, 0xb0, 0xdd, 0x37, 0xb4, 0x2e, 0x99, 0xc2, 0xa5, 0x14, 0x48, + 0x2c, 0xf3, 0xeb, 0x40, 0xf2, 0x42, 0xd3, 0x58, 0xd1, 0x6a, 0x09, 0xc0, 0xd4, 0x16, 0xcf, 0xd3, 0xb7, 0x7c, 0x33, + 0xab, 0xc7, 0xec, 0x33, 0xba, 0x05, 0xb8, 0xf6, 0x29, 0x65, 0x5c, 0x0f, 0x1e, 0x7b, 0xa0, 0x40, 0x03, 0xf4, 0x94, + 0x73, 0xb7, 0xf8, 0x2b, 0xdb, 0x10, 0xaf, 0x43, 0x37, 0xa8, 0x65, 0x89, 0x3e, 0xe7, 0xd7, 0xe2, 0xe2, 0x3f, 0xe5, + 0x2e, 0x08, 0x8c, 0x5c, 0x7c, 0x52, 0xd0, 0xc3, 0x69, 0xa2, 0xcb, 0x84, 0xc7, 0x7b, 0xd4, 0x69, 0x0a, 0xca, 0x0d, + 0xcc, 0xe2, 0x26, 0x8b, 0x26, 0xdd, 0x5d, 0xdb, 0xea, 0xde, 0x1d, 0x8e, 0x35, 0x37, 0x75, 0x88, 0x3d, 0x75, 0x6f, + 0x56, 0x4d, 0x71, 0xf1, 0x6d, 0xdb, 0x64, 0x1a, 0xb6, 0x36, 0x8a, 0x4a, 0x9a, 0xe6, 0x2d, 0x6b, 0x7f, 0x91, 0xed, + 0x34, 0xc0, 0xdb, 0x85, 0x44, 0xd7, 0x7c, 0xb4, 0x04, 0xb1, 0xa5, 0xfc, 0x78, 0x31, 0xe5, 0xb1, 0xa2, 0xc5, 0x19, + 0xd6, 0x4d, 0xaa, 0x4c, 0xf2, 0x0c, 0x1d, 0x4d, 0xa8, 0xf3, 0x7d, 0x1c, 0x56, 0x8d, 0x46, 0xff, 0xbc, 0x4e, 0xcd, + 0x08, 0x93, 0xd0, 0xe8, 0x44, 0x38, 0x73, 0x18, 0x97, 0xc6, 0x88, 0x97, 0x5e, 0x7e, 0xcc, 0x3f, 0x98, 0x53, 0x14, + 0x58, 0x4f, 0x70, 0x5e, 0x0f, 0x6d, 0xe6, 0xc4, 0x21, 0xd0, 0x73, 0x63, 0x94, 0xf5, 0x25, 0xfd, 0xcc, 0x1b, 0x8d, + 0x7d, 0x28, 0xb9, 0x20, 0x08, 0x15, 0x9e, 0x73, 0x1c, 0x32, 0xcc, 0x40, 0x5f, 0x37, 0xd9, 0x02, 0x3f, 0x6b, 0x73, + 0x1e, 0xf6, 0x45, 0xac, 0x2d, 0x47, 0x17, 0x83, 0xaf, 0x9e, 0xd7, 0x31, 0x3f, 0x90, 0xbf, 0x5d, 0x2b, 0xe3, 0x18, + 0x95, 0x68, 0x10, 0xbb, 0x22, 0x6d, 0x0a, 0x80, 0xa8, 0xd4, 0x39, 0x0e, 0xa2, 0x02, 0xc6, 0xda, 0x0f, 0x94, 0x26, + 0x94, 0x91, 0x02, 0x58, 0x9d, 0xe1, 0x0c, 0x60, 0x07, 0x23, 0xd9, 0x35, 0xab, 0x8f, 0x91, 0xc5, 0x79, 0xb4, 0xbb, + 0x9a, 0x38, 0x2d, 0xba, 0x7b, 0x71, 0x51, 0x26, 0xc6, 0x3d, 0x2a, 0xda, 0x92, 0xc6, 0xad, 0x01, 0x73, 0x56, 0x74, + 0x6b, 0x32, 0x95, 0xab, 0x3b, 0x76, 0x96, 0xe0, 0xf4, 0xd6, 0x15, 0x58, 0xeb, 0x0e, 0xe6, 0xeb, 0x74, 0x56, 0xdc, + 0x7f, 0xa2, 0x20, 0x4d, 0x23, 0xb0, 0x66, 0x13, 0xa4, 0xfa, 0xd1, 0x92, 0x33, 0x0b, 0x58, 0xa5, 0x49, 0x69, 0x69, + 0x05, 0x0c, 0x3e, 0xdb, 0x88, 0x37, 0x6c, 0xcf, 0x9a, 0x0e, 0xab, 0xd1, 0xf7, 0xbe, 0x53, 0x93, 0xda, 0x23, 0xd3, + 0xdd, 0xf6, 0xe6, 0x14, 0x42, 0xf4, 0x85, 0x29, 0xcd, 0x14, 0x01, 0x70, 0xfe, 0xd3, 0x13, 0xc0, 0xed, 0xdb, 0x83, + 0x60, 0xa9, 0xe4, 0xb9, 0xda, 0x9c, 0xb0, 0x03, 0x22, 0x5b, 0xce, 0x75, 0x47, 0x42, 0x0c, 0x8e, 0x39, 0xa3, 0xa2, + 0x17, 0x24, 0x71, 0x06, 0xad, 0x6c, 0x75, 0x0d, 0xf8, 0xad, 0xc3, 0x81, 0x09, 0x51, 0x8e, 0x60, 0xf0, 0x8e, 0x31, + 0x6c, 0x66, 0x72, 0x9b, 0xd1, 0x40, 0x34, 0x54, 0x43, 0x96, 0x2f, 0x7b, 0xb1, 0x3d, 0xda, 0xd1, 0x7c, 0x60, 0xc9, + 0xfc, 0xe6, 0x13, 0xe1, 0x3e, 0xb6, 0x7b, 0xd8, 0xab, 0xef, 0x85, 0x49, 0x75, 0x7c, 0xda, 0xba, 0x74, 0xae, 0xbd, + 0xb8, 0xae, 0x5e, 0x9a, 0xbd, 0xe9, 0x1f, 0xce, 0xc6, 0x22, 0x07, 0x59, 0x05, 0xfd, 0x79, 0x30, 0x0f, 0x04, 0xd5, + 0xd4, 0x65, 0xb2, 0x08, 0x70, 0xa9, 0x11, 0xa5, 0xd7, 0x3a, 0x23, 0xb0, 0x0d, 0x8c, 0xeb, 0xb1, 0xb9, 0xc2, 0xd3, + 0xf3, 0x59, 0x12, 0x0d, 0x72, 0x28, 0x15, 0x7a, 0xcd, 0xe7, 0xa3, 0x0f, 0x4a, 0xfe, 0xeb, 0xf2, 0xb4, 0xfe, 0x4b, + 0xca, 0x19, 0xa8, 0xa6, 0x9d, 0xca, 0x3f, 0x96, 0x75, 0x10, 0x6f, 0x5b, 0xd7, 0x77, 0xae, 0xe7, 0x3f, 0x4c, 0x48, + 0xa6, 0xce, 0x6d, 0xe8, 0x2c, 0x26, 0x7d, 0xbf, 0x4b, 0xb4, 0x71, 0xb6, 0x22, 0x25, 0x06, 0xaa, 0xf6, 0x05, 0xd9, + 0xa4, 0xde, 0x24, 0xb5, 0xba, 0xf1, 0x91, 0xb6, 0xc8, 0x0a, 0x6e, 0x1a, 0xb2, 0x81, 0xfa, 0x69, 0xef, 0x8e, 0xdd, + 0xbe, 0x3f, 0x99, 0xbb, 0xaf, 0xdd, 0xe6, 0xdf, 0x28, 0xbb, 0xfd, 0xdc, 0x1b, 0x69, 0x36, 0x14, 0x3d, 0x6f, 0xbc, + 0x6c, 0x4b, 0xc6, 0xc1, 0x5b, 0x33, 0x03, 0xc1, 0x61, 0x4e, 0x3e, 0xd6, 0x79, 0x06, 0x24, 0x2d, 0xb3, 0x68, 0x03, + 0x72, 0x8d, 0x4b, 0x1c, 0x50, 0x55, 0xb0, 0xf3, 0x99, 0xa9, 0x36, 0x27, 0x12, 0xd7, 0x3b, 0x59, 0x1e, 0x76, 0x74, + 0x71, 0x87, 0x69, 0x99, 0x6e, 0xe2, 0xc2, 0xd1, 0x0d, 0x3b, 0x25, 0x4d, 0x36, 0x4f, 0x34, 0x9b, 0x4e, 0xbf, 0x4b, + 0xfa, 0xe6, 0x30, 0x90, 0x36, 0x67, 0x3e, 0xdc, 0x74, 0x1e, 0xba, 0xd0, 0x15, 0xee, 0x26, 0x15, 0xa9, 0xb4, 0x9c, + 0x40, 0x55, 0xc7, 0xb6, 0x52, 0x50, 0x94, 0xe2, 0xdf, 0x7c, 0x6f, 0x78, 0x58, 0x15, 0x7c, 0x13, 0x13, 0x49, 0xcc, + 0xd6, 0xaa, 0xf1, 0x85, 0x38, 0xfd, 0x41, 0x99, 0xb7, 0xf3, 0xf9, 0x24, 0x8e, 0x21, 0xff, 0xbd, 0x6a, 0x2a, 0x52, + 0x40, 0x93, 0x38, 0x48, 0xc8, 0xcc, 0xd8, 0x29, 0xfa, 0x78, 0x42, 0xe8, 0x4c, 0x52, 0x3e, 0xb8, 0xcc, 0xc1, 0x2f, + 0xbb, 0x3d, 0xba, 0xad, 0x72, 0x9b, 0x5b, 0x81, 0x3d, 0x55, 0x53, 0xa4, 0x25, 0x85, 0x4c, 0xba, 0x3a, 0x75, 0x6c, + 0x9c, 0xf5, 0xb5, 0xfb, 0x6f, 0x55, 0xc9, 0x9e, 0xbf, 0x3e, 0xe7, 0x6b, 0xe3, 0x90, 0x26, 0x15, 0xff, 0x8e, 0xdb, + 0x75, 0x77, 0x03, 0xc0, 0x9f, 0xb5, 0x4a, 0x89, 0x97, 0xd2, 0x35, 0xdd, 0x57, 0xd1, 0x6e, 0x78, 0xb6, 0xea, 0x27, + 0x4c, 0xd9, 0x9b, 0x91, 0xf2, 0x7e, 0xa2, 0xfe, 0xd2, 0xc3, 0x96, 0xa3, 0x19, 0x70, 0x32, 0xbc, 0xb9, 0x9d, 0x3b, + 0xcc, 0xf9, 0xd7, 0xd8, 0x1a, 0x0e, 0xbb, 0x6f, 0x40, 0x6f, 0x51, 0x1c, 0xb5, 0x6b, 0xa2, 0x64, 0x02, 0x01, 0x13, + 0xc1, 0x81, 0xb8, 0x8f, 0xd5, 0x48, 0x66, 0xed, 0x4b, 0xd8, 0x1d, 0xad, 0x4c, 0x8b, 0x96, 0x6a, 0xcd, 0xa7, 0x42, + 0x99, 0x49, 0x2a, 0x86, 0xd5, 0xc0, 0x9f, 0x5d, 0x39, 0x2d, 0xf8, 0x12, 0x58, 0x5e, 0xee, 0x78, 0x16, 0xb6, 0x0b, + 0x79, 0x7f, 0xfb, 0x60, 0x0d, 0xf8, 0x58, 0x9f, 0xb2, 0xe2, 0xbd, 0xfe, 0x77, 0x61, 0x43, 0x2d, 0x3d, 0xe6, 0xd7, + 0x66, 0xe1, 0x07, 0xe7, 0x35, 0x0e, 0x6e, 0x55, 0xed, 0x54, 0x31, 0x2e, 0x45, 0x14, 0x40, 0x80, 0x57, 0x34, 0x7c, + 0x47, 0x9d, 0xc7, 0xb3, 0xba, 0x44, 0x3f, 0x7e, 0xdf, 0x6d, 0x69, 0x4b, 0x12, 0x10, 0xa5, 0xca, 0x29, 0x72, 0x2b, + 0x27, 0xd7, 0x2c, 0x92, 0xa5, 0x77, 0x66, 0xd3, 0xa8, 0x68, 0x1a, 0x00, 0x48, 0xdf, 0x23, 0xcf, 0x82, 0xe7, 0x50, + 0x6e, 0x25, 0xf1, 0x56, 0xc9, 0x4c, 0x80, 0x89, 0xc2, 0x0f, 0x12, 0x21, 0x0c, 0x88, 0xbc, 0x1b, 0x06, 0x69, 0x6d, + 0x5f, 0xb8, 0x3e, 0x03, 0x58, 0x26, 0xc4, 0x5f, 0xdc, 0x87, 0x5b, 0xe7, 0xd3, 0xc1, 0xc9, 0x8d, 0x1c, 0x22, 0x82, + 0xd9, 0x28, 0xdd, 0x9b, 0x1c, 0x59, 0x65, 0xf7, 0x93, 0xab, 0x56, 0x4f, 0x04, 0x54, 0x5a, 0xbe, 0x57, 0xdc, 0x8c, + 0x38, 0x7f, 0x06, 0xdd, 0x6d, 0x70, 0xe5, 0x2a, 0xe7, 0xb4, 0x53, 0xd9, 0x21, 0xd9, 0xfe, 0xbb, 0x08, 0x5a, 0x94, + 0xcd, 0x14, 0xbe, 0x94, 0x2d, 0x3c, 0xb7, 0xd5, 0x95, 0xdb, 0x00, 0x90, 0x45, 0x62, 0x34, 0x17, 0x0d, 0xef, 0x92, + 0x08, 0xe3, 0xa2, 0xcd, 0x9f, 0xaa, 0x4f, 0xb3, 0x1a, 0x2a, 0xca, 0x46, 0xb5, 0xd9, 0x68, 0x86, 0x0c, 0xc4, 0x13, + 0xf4, 0xf2, 0xab, 0x1d, 0xe0, 0xc7, 0x2a, 0x79, 0xb2, 0x74, 0x1b, 0xf1, 0xb6, 0x3e, 0x49, 0xd4, 0xd3, 0x56, 0xf7, + 0x65, 0x98, 0x2c, 0x68, 0x9e, 0x3e, 0xff, 0xdf, 0x1d, 0xe0, 0x97, 0x90, 0x87, 0x2b, 0x16, 0xbe, 0x53, 0x75, 0x1f, + 0xaf, 0xaa, 0x70, 0xb1, 0x5e, 0x36, 0xe7, 0x83, 0x0e, 0x20, 0x47, 0xaa, 0xfa, 0x7d, 0x0e, 0xd3, 0x90, 0xe5, 0xb7, + 0x46, 0x17, 0x6e, 0x45, 0xc1, 0x81, 0xe7, 0xbd, 0x16, 0x2d, 0xd4, 0xd4, 0x55, 0x46, 0x84, 0x71, 0x4a, 0x50, 0x87, + 0x5b, 0x5d, 0xf0, 0xb4, 0x9b, 0x5b, 0x50, 0x49, 0x9e, 0x77, 0x93, 0x08, 0xe9, 0xc0, 0x7b, 0xb7, 0x52, 0x56, 0xbd, + 0x6e, 0x08, 0xc3, 0x5e, 0x39, 0xbb, 0x0a, 0x95, 0x3a, 0xad, 0xb4, 0x86, 0x1b, 0x5a, 0xb5, 0xa6, 0x68, 0xe0, 0xe4, + 0x94, 0xaa, 0x55, 0x2d, 0x79, 0xd6, 0x74, 0x6e, 0xb2, 0xcd, 0x5c, 0x56, 0x74, 0xdd, 0x21, 0xc3, 0x2b, 0x85, 0x75, + 0x5d, 0x07, 0xd7, 0x70, 0xa2, 0xc1, 0x79, 0xdf, 0x6e, 0x5b, 0x8e, 0x14, 0xd9, 0x2e, 0x56, 0x17, 0xee, 0xe8, 0xf8, + 0x2f, 0x0f, 0x28, 0x3e, 0x1d, 0xb5, 0xe4, 0x51, 0x8c, 0xac, 0xd0, 0x34, 0xb8, 0x06, 0x22, 0xfc, 0x0b, 0xaf, 0x4d, + 0xda, 0x6b, 0x4a, 0x26, 0xd4, 0xad, 0x9b, 0x73, 0x38, 0x48, 0x4b, 0xfc, 0xd2, 0x34, 0x8d, 0xb7, 0x79, 0x7a, 0x1f, + 0x4d, 0x56, 0xb5, 0xb7, 0x88, 0x4d, 0x7a, 0x76, 0x6d, 0x64, 0xb8, 0xc1, 0x84, 0x3c, 0xfe, 0x07, 0x3b, 0x01, 0x3a, + 0x91, 0x92, 0x05, 0x97, 0xe3, 0xca, 0x52, 0x0c, 0xf5, 0x9b, 0x57, 0x26, 0xeb, 0x53, 0x58, 0x64, 0x5e, 0x46, 0xae, + 0x5b, 0x2a, 0x47, 0xc3, 0x0f, 0x7d, 0x92, 0x2a, 0xe2, 0x3c, 0x03, 0x11, 0x78, 0xca, 0x6a, 0xe9, 0x79, 0x8e, 0x59, + 0x1b, 0x47, 0x92, 0xf9, 0x20, 0x24, 0x1f, 0xc6, 0xa5, 0x18, 0x52, 0x6c, 0xdf, 0xd8, 0x52, 0xe5, 0xf8, 0x86, 0x10, + 0x95, 0x13, 0xae, 0xa0, 0x09, 0x63, 0xed, 0x4f, 0x51, 0x51, 0x74, 0x67, 0xb5, 0x24, 0xd8, 0xad, 0x3a, 0x39, 0xa9, + 0xce, 0x34, 0x7f, 0x80, 0xc1, 0xd2, 0x1b, 0x74, 0x74, 0x58, 0x57, 0x63, 0x7e, 0x74, 0xb0, 0xe2, 0xd6, 0x57, 0x36, + 0x99, 0x45, 0xdb, 0x98, 0x71, 0xa6, 0xd4, 0x16, 0xdf, 0x5b, 0x9b, 0x5d, 0x04, 0x66, 0xf7, 0x0a, 0x97, 0x28, 0x22, + 0x67, 0xeb, 0x98, 0x91, 0x54, 0x71, 0xed, 0x10, 0xa9, 0xea, 0x9c, 0xd0, 0xc7, 0x40, 0x8b, 0xcf, 0x38, 0x5d, 0x2d, + 0xc4, 0x36, 0x0e, 0xbb, 0x8e, 0x4c, 0x95, 0xe4, 0x77, 0xd1, 0xe7, 0x7e, 0x2c, 0xc1, 0xe6, 0x02, 0xe2, 0x39, 0xdf, + 0x3b, 0x17, 0x6a, 0x16, 0x76, 0x21, 0xec, 0x60, 0x1a, 0x25, 0xe4, 0x68, 0xbf, 0x56, 0x7e, 0x8e, 0xe0, 0xd5, 0x2b, + 0x3d, 0x93, 0x0d, 0x3f, 0x11, 0xd1, 0xca, 0x52, 0xc2, 0x91, 0x0c, 0xa3, 0xf7, 0x2f, 0xde, 0xdc, 0x70, 0x90, 0xa1, + 0xf0, 0x0c, 0x36, 0x0f, 0x44, 0xc0, 0xed, 0xdd, 0x4f, 0x98, 0xd6, 0x52, 0x29, 0x08, 0xe7, 0x0a, 0x86, 0x04, 0x1b, + 0xe3, 0x52, 0x66, 0x6b, 0x93, 0x35, 0x01, 0x6b, 0xe1, 0x88, 0x3a, 0x68, 0x4c, 0x7a, 0x9e, 0x77, 0x9a, 0xd6, 0x31, + 0xff, 0x29, 0xb8, 0x60, 0xf9, 0x9e, 0x8d, 0xeb, 0x15, 0x04, 0xcd, 0x35, 0xae, 0xb1, 0xa6, 0xbb, 0xe8, 0x41, 0xea, + 0xfd, 0x35, 0x7b, 0xc6, 0x2a, 0x7f, 0xb7, 0xc0, 0x24, 0xd0, 0xa0, 0x50, 0x34, 0xe5, 0x2b, 0xa1, 0x43, 0x88, 0x5e, + 0xcd, 0x1b, 0xff, 0x2a, 0x7a, 0x96, 0x53, 0xcd, 0xe4, 0x76, 0xa3, 0x1a, 0x9a, 0x61, 0xca, 0x14, 0x12, 0xda, 0xc6, + 0x0f, 0x24, 0x5f, 0x76, 0xcb, 0xd4, 0xc2, 0x9c, 0xfd, 0x97, 0x16, 0xc7, 0xb1, 0x85, 0xaa, 0x55, 0x5f, 0x84, 0x39, + 0x4e, 0x4c, 0x5b, 0x77, 0xd9, 0xc8, 0x9d, 0xcd, 0x21, 0xa8, 0xa6, 0x6c, 0x6e, 0xd4, 0xbd, 0x63, 0x3e, 0x32, 0x87, + 0xb7, 0xc8, 0xef, 0x76, 0x64, 0x5e, 0x26, 0x97, 0x7d, 0xfc, 0xac, 0xd7, 0xbf, 0x09, 0x80, 0xc4, 0x36, 0x06, 0x8e, + 0xcd, 0xf3, 0xae, 0xb1, 0x96, 0x1b, 0xd3, 0x45, 0x62, 0x4d, 0x1d, 0x00, 0x0a, 0x9e, 0x72, 0xa0, 0x50, 0x49, 0x53, + 0x12, 0x04, 0xf5, 0x10, 0x72, 0x44, 0x39, 0xbe, 0x5d, 0xc4, 0x5c, 0xd7, 0xab, 0xc9, 0xc6, 0xbf, 0xdc, 0xfa, 0x68, + 0xd5, 0x07, 0xb4, 0xfb, 0x99, 0x8d, 0x7a, 0x58, 0xa4, 0xc6, 0x29, 0x0c, 0xf9, 0x11, 0xe7, 0xb1, 0xa6, 0x41, 0x37, + 0x4e, 0x06, 0x5a, 0x41, 0x2f, 0x15, 0xf8, 0xdf, 0x42, 0x39, 0x63, 0xe5, 0x46, 0x79, 0xa8, 0x58, 0xad, 0x5d, 0xf7, + 0xaf, 0xf8, 0x32, 0x62, 0x12, 0xa6, 0x87, 0x27, 0x60, 0xd6, 0x52, 0xae, 0xe4, 0xe7, 0xf5, 0x36, 0x54, 0x0b, 0x0f, + 0x38, 0xe9, 0xbc, 0xae, 0x3e, 0x07, 0x72, 0x91, 0x35, 0x53, 0x74, 0x68, 0xce, 0xd3, 0xa0, 0x82, 0x09, 0xbf, 0xad, + 0xe7, 0x26, 0x09, 0xba, 0xd4, 0x38, 0x56, 0x1e, 0x76, 0x1f, 0x47, 0xa3, 0xb3, 0x28, 0x27, 0x2e, 0x54, 0x63, 0x97, + 0xe7, 0x59, 0x54, 0x39, 0x2f, 0xf6, 0xa4, 0xab, 0x75, 0x65, 0xad, 0xbd, 0xa5, 0x15, 0xf3, 0xc6, 0x50, 0x4b, 0x52, + 0x73, 0x98, 0xd6, 0x89, 0x34, 0xb3, 0x68, 0x58, 0x99, 0x55, 0x08, 0xde, 0x86, 0xdd, 0x46, 0x88, 0xec, 0x82, 0x83, + 0xb4, 0x10, 0x2f, 0xbd, 0x59, 0x6a, 0x38, 0xc1, 0x53, 0xc8, 0x15, 0xfd, 0xc3, 0x69, 0x61, 0x40, 0x6a, 0x2b, 0x4a, + 0x66, 0xfd, 0xe8, 0xbf, 0xd9, 0x0c, 0xf7, 0x33, 0xd7, 0xca, 0x3b, 0xd4, 0x1f, 0x07, 0xa3, 0xd9, 0x8f, 0x49, 0x9f, + 0x72, 0xde, 0x2e, 0x05, 0x98, 0x2c, 0xc1, 0xb9, 0x17, 0xec, 0xcd, 0x80, 0x96, 0x37, 0x5e, 0x35, 0xb9, 0x21, 0x13, + 0xae, 0x9f, 0x24, 0x71, 0x2e, 0x56, 0x41, 0x7a, 0x09, 0xee, 0xbd, 0x68, 0xa8, 0x95, 0x05, 0xe9, 0xfe, 0x63, 0xb6, + 0xf8, 0x6b, 0x83, 0x91, 0x29, 0x88, 0x4f, 0x9e, 0xb0, 0xb7, 0x24, 0x8d, 0x4f, 0xe0, 0xd6, 0xb1, 0xe1, 0xda, 0xac, + 0x40, 0x1f, 0xfc, 0x79, 0xb2, 0x70, 0x68, 0x0d, 0xfc, 0xe7, 0xbb, 0x7f, 0x19, 0xaa, 0x1e, 0x3c, 0xdb, 0x99, 0x26, + 0xeb, 0x86, 0x9a, 0x48, 0xc3, 0x5f, 0xed, 0x7d, 0x01, 0xb8, 0x08, 0x57, 0x31, 0x03, 0x12, 0xd0, 0x95, 0xae, 0x58, + 0x60, 0x98, 0x02, 0xbb, 0x8c, 0xfe, 0x04, 0xbc, 0xad, 0x5c, 0x63, 0x3a, 0x4c, 0x8a, 0x4d, 0x00, 0xc4, 0x25, 0x01, + 0xf2, 0x96, 0x0e, 0x55, 0x04, 0x3a, 0x38, 0xc4, 0x7a, 0x79, 0x67, 0x12, 0xdf, 0xb9, 0x8f, 0xac, 0xce, 0x81, 0x3f, + 0x0d, 0xc8, 0x76, 0xa1, 0x00, 0x76, 0xcb, 0xbd, 0x5d, 0x87, 0x47, 0x83, 0x0c, 0x29, 0x51, 0x8c, 0x25, 0xf8, 0xf8, + 0x64, 0x1e, 0xf3, 0x98, 0xe7, 0xe3, 0x80, 0x6f, 0xf4, 0x01, 0x54, 0x1c, 0x2a, 0x90, 0xbf, 0x0b, 0x51, 0xa1, 0x2e, + 0xf7, 0xd1, 0x02, 0xc0, 0xe8, 0x13, 0xcc, 0xa1, 0x13, 0xb7, 0xd4, 0x1b, 0x50, 0xe5, 0x7b, 0x90, 0x52, 0x09, 0xfe, + 0x46, 0x26, 0x53, 0xd5, 0x9e, 0x8a, 0x59, 0x55, 0x18, 0x45, 0x24, 0x6c, 0xd4, 0x16, 0xc2, 0x1d, 0x63, 0x46, 0xcd, + 0x8f, 0x9d, 0x79, 0x1c, 0x4b, 0x7b, 0x3d, 0x12, 0x4a, 0x76, 0xc6, 0x7b, 0x0f, 0x4a, 0xe1, 0xe0, 0x2a, 0x80, 0xfb, + 0xb4, 0xfa, 0x9c, 0x4a, 0x8c, 0x99, 0x65, 0xd1, 0xf0, 0x50, 0x7a, 0x93, 0xa8, 0xf1, 0x55, 0x70, 0xfd, 0xcd, 0x40, + 0xbc, 0x8a, 0x3f, 0x7b, 0xdc, 0xf4, 0x71, 0xf5, 0xbf, 0x21, 0xe0, 0xea, 0x2c, 0x5c, 0x39, 0x61, 0x9f, 0x27, 0xc8, + 0xd7, 0x0d, 0xde, 0x2e, 0x5b, 0x4b, 0x34, 0x4f, 0x66, 0xe9, 0x73, 0x67, 0x58, 0xa0, 0xaa, 0xaa, 0xf9, 0x2d, 0x0a, + 0x25, 0x64, 0x91, 0x41, 0x68, 0x48, 0x9a, 0x99, 0x48, 0xed, 0xdc, 0x5b, 0x6e, 0x62, 0x47, 0x1a, 0x78, 0xda, 0xee, + 0x3d, 0xc3, 0xc7, 0x68, 0x30, 0x14, 0xc9, 0x33, 0xb8, 0xf2, 0x06, 0xba, 0x52, 0x49, 0xca, 0xe5, 0x7c, 0x2c, 0xfa, + 0x32, 0xf4, 0x2b, 0xfa, 0x4d, 0x5a, 0x96, 0xc7, 0x5d, 0x24, 0x52, 0xff, 0x57, 0xb9, 0xe6, 0x34, 0xfa, 0xbc, 0x34, + 0xb6, 0x51, 0x31, 0x68, 0x70, 0xdb, 0x14, 0x08, 0x39, 0x53, 0x5a, 0x94, 0x1e, 0x0c, 0x2d, 0x7d, 0xff, 0xc3, 0x55, + 0x58, 0xba, 0xa7, 0xb4, 0x53, 0x9e, 0x5e, 0xf4, 0x52, 0x83, 0x81, 0xf8, 0x77, 0xb2, 0xe4, 0x4d, 0x5f, 0xa9, 0x91, + 0x4c, 0xfc, 0x1f, 0xbc, 0xb4, 0x51, 0x2e, 0x97, 0x3a, 0xa5, 0xd3, 0x0e, 0x8a, 0xa3, 0x2e, 0x39, 0x1e, 0xc5, 0xbe, + 0x65, 0x34, 0x8a, 0x57, 0xca, 0x3e, 0x8b, 0x89, 0x1b, 0xf4, 0x44, 0x34, 0x68, 0xd6, 0x32, 0x80, 0x26, 0x7a, 0x4d, + 0xc9, 0x88, 0x53, 0x77, 0x82, 0x1b, 0x81, 0x32, 0xab, 0x68, 0x43, 0x52, 0x37, 0xbe, 0x31, 0x98, 0x5a, 0x3d, 0xee, + 0x87, 0x21, 0x2a, 0x65, 0x7d, 0xfb, 0x74, 0x44, 0xd5, 0x57, 0xd9, 0xa5, 0xf4, 0xad, 0x62, 0xa3, 0x5d, 0xea, 0x70, + 0xc7, 0x1c, 0xd8, 0xe4, 0x99, 0x41, 0x2d, 0x67, 0x0e, 0x31, 0x3f, 0x3d, 0x8f, 0x36, 0x0e, 0x98, 0x9d, 0x18, 0x62, + 0x8e, 0x3a, 0x57, 0x25, 0x90, 0xc6, 0x60, 0x3a, 0xb1, 0x93, 0x44, 0xea, 0x4b, 0xcb, 0x5e, 0xaf, 0x54, 0x31, 0xa7, + 0x96, 0x96, 0xfd, 0x00, 0x76, 0xf8, 0x4a, 0xcb, 0x4f, 0x54, 0x61, 0x68, 0x76, 0xcb, 0x1a, 0xe1, 0xaf, 0x36, 0xbd, + 0x8e, 0xd7, 0xf1, 0x2a, 0x95, 0xa5, 0x3b, 0x20, 0x86, 0x1c, 0xcc, 0x4e, 0xb0, 0x01, 0x29, 0xa2, 0x65, 0x71, 0xbe, + 0xe6, 0x29, 0x9f, 0x8d, 0x63, 0x89, 0xb5, 0xd1, 0x63, 0xcb, 0xdb, 0xe6, 0xdc, 0xa3, 0x19, 0xa1, 0x22, 0x51, 0x62, + 0xd9, 0xd6, 0xb0, 0xb8, 0x11, 0x0b, 0x4a, 0x88, 0x25, 0xfa, 0x05, 0x3f, 0x23, 0xe2, 0x6a, 0x80, 0xde, 0xa4, 0x76, + 0x06, 0x5d, 0x05, 0x1d, 0x8c, 0xa3, 0x6b, 0xfe, 0x3b, 0x0d, 0x37, 0x85, 0x2e, 0x11, 0xb7, 0x0d, 0x70, 0xc9, 0xc5, + 0x0c, 0x83, 0x3a, 0x85, 0xac, 0x6e, 0xe2, 0x5b, 0x5d, 0xe4, 0x7f, 0x62, 0xf1, 0x27, 0xb8, 0x90, 0x17, 0x97, 0x86, + 0x17, 0xe4, 0xa6, 0xbc, 0xf7, 0x5b, 0xdc, 0xc8, 0x10, 0xad, 0x7c, 0xfa, 0xe8, 0xf2, 0x62, 0x91, 0x66, 0xdc, 0xa9, + 0xe9, 0xad, 0xf1, 0xb9, 0x6e, 0x45, 0x7f, 0x32, 0x9e, 0x9b, 0x71, 0x92, 0x66, 0xe4, 0xa7, 0x7c, 0xc8, 0xef, 0xa1, + 0x54, 0x33, 0x9c, 0x57, 0x73, 0x1d, 0x50, 0xcf, 0x0c, 0x5f, 0x4e, 0x63, 0x1d, 0x98, 0x74, 0x0b, 0xfe, 0xb0, 0x87, + 0x43, 0xd9, 0xb4, 0xb7, 0x4f, 0xde, 0xf0, 0xb9, 0xd5, 0x3d, 0x5d, 0x32, 0x4a, 0x1a, 0x4c, 0x7d, 0x54, 0xb5, 0xdf, + 0x97, 0x68, 0x1c, 0xc4, 0xd3, 0x18, 0x6b, 0x44, 0xff, 0x4b, 0x7c, 0x7c, 0x55, 0x86, 0x37, 0xc0, 0x3c, 0x28, 0x49, + 0x8e, 0xa5, 0x5f, 0x8c, 0x69, 0x84, 0xc8, 0x7b, 0xcc, 0x2f, 0xea, 0xf5, 0x60, 0xe3, 0x32, 0xe4, 0xe2, 0x15, 0xd1, + 0xe3, 0xd9, 0xe2, 0x5b, 0xe8, 0xc2, 0x70, 0x98, 0x9a, 0x00, 0xfe, 0x1f, 0x65, 0x0f, 0xd4, 0x0f, 0xa1, 0x7c, 0x99, + 0x36, 0xb6, 0x9f, 0x6d, 0x9a, 0x65, 0x46, 0xde, 0x9d, 0x27, 0x6b, 0xb6, 0x91, 0xc4, 0xda, 0x34, 0x6a, 0x13, 0x34, + 0x5a, 0xbd, 0xcd, 0xd9, 0x37, 0x36, 0xa6, 0xd1, 0xd0, 0xf7, 0x68, 0xa6, 0xf4, 0xfa, 0x31, 0x7d, 0x71, 0x7d, 0x87, + 0x98, 0x18, 0xf6, 0x9b, 0xd5, 0x3a, 0x24, 0x36, 0xba, 0xdb, 0x71, 0xc6, 0xfa, 0x1e, 0xd1, 0x7d, 0x93, 0xcb, 0x42, + 0x4e, 0x6e, 0x42, 0xa6, 0x12, 0x75, 0xed, 0xdb, 0x6a, 0xd8, 0xde, 0x03, 0x94, 0x51, 0xb3, 0xd4, 0xc0, 0xe8, 0x8b, + 0xd7, 0xe5, 0x0c, 0xc1, 0x35, 0xb7, 0xde, 0xb8, 0x40, 0x64, 0xf0, 0xd1, 0xb4, 0xcc, 0x65, 0x51, 0x03, 0x27, 0x47, + 0xeb, 0x20, 0xfd, 0xf2, 0x20, 0x1e, 0xa9, 0xfa, 0xe2, 0x6d, 0xcd, 0xc0, 0x8a, 0x96, 0xa8, 0x86, 0x0f, 0x7c, 0xbc, + 0x36, 0xce, 0xcb, 0x8c, 0x5f, 0x4e, 0x8e, 0xd2, 0x0d, 0xe3, 0xca, 0xda, 0xee, 0x62, 0x1c, 0xae, 0xba, 0xad, 0x4a, + 0xa6, 0x64, 0xc6, 0xbe, 0x25, 0x99, 0x9f, 0x49, 0xa5, 0xe7, 0x8d, 0x9a, 0x97, 0xb0, 0xd9, 0xf3, 0x67, 0x3a, 0xc5, + 0x95, 0x49, 0x36, 0x0a, 0xdd, 0xff, 0xd1, 0x8d, 0x58, 0x7a, 0x8f, 0x0e, 0x8c, 0x39, 0xb8, 0x7a, 0x4a, 0xcf, 0x43, + 0x5b, 0x0d, 0xef, 0xe9, 0xfb, 0x34, 0x5f, 0x89, 0xcf, 0x7f, 0xe9, 0x86, 0x8d, 0x45, 0x9d, 0xf4, 0x7e, 0xd5, 0x29, + 0x24, 0x0e, 0x6e, 0x45, 0x3b, 0x21, 0x27, 0xf9, 0x09, 0x41, 0x7d, 0xd9, 0xa0, 0xda, 0x00, 0x6c, 0x58, 0xa5, 0xa2, + 0x2e, 0x06, 0x5a, 0x8e, 0x28, 0x5b, 0x0f, 0xfa, 0xda, 0xb4, 0x3d, 0xdd, 0x5f, 0x35, 0xab, 0x6d, 0xeb, 0x65, 0x09, + 0x53, 0x96, 0x4e, 0xdb, 0x85, 0x3a, 0x6d, 0xc9, 0x33, 0xfd, 0x52, 0x17, 0x73, 0xda, 0xc4, 0xc1, 0xcf, 0x95, 0xbf, + 0x87, 0xdb, 0xda, 0x1d, 0xbb, 0xd6, 0xc8, 0x06, 0xc7, 0xed, 0x31, 0xc7, 0xd9, 0x05, 0x22, 0x5a, 0x16, 0xda, 0x1e, + 0xaa, 0x16, 0xa9, 0x3b, 0xf5, 0x9d, 0x09, 0xbb, 0x09, 0x20, 0x54, 0xec, 0x5d, 0x92, 0x3c, 0x7c, 0x96, 0xd9, 0xe8, + 0xc0, 0x6e, 0xb2, 0x52, 0x9b, 0xf8, 0xfa, 0x94, 0x99, 0x96, 0xa2, 0xab, 0x33, 0x6a, 0xe0, 0xce, 0x69, 0x3e, 0x39, + 0x68, 0x26, 0xca, 0x6d, 0x13, 0xd9, 0xf3, 0x91, 0x3a, 0x41, 0x5d, 0xa0, 0x12, 0x35, 0xad, 0x53, 0xcb, 0x08, 0x0a, + 0x37, 0xc9, 0xde, 0x78, 0xa4, 0x9b, 0xb1, 0x62, 0xfb, 0x15, 0xa8, 0x9b, 0xb3, 0x1b, 0x77, 0x60, 0xc8, 0xaa, 0x15, + 0x6a, 0x67, 0x04, 0xc7, 0xd0, 0x7c, 0x2d, 0x29, 0x12, 0x86, 0x95, 0x80, 0x1d, 0x38, 0x52, 0xa4, 0x20, 0xb8, 0xdb, + 0xea, 0xfc, 0x0d, 0x94, 0x1e, 0x51, 0xa2, 0xc2, 0x2b, 0x2a, 0xa7, 0x74, 0x83, 0x5d, 0x3d, 0x17, 0x20, 0x60, 0x0a, + 0x28, 0x36, 0x32, 0x8b, 0xca, 0x76, 0xab, 0x42, 0xf6, 0x72, 0x3d, 0xb8, 0xbc, 0xf9, 0x40, 0xdd, 0xd8, 0xf4, 0xdd, + 0x97, 0x34, 0xe8, 0x84, 0xe2, 0xc1, 0x07, 0xec, 0xb1, 0x15, 0xf1, 0x4d, 0x76, 0xc8, 0x34, 0x91, 0x31, 0xea, 0x4b, + 0xe4, 0x83, 0x69, 0xff, 0xee, 0x97, 0xc3, 0x2a, 0xe0, 0xea, 0x77, 0xba, 0x91, 0x43, 0xc5, 0xbc, 0x1b, 0x10, 0xa2, + 0x90, 0x01, 0x19, 0xd1, 0xd6, 0x7f, 0xb6, 0xf4, 0xb5, 0x44, 0x3b, 0xda, 0xda, 0x27, 0x01, 0xd9, 0x43, 0x6f, 0xb6, + 0xc1, 0x39, 0x19, 0x2c, 0x00, 0x0c, 0xfe, 0x0b, 0xcd, 0x37, 0x89, 0xa5, 0x84, 0x56, 0x45, 0xf0, 0x71, 0x68, 0x66, + 0x6f, 0xcc, 0xa8, 0xfa, 0x34, 0x03, 0xe8, 0x9e, 0x84, 0x50, 0xe6, 0x6c, 0xaf, 0x37, 0x04, 0x75, 0xec, 0x17, 0x8a, + 0xd5, 0x67, 0x70, 0xc3, 0xff, 0xe8, 0xab, 0x5f, 0xe0, 0x5e, 0x45, 0x51, 0x13, 0xbb, 0xa6, 0x68, 0x1c, 0x4a, 0xb8, + 0xc9, 0x85, 0xf5, 0x2e, 0x09, 0x02, 0x8d, 0xfe, 0x2b, 0x35, 0xc5, 0xc8, 0x02, 0xba, 0xb3, 0x85, 0xc0, 0x5a, 0xc1, + 0x48, 0x4a, 0x44, 0x28, 0x65, 0xae, 0x33, 0x8b, 0xb7, 0xec, 0xea, 0x97, 0xb6, 0xc4, 0xea, 0xcd, 0x3b, 0x06, 0x67, + 0xc5, 0xf2, 0xed, 0x79, 0x27, 0x33, 0x2f, 0xb4, 0x2c, 0x10, 0xd5, 0x14, 0xd2, 0x97, 0xbc, 0x85, 0xd1, 0xca, 0x63, + 0xe3, 0x82, 0x69, 0x7d, 0xff, 0x52, 0xaa, 0x6a, 0xe7, 0x45, 0xa8, 0xab, 0x97, 0xd1, 0xc4, 0xc2, 0xad, 0xa5, 0x0c, + 0xec, 0x4a, 0x44, 0xb0, 0x4d, 0x11, 0xc0, 0xe4, 0x6b, 0x20, 0x44, 0x3c, 0xa8, 0x82, 0x52, 0x3d, 0x61, 0x61, 0xdf, + 0xa0, 0xe0, 0xdd, 0x5d, 0x74, 0x8d, 0x6f, 0x81, 0x88, 0xde, 0x96, 0xc0, 0x30, 0x3c, 0x2e, 0x9e, 0x4a, 0x79, 0x53, + 0x12, 0xb0, 0x5d, 0x85, 0xef, 0x45, 0x94, 0x9b, 0xb5, 0x1f, 0x8d, 0x68, 0xab, 0x0d, 0x12, 0xa5, 0x45, 0xf6, 0x1a, + 0x4f, 0x9b, 0xfc, 0xaa, 0x79, 0x67, 0xf7, 0x36, 0x7d, 0xd5, 0x86, 0x30, 0x3c, 0x45, 0x3a, 0x25, 0x6c, 0xbb, 0x48, + 0xc4, 0xfd, 0x1f, 0x67, 0x8a, 0x16, 0xfb, 0x6c, 0x9c, 0x4b, 0xb5, 0xeb, 0x3b, 0x04, 0x8c, 0x9f, 0xd5, 0x43, 0x77, + 0xfd, 0xa9, 0x1c, 0xeb, 0x6f, 0x46, 0x1d, 0x54, 0xe0, 0xe1, 0x6e, 0x96, 0x7e, 0x8d, 0xc6, 0xf7, 0x5a, 0x7c, 0xd9, + 0xfb, 0x8a, 0x00, 0xbc, 0x78, 0x13, 0xef, 0xa2, 0xfd, 0x44, 0x27, 0x70, 0x8c, 0xb0, 0x6d, 0x93, 0x80, 0xb5, 0x8f, + 0x5f, 0x91, 0x14, 0xe4, 0xc8, 0xef, 0x40, 0xfe, 0xb7, 0xc6, 0xdc, 0xf0, 0x1d, 0x15, 0x73, 0x4b, 0x29, 0x5d, 0x25, + 0x4f, 0x4e, 0x61, 0x7b, 0xcc, 0x02, 0xc4, 0x11, 0x38, 0x78, 0x3f, 0xb1, 0x27, 0x7f, 0xba, 0xa0, 0x6e, 0x46, 0x47, + 0x8a, 0x43, 0xb1, 0x9a, 0x9f, 0x1a, 0x1a, 0x29, 0x0f, 0xd3, 0x11, 0x41, 0x4d, 0x68, 0x31, 0x16, 0x8e, 0x2e, 0x49, + 0x00, 0x81, 0x09, 0x50, 0xa7, 0xc8, 0xa2, 0xaf, 0x47, 0x6e, 0xc5, 0xa4, 0x67, 0x5b, 0xb9, 0x74, 0xed, 0x13, 0xde, + 0xd4, 0x9e, 0x81, 0x5b, 0xab, 0xc6, 0x68, 0x75, 0x67, 0x47, 0x65, 0xa5, 0xc7, 0xe4, 0x74, 0x6e, 0xae, 0xc4, 0x72, + 0x4d, 0x71, 0x1f, 0x8e, 0x76, 0x0f, 0xea, 0x1d, 0x51, 0x04, 0x62, 0x4c, 0x94, 0xd9, 0x99, 0x9c, 0xed, 0x37, 0x7a, + 0x00, 0xdf, 0x52, 0x50, 0x2f, 0x98, 0x0f, 0xb8, 0xdc, 0x5b, 0xde, 0x91, 0x79, 0xe0, 0x95, 0x09, 0x47, 0x4d, 0xb9, + 0xf6, 0x66, 0x23, 0xb3, 0x44, 0x4d, 0x78, 0xfe, 0xbf, 0x1a, 0x6a, 0x48, 0x2c, 0x20, 0x93, 0xb1, 0x6f, 0xdf, 0x55, + 0xe4, 0xd3, 0x2c, 0x74, 0xb8, 0xc2, 0x01, 0xd4, 0x71, 0x6a, 0x6a, 0xc0, 0x0d, 0x78, 0xf8, 0x41, 0x42, 0x2b, 0xdf, + 0x25, 0xd4, 0xf8, 0xe7, 0x7e, 0xc6, 0xbe, 0x77, 0x9b, 0x6d, 0x9e, 0xd3, 0x2b, 0xc0, 0xd2, 0xe8, 0xfe, 0x36, 0xe9, + 0x8b, 0x83, 0x06, 0x0c, 0x55, 0x27, 0xaf, 0x16, 0xd3, 0xc6, 0x76, 0xf3, 0xaf, 0xcf, 0xe4, 0xbc, 0xa3, 0xf7, 0xa5, + 0xe7, 0xb6, 0xb9, 0x1f, 0x77, 0x75, 0x57, 0xb1, 0x6e, 0x5e, 0x34, 0xc4, 0x8a, 0x22, 0x2e, 0x3e, 0xac, 0x77, 0xb7, + 0x73, 0xbb, 0x75, 0x24, 0xc5, 0x3b, 0x05, 0x77, 0x4a, 0x4a, 0x75, 0xcf, 0x8c, 0xa1, 0x27, 0xec, 0xbd, 0x6c, 0xdc, + 0xff, 0x72, 0xe9, 0xac, 0xbb, 0xe2, 0xae, 0x72, 0xf0, 0xc6, 0xa4, 0x8b, 0x16, 0xec, 0xfa, 0x45, 0xaf, 0xdf, 0x7c, + 0xa1, 0x7e, 0x5a, 0xd1, 0x2d, 0x4a, 0x28, 0xa0, 0x0d, 0x2d, 0x5f, 0x10, 0xef, 0x84, 0xca, 0x46, 0x77, 0xc2, 0xc9, + 0xd3, 0xe2, 0xbe, 0xfa, 0x4e, 0xc6, 0xe0, 0x2f, 0x90, 0xaf, 0xe6, 0x51, 0xf0, 0xf1, 0x9f, 0xc4, 0x2f, 0x2f, 0x8b, + 0xfa, 0xcd, 0x8b, 0xd7, 0x5e, 0x0b, 0x80, 0x69, 0x9d, 0x1f, 0xf1, 0xe2, 0x7b, 0x4b, 0xe7, 0x41, 0x92, 0x3f, 0x62, + 0x3c, 0xfb, 0x28, 0x4b, 0x80, 0x04, 0x58, 0xa5, 0x7a, 0x67, 0x16, 0xc4, 0xe3, 0xfb, 0x30, 0x11, 0x39, 0x03, 0x09, + 0x1b, 0x14, 0x0a, 0xc2, 0xf8, 0x4e, 0x23, 0xc2, 0x7b, 0x14, 0x31, 0x15, 0x5e, 0x76, 0x7d, 0xbf, 0x4a, 0x71, 0xb0, + 0x02, 0xa3, 0x76, 0xfb, 0x2f, 0x26, 0x53, 0x60, 0x4f, 0x1c, 0x4c, 0xd4, 0x15, 0x4e, 0x78, 0xfc, 0xe1, 0xe4, 0xfe, + 0x25, 0x3d, 0x52, 0x55, 0x87, 0x39, 0x32, 0xbe, 0xb6, 0xaa, 0xea, 0xc5, 0xaf, 0xd0, 0xb6, 0x2f, 0x67, 0xa9, 0xb5, + 0x74, 0xd9, 0xab, 0x81, 0x6c, 0xed, 0x6c, 0xa2, 0xba, 0x3b, 0x59, 0x1e, 0x97, 0x1b, 0xc2, 0x10, 0x88, 0x75, 0xee, + 0xf2, 0xc8, 0x25, 0xdb, 0xc7, 0xc2, 0xc5, 0x29, 0xdb, 0xfc, 0xec, 0x59, 0xfa, 0xcb, 0x42, 0x79, 0xca, 0xb7, 0xde, + 0xc2, 0xdb, 0xaf, 0x89, 0x1e, 0xf4, 0x77, 0xd3, 0x26, 0xca, 0x01, 0xd1, 0x81, 0x83, 0xc6, 0xf7, 0xa7, 0xf7, 0xff, + 0xa8, 0x19, 0x52, 0x3d, 0x6b, 0x49, 0x2b, 0x07, 0x7f, 0x48, 0x9c, 0x2d, 0xcd, 0x61, 0x2a, 0x11, 0x24, 0xe3, 0xda, + 0xf4, 0x32, 0x59, 0x7b, 0xd3, 0x76, 0x97, 0x1d, 0x90, 0xb5, 0xe4, 0x14, 0x88, 0x1a, 0xb9, 0xd7, 0x35, 0xdf, 0x42, + 0xa8, 0x93, 0x58, 0xa6, 0xb6, 0x7b, 0x8d, 0x3a, 0x83, 0xb5, 0x04, 0xd0, 0x20, 0xe6, 0x35, 0xfe, 0x37, 0x43, 0x33, + 0xfe, 0xf6, 0xcd, 0x93, 0x83, 0x1b, 0x46, 0x82, 0xa9, 0xf8, 0x28, 0x80, 0xe1, 0x8c, 0xe0, 0x49, 0xbd, 0xbe, 0xf6, + 0x25, 0x06, 0xfa, 0xa1, 0xa4, 0xea, 0xc5, 0xde, 0xcd, 0xce, 0x2b, 0x70, 0x51, 0xda, 0x3f, 0x50, 0x7c, 0x43, 0x9a, + 0x91, 0x5a, 0xd9, 0xab, 0x7b, 0xef, 0xd4, 0x76, 0xd2, 0x6b, 0xc9, 0x82, 0xe6, 0xc0, 0x4b, 0x06, 0xb7, 0x24, 0x67, + 0x60, 0x79, 0x7f, 0x2e, 0x3d, 0xd9, 0x19, 0xf8, 0x44, 0xea, 0x97, 0xfa, 0x4a, 0xdc, 0x2c, 0x09, 0x65, 0x2c, 0x24, + 0xd5, 0xfd, 0x0a, 0x44, 0xaf, 0xff, 0xe8, 0x46, 0x85, 0x86, 0xbd, 0x3a, 0xdb, 0x31, 0x90, 0x46, 0x8c, 0xf6, 0x2e, + 0xb5, 0xde, 0xee, 0xe9, 0x91, 0x31, 0x7d, 0xde, 0xfb, 0xb9, 0xea, 0xdc, 0x91, 0xd9, 0x86, 0x54, 0xff, 0x54, 0xcc, + 0x5a, 0x52, 0x21, 0xdb, 0x8a, 0xed, 0xb4, 0x02, 0x77, 0x1e, 0x4c, 0xde, 0x1d, 0x98, 0xbb, 0x0f, 0x64, 0x0e, 0x63, + 0xad, 0x2b, 0x55, 0x95, 0x1b, 0x5f, 0xc4, 0xd0, 0xef, 0x03, 0xc9, 0x2c, 0xb2, 0x48, 0xaa, 0xc0, 0x16, 0x6a, 0x23, + 0xef, 0xdd, 0xcf, 0xc5, 0xaa, 0xd3, 0x2f, 0x4d, 0x82, 0x74, 0xff, 0x46, 0xe4, 0x9a, 0x19, 0x79, 0xf3, 0xbe, 0xda, + 0x46, 0x30, 0xac, 0xa3, 0x8d, 0x48, 0xa1, 0x9d, 0x2f, 0x19, 0xfe, 0x33, 0x92, 0x77, 0x62, 0xa6, 0x7f, 0x90, 0xce, + 0xac, 0x1f, 0x84, 0xf1, 0x76, 0xbf, 0x40, 0x73, 0xfe, 0xa1, 0x80, 0x67, 0x2f, 0x14, 0x60, 0x01, 0x69, 0xf4, 0x4a, + 0xea, 0x63, 0x4d, 0x50, 0x4e, 0xb8, 0x32, 0x94, 0x6c, 0x94, 0xd7, 0x52, 0x7b, 0x42, 0xfb, 0xa6, 0x64, 0x03, 0x6c, + 0xe2, 0x3a, 0x76, 0xd1, 0xd4, 0xb1, 0xc0, 0x74, 0xb9, 0x7f, 0x71, 0x6c, 0x0f, 0x52, 0xb9, 0x70, 0x01, 0x5f, 0xe8, + 0x02, 0x77, 0x61, 0x38, 0x40, 0x6b, 0x50, 0xff, 0x71, 0xdc, 0x14, 0xff, 0x50, 0x4a, 0x25, 0xb1, 0xc9, 0x42, 0xa9, + 0x50, 0x7b, 0x88, 0x9f, 0x1b, 0xe5, 0x5a, 0x4f, 0x82, 0x6b, 0xa4, 0x08, 0x08, 0x8e, 0x2b, 0x26, 0x71, 0x35, 0xa5, + 0x21, 0xb8, 0x73, 0xf4, 0x99, 0xd7, 0xf2, 0x2b, 0xa1, 0xec, 0xba, 0xc0, 0x67, 0x60, 0x05, 0x18, 0xec, 0x2f, 0xec, + 0x0b, 0x47, 0x17, 0x2d, 0x67, 0xeb, 0xb3, 0x03, 0x27, 0x40, 0x1e, 0x2b, 0x4f, 0x24, 0x61, 0x6b, 0x72, 0xee, 0x4d, + 0x6e, 0xdf, 0x33, 0x85, 0x68, 0x52, 0x44, 0xd5, 0xe3, 0x17, 0xb8, 0x20, 0x2d, 0xa9, 0x64, 0xa5, 0xa0, 0x55, 0xa8, + 0x40, 0xb4, 0xd1, 0xc6, 0xd5, 0xaa, 0xd3, 0xfb, 0xa7, 0x11, 0x9d, 0x97, 0xc6, 0xda, 0x10, 0x43, 0xe0, 0x88, 0xb5, + 0xf5, 0x53, 0x85, 0x8d, 0x37, 0xc9, 0xba, 0xb8, 0xcf, 0x63, 0xfb, 0x35, 0x43, 0x33, 0x12, 0x6f, 0x2a, 0x6f, 0x9b, + 0xe2, 0x61, 0xc1, 0x1b, 0x27, 0x7a, 0xa1, 0x5f, 0xb0, 0x39, 0xe1, 0xf4, 0xd7, 0x75, 0x97, 0xc9, 0xb1, 0xfa, 0xd8, + 0x43, 0x48, 0xb9, 0x50, 0xa3, 0x42, 0xa4, 0xe7, 0xed, 0xd8, 0x5c, 0xb9, 0xc7, 0xd2, 0xe8, 0x1c, 0xd7, 0xa4, 0x24, + 0xdb, 0xcd, 0xf0, 0xd2, 0xa6, 0x82, 0x38, 0x71, 0x77, 0x3f, 0xa8, 0x05, 0xef, 0xb6, 0x21, 0xad, 0x69, 0xfd, 0xfa, + 0x95, 0x3f, 0xbf, 0x71, 0x56, 0x52, 0x2c, 0x92, 0x45, 0xd4, 0x6c, 0xd7, 0x4f, 0xec, 0xf2, 0x67, 0xd2, 0xfb, 0x2c, + 0xbc, 0xc9, 0xda, 0xbf, 0x1e, 0xe1, 0x4b, 0xae, 0x4d, 0x29, 0x92, 0x29, 0xca, 0xdd, 0xbf, 0x49, 0x90, 0x10, 0x19, + 0xfe, 0x42, 0x00, 0xc6, 0xba, 0xa7, 0x55, 0xf3, 0xd1, 0x59, 0x89, 0xb3, 0x0f, 0xbc, 0x06, 0xe0, 0xa2, 0xe0, 0x0b, + 0xa3, 0x34, 0x5a, 0xb1, 0x18, 0x1c, 0x07, 0x9a, 0xca, 0x07, 0x5c, 0xff, 0x30, 0xa3, 0x42, 0x29, 0x36, 0xd4, 0xf7, + 0x13, 0xa7, 0x65, 0x42, 0x40, 0x23, 0x9d, 0x39, 0xb7, 0x51, 0x2b, 0xf0, 0xed, 0x71, 0x3d, 0x1c, 0xe4, 0x7a, 0xda, + 0x21, 0xf8, 0x34, 0x4d, 0x7e, 0x73, 0xc8, 0xe6, 0xf2, 0xa5, 0xd9, 0xef, 0xa5, 0x1b, 0x26, 0x2f, 0x36, 0xf4, 0x56, + 0xd8, 0x08, 0x03, 0x51, 0x8d, 0x2a, 0x68, 0x24, 0x24, 0x61, 0xa7, 0xbd, 0x26, 0x38, 0x9a, 0xd2, 0x62, 0x2a, 0xfc, + 0xa4, 0xae, 0x4f, 0xc6, 0xd7, 0xa2, 0x31, 0xb5, 0x8e, 0x1b, 0xf1, 0x71, 0x39, 0x9f, 0x01, 0xc8, 0x42, 0xc5, 0x73, + 0x4b, 0xa2, 0xcf, 0x28, 0x38, 0x1e, 0x54, 0x59, 0x31, 0xd2, 0x0e, 0x43, 0x11, 0x72, 0x63, 0xa6, 0x71, 0x1c, 0x17, + 0xfe, 0x82, 0xd3, 0x2a, 0x8d, 0x31, 0xaa, 0xbc, 0xb6, 0xe9, 0xa5, 0xf9, 0x3a, 0xa1, 0x3a, 0x97, 0xf1, 0xd7, 0x93, + 0xef, 0xb9, 0x92, 0x29, 0x40, 0x1e, 0x69, 0xbc, 0x61, 0xef, 0x78, 0x06, 0xbc, 0x99, 0xc1, 0x25, 0x01, 0x48, 0x27, + 0xeb, 0x74, 0x6e, 0xc3, 0x23, 0xd2, 0x09, 0x38, 0x3b, 0xaa, 0xf4, 0xe4, 0xca, 0x4a, 0x32, 0xd6, 0x1d, 0xc6, 0x7c, + 0xc9, 0xc6, 0xa5, 0x8d, 0xb7, 0x53, 0x66, 0x9d, 0xa5, 0xcb, 0x94, 0x88, 0x07, 0x95, 0xa4, 0xf1, 0x32, 0xc0, 0x61, + 0x9a, 0x97, 0x6f, 0xd3, 0x5a, 0x7e, 0xcf, 0x70, 0x93, 0x21, 0x15, 0x4d, 0x56, 0x69, 0x76, 0x01, 0x20, 0xc0, 0xb6, + 0x5d, 0x74, 0xd3, 0xe4, 0x08, 0x46, 0xe4, 0x1f, 0xd0, 0xbb, 0xe0, 0x8e, 0xec, 0x1d, 0xb5, 0x3b, 0xb3, 0xc7, 0x41, + 0x80, 0x77, 0x75, 0x4b, 0x76, 0x29, 0x13, 0xdf, 0xc4, 0xd0, 0xf5, 0xab, 0x96, 0x00, 0xb8, 0x01, 0x76, 0x59, 0x12, + 0x75, 0x26, 0x73, 0x81, 0x55, 0x79, 0xcc, 0xc3, 0x54, 0xa6, 0x98, 0xaa, 0x05, 0x5b, 0x82, 0x5c, 0x40, 0xb9, 0xbc, + 0x71, 0xb9, 0xae, 0xaf, 0x02, 0x40, 0xd1, 0xc3, 0x38, 0x2a, 0x26, 0x9e, 0x1b, 0xe9, 0x85, 0xbd, 0xaa, 0x40, 0x61, + 0x7c, 0x6a, 0x4b, 0x72, 0x72, 0x29, 0xfd, 0xc9, 0x64, 0xdb, 0x6a, 0xb6, 0xdb, 0xc9, 0x45, 0x42, 0xd7, 0x92, 0xd8, + 0x42, 0x2e, 0xa9, 0xdb, 0xbb, 0x3a, 0xc4, 0xf2, 0x5e, 0x16, 0x30, 0xda, 0x46, 0x67, 0xdd, 0x55, 0x1f, 0xd6, 0x94, + 0x08, 0x27, 0xcb, 0xc6, 0x7c, 0x27, 0xd6, 0x17, 0xa9, 0x35, 0x06, 0x1a, 0x67, 0xde, 0xfa, 0x25, 0x43, 0xcd, 0x04, + 0x9f, 0x54, 0x2f, 0x96, 0xc5, 0x7c, 0xe6, 0x82, 0xa8, 0xd8, 0x2c, 0xee, 0x5f, 0x6d, 0xba, 0xe0, 0x74, 0x4d, 0xda, + 0x0d, 0xa4, 0x1b, 0x58, 0x34, 0xdc, 0x45, 0x84, 0x45, 0xfb, 0x23, 0x9a, 0x15, 0xcb, 0x0a, 0xa3, 0xc7, 0x4f, 0xe6, + 0xd8, 0x53, 0xc1, 0xb1, 0xb4, 0x40, 0xc2, 0x11, 0xbf, 0x79, 0x8d, 0xd5, 0xa2, 0x6e, 0x65, 0x4c, 0x34, 0x96, 0xa6, + 0xfe, 0x61, 0x21, 0x6d, 0xfb, 0x1a, 0xa8, 0xfe, 0x19, 0x7c, 0x12, 0xdb, 0x19, 0x83, 0xbc, 0xb1, 0x0d, 0x6c, 0xe5, + 0x80, 0x3a, 0x09, 0xa5, 0x27, 0x25, 0xe5, 0x6e, 0x2e, 0x50, 0xaa, 0x34, 0xcd, 0x28, 0xf6, 0xbc, 0x4e, 0x34, 0x5d, + 0xd7, 0x08, 0x27, 0x19, 0x39, 0xd1, 0xe7, 0x8d, 0x82, 0xbc, 0xdd, 0xe6, 0xb2, 0x2f, 0x0d, 0x9c, 0x75, 0xe8, 0x36, + 0x9c, 0xc9, 0x28, 0x69, 0x08, 0x09, 0xda, 0x10, 0x66, 0x6d, 0xb2, 0xd5, 0x22, 0xb4, 0x0d, 0x69, 0x51, 0xf0, 0xc3, + 0xee, 0x1b, 0xcc, 0x23, 0xe8, 0xe9, 0x94, 0xf1, 0x87, 0xd3, 0x6f, 0x2e, 0x1f, 0xee, 0x6c, 0x32, 0x27, 0x02, 0x2d, + 0x3a, 0xcf, 0xa7, 0x87, 0xe2, 0x45, 0x81, 0x20, 0x22, 0x68, 0x0e, 0x6f, 0x09, 0x4e, 0x3e, 0x26, 0xf4, 0x5a, 0xf5, + 0x16, 0x75, 0xf8, 0xc4, 0x83, 0xef, 0xda, 0x3e, 0x21, 0x0e, 0x46, 0x6f, 0xda, 0xf2, 0x28, 0xcd, 0x33, 0x09, 0xf5, + 0xd4, 0x15, 0x03, 0x57, 0x95, 0x8c, 0x1c, 0xbf, 0x59, 0x5f, 0x13, 0x62, 0x45, 0xc0, 0x18, 0x52, 0xbd, 0xc5, 0x18, + 0x1c, 0x32, 0xe6, 0xe5, 0x38, 0x18, 0xd7, 0x6c, 0x8a, 0x2c, 0x6b, 0x43, 0xd9, 0x5d, 0xf9, 0xe9, 0x5c, 0x8c, 0x56, + 0xa1, 0x6c, 0x24, 0x9e, 0xe5, 0x51, 0x8a, 0x71, 0x0f, 0xab, 0x9e, 0x46, 0xc4, 0x96, 0x35, 0x75, 0x3e, 0x21, 0xf4, + 0xd9, 0x83, 0x98, 0xb3, 0x0b, 0x53, 0x16, 0x7a, 0x89, 0xa1, 0x2a, 0xbd, 0x0d, 0x98, 0xbe, 0x15, 0x5b, 0x24, 0xda, + 0x8e, 0x44, 0xa2, 0x98, 0xe0, 0x84, 0x38, 0x6c, 0x45, 0x8e, 0x07, 0xab, 0xbd, 0x83, 0xc9, 0xe8, 0x33, 0x4e, 0x0b, + 0xeb, 0x91, 0x98, 0xfd, 0x31, 0x4e, 0x09, 0x03, 0xce, 0xed, 0x4e, 0x4c, 0x79, 0x37, 0x22, 0x1e, 0x7d, 0x20, 0xd7, + 0x6f, 0xa5, 0x45, 0xb0, 0xc7, 0x13, 0x39, 0x52, 0x15, 0xc5, 0x0a, 0x6e, 0x1f, 0x85, 0x0c, 0x4f, 0x5d, 0x38, 0x9a, + 0xb3, 0x61, 0x3c, 0x10, 0x51, 0x6d, 0x5c, 0xd8, 0xb4, 0x96, 0x81, 0x89, 0xc6, 0x8c, 0xd5, 0xe8, 0xe0, 0x02, 0x5e, + 0xe4, 0xfd, 0xef, 0x0b, 0xa6, 0x69, 0x2d, 0x1f, 0x34, 0x83, 0xfe, 0xbb, 0x32, 0xdb, 0x2c, 0x1f, 0xde, 0xd7, 0xcb, + 0x87, 0xfd, 0x44, 0xce, 0xdc, 0xef, 0xaa, 0xb7, 0x9f, 0xfe, 0x69, 0x21, 0x07, 0xf9, 0xb7, 0xbc, 0x0a, 0x83, 0xab, + 0xad, 0xe3, 0x89, 0x1b, 0x5c, 0x4d, 0x5f, 0x3b, 0xe4, 0xb3, 0x2b, 0x6a, 0xdb, 0x70, 0x91, 0x66, 0x3c, 0xb6, 0x3c, + 0x59, 0x83, 0x15, 0x59, 0x54, 0x2b, 0x58, 0x3b, 0xc9, 0x13, 0xdd, 0xf5, 0xd9, 0x25, 0xb8, 0x27, 0x2f, 0x26, 0x32, + 0x65, 0xf6, 0x01, 0xf8, 0x50, 0x22, 0x7f, 0x62, 0xb7, 0xf0, 0xdf, 0x51, 0x05, 0xdd, 0x41, 0xc1, 0x50, 0x6b, 0x49, + 0xd8, 0xe6, 0x0b, 0x25, 0xbf, 0x96, 0x08, 0x7c, 0x51, 0xbd, 0x85, 0x75, 0x43, 0xca, 0x9f, 0x58, 0x6e, 0x4f, 0xa9, + 0x13, 0x4d, 0xa3, 0x3b, 0x79, 0x1a, 0x7e, 0xe9, 0x92, 0xe0, 0xb2, 0x4d, 0xfd, 0xbf, 0xbe, 0xff, 0xaf, 0xd7, 0x09, + 0x26, 0x21, 0xef, 0x20, 0x1e, 0x2e, 0x5f, 0x0c, 0xae, 0x3a, 0xd2, 0xf9, 0x66, 0x1f, 0xbe, 0x89, 0x86, 0xe5, 0x61, + 0xfd, 0xbc, 0xf7, 0x17, 0x5d, 0x7e, 0x6f, 0xa2, 0xef, 0x60, 0xdb, 0xb4, 0xa1, 0xb4, 0x3d, 0xa4, 0x01, 0x4b, 0x8d, + 0x0b, 0x9a, 0x55, 0xf1, 0xd8, 0x14, 0x16, 0xab, 0x7b, 0x7b, 0x4d, 0x9e, 0x72, 0x6c, 0xfd, 0x87, 0xa0, 0x83, 0xcc, + 0xf1, 0x68, 0xb8, 0x2c, 0xcf, 0xd2, 0x2c, 0xd6, 0x31, 0xe8, 0xee, 0x9d, 0x50, 0x7b, 0xb1, 0x18, 0x5a, 0x1b, 0xb5, + 0x45, 0x92, 0x48, 0xe3, 0x5d, 0x5d, 0x6c, 0xea, 0x21, 0x74, 0x69, 0xeb, 0x34, 0x6d, 0x12, 0xc7, 0x38, 0xd9, 0x96, + 0xbd, 0x06, 0xe8, 0x95, 0xbe, 0xe8, 0x2f, 0x58, 0x7a, 0x6d, 0xbf, 0xd6, 0x47, 0x8c, 0x9b, 0x0d, 0xbc, 0x3f, 0x3a, + 0x65, 0xe2, 0xe2, 0xd0, 0xd8, 0xf9, 0x16, 0x27, 0x0e, 0x7b, 0x7e, 0x8d, 0x4b, 0xaa, 0xa9, 0x97, 0x48, 0x1b, 0xc6, + 0x6a, 0x70, 0x62, 0xe9, 0x5f, 0xdb, 0x58, 0x3c, 0x48, 0x8e, 0x48, 0x65, 0x27, 0x33, 0xf5, 0x72, 0xb4, 0xf0, 0xb7, + 0xae, 0xd6, 0xf5, 0x87, 0xf8, 0xe6, 0x1f, 0x88, 0x9d, 0xa8, 0x9d, 0x5e, 0x34, 0x8a, 0x0c, 0x21, 0xd3, 0x53, 0xfc, + 0x8b, 0x5b, 0x28, 0xc3, 0x69, 0xa2, 0xb3, 0x51, 0xee, 0xed, 0x9d, 0x23, 0x3f, 0x24, 0xbc, 0x71, 0xe7, 0x72, 0x59, + 0x61, 0x60, 0xda, 0x01, 0x36, 0x50, 0x41, 0xc6, 0x81, 0xa5, 0xf8, 0x09, 0x66, 0x97, 0x21, 0xca, 0x6e, 0x99, 0x11, + 0x2f, 0x6d, 0xa7, 0xd2, 0x18, 0xb2, 0xf3, 0x22, 0x77, 0xf1, 0x98, 0x38, 0x36, 0x52, 0x1b, 0x9f, 0x14, 0x10, 0x8e, + 0xf5, 0x61, 0xc8, 0xa6, 0xdb, 0x29, 0x79, 0x6a, 0x39, 0x05, 0x9a, 0x47, 0x7e, 0x8f, 0x88, 0x8e, 0xc6, 0xd6, 0x69, + 0x50, 0x7b, 0x16, 0x1f, 0x2d, 0x17, 0xbe, 0x10, 0x2d, 0xef, 0x02, 0x5b, 0x33, 0xe4, 0x05, 0xab, 0xf7, 0x29, 0x10, + 0xe4, 0x36, 0x6c, 0x7f, 0xcf, 0x97, 0xee, 0xef, 0xac, 0x61, 0x88, 0x79, 0xd0, 0x64, 0xcc, 0xd7, 0x1c, 0x56, 0x84, + 0x4d, 0x59, 0xaf, 0x84, 0x7d, 0x1d, 0x9c, 0xba, 0x1e, 0x4e, 0x52, 0xe9, 0xb9, 0x1a, 0xcd, 0xbb, 0x74, 0xa4, 0x34, + 0x65, 0x8a, 0x36, 0xa6, 0x77, 0x7d, 0x4e, 0x36, 0x47, 0x57, 0x74, 0x3c, 0xeb, 0xa0, 0x14, 0x1e, 0x3e, 0xb5, 0xc1, + 0xa9, 0x7b, 0x46, 0x2f, 0xe4, 0xd7, 0x20, 0xbd, 0xa6, 0x45, 0x15, 0xf4, 0x69, 0xf5, 0x83, 0x17, 0x1f, 0xbf, 0x5b, + 0x25, 0xd0, 0xd8, 0xec, 0x93, 0x0d, 0xc1, 0x59, 0x1e, 0x80, 0x1f, 0x16, 0xf8, 0xff, 0x80, 0x3e, 0x20, 0x66, 0x73, + 0xd3, 0xfe, 0x30, 0x87, 0xf2, 0x4d, 0xf3, 0xf5, 0x42, 0x98, 0x16, 0x9d, 0x1f, 0x7c, 0xa8, 0x1b, 0x04, 0xd8, 0x64, + 0xcf, 0xff, 0x2b, 0xc8, 0x01, 0x82, 0x09, 0xe7, 0xef, 0xe3, 0x7a, 0x38, 0xbf, 0xd1, 0xcf, 0x11, 0x99, 0x3b, 0xdc, + 0xcc, 0xde, 0x4d, 0xbb, 0xf4, 0xaa, 0x2c, 0x36, 0x92, 0xd7, 0xc2, 0xa5, 0x8d, 0xcb, 0x69, 0x1b, 0xd1, 0x92, 0x2d, + 0x12, 0x2c, 0x7c, 0x4b, 0x00, 0x70, 0xa4, 0x7b, 0xa8, 0x6d, 0xf3, 0xbf, 0x28, 0xb6, 0x18, 0x2b, 0xb8, 0x9d, 0xd6, + 0xae, 0xae, 0xfd, 0xd0, 0x76, 0x9b, 0x65, 0x0c, 0x30, 0x7a, 0xb0, 0x33, 0x57, 0x19, 0x65, 0xb9, 0x43, 0x9c, 0x3d, + 0x5c, 0x19, 0xb5, 0xcb, 0x98, 0x70, 0x54, 0xeb, 0x66, 0xb5, 0xa7, 0x02, 0x02, 0x35, 0x62, 0xb1, 0x83, 0xae, 0xcc, + 0x8a, 0x48, 0x3a, 0x7b, 0x6f, 0xc6, 0xf0, 0x6e, 0x83, 0xc5, 0x65, 0xcc, 0x88, 0xe4, 0x8d, 0x81, 0x36, 0xb7, 0xe2, + 0xb1, 0x77, 0x7a, 0xf3, 0xe0, 0xfe, 0xf6, 0xe6, 0xf2, 0xe6, 0x76, 0x89, 0xb7, 0x89, 0x2e, 0xd5, 0x1a, 0x99, 0x53, + 0x7b, 0xbe, 0x96, 0x8c, 0x76, 0xc8, 0xf7, 0xb6, 0xd5, 0xba, 0x84, 0x16, 0x49, 0x80, 0x48, 0x2b, 0x24, 0xab, 0xea, + 0x94, 0x01, 0x0e, 0x9d, 0xa6, 0x61, 0xdb, 0xe3, 0x5e, 0x52, 0x28, 0xd8, 0xca, 0x84, 0xa3, 0x3c, 0x3b, 0xf5, 0x54, + 0x23, 0x73, 0xf6, 0x4c, 0x70, 0x5d, 0x2c, 0x24, 0x22, 0xcf, 0xd7, 0x9c, 0x2c, 0x1e, 0x01, 0xcc, 0x9c, 0xdf, 0x4f, + 0xf3, 0x14, 0x97, 0x38, 0x6c, 0xaa, 0x51, 0x46, 0x5f, 0x6f, 0x09, 0xa1, 0xa1, 0x78, 0x39, 0x14, 0xf8, 0x7a, 0xc2, + 0xf5, 0x5d, 0xa4, 0x23, 0x78, 0x42, 0xc7, 0x49, 0xf2, 0x4b, 0x43, 0x66, 0xdf, 0x6f, 0x9a, 0xc9, 0x36, 0xea, 0x8a, + 0xbe, 0x6e, 0xc9, 0x5f, 0x4f, 0xc6, 0x69, 0x6d, 0x70, 0xe9, 0xf8, 0x6f, 0xa0, 0x7b, 0x41, 0x8c, 0x83, 0x85, 0x33, + 0x88, 0xa3, 0xf0, 0x2b, 0xb6, 0x20, 0x2f, 0x3a, 0xef, 0xf9, 0x73, 0x02, 0x70, 0xb9, 0x5b, 0x06, 0x17, 0x26, 0x96, + 0x79, 0xac, 0xcb, 0x18, 0xd9, 0xc9, 0x42, 0x4e, 0x8d, 0xda, 0x57, 0x44, 0xdb, 0x9a, 0x09, 0xec, 0x47, 0x7c, 0x79, + 0x9c, 0x4a, 0x5c, 0x9b, 0x31, 0x8b, 0x8d, 0x18, 0xbc, 0xa9, 0x3c, 0x28, 0x36, 0x98, 0x85, 0xe7, 0xfb, 0xd6, 0x10, + 0x52, 0x6b, 0xd2, 0x61, 0xb0, 0x53, 0x5e, 0xc4, 0x36, 0x70, 0xca, 0x2e, 0x6e, 0xc7, 0x5a, 0x8c, 0x5f, 0xd7, 0x78, + 0xc5, 0x58, 0x47, 0x2d, 0x38, 0xce, 0x7b, 0xcb, 0x61, 0x9b, 0x60, 0x40, 0xff, 0xb1, 0x13, 0x34, 0xf3, 0xca, 0x9d, + 0x6c, 0x1d, 0x10, 0xe4, 0x6c, 0xc8, 0x12, 0x41, 0x0d, 0xbf, 0x26, 0x9b, 0x36, 0x96, 0x17, 0x9d, 0xe3, 0xfb, 0x8c, + 0x69, 0x47, 0xfb, 0x2c, 0x72, 0x11, 0x25, 0xe3, 0x57, 0x12, 0xa4, 0x73, 0x65, 0x37, 0x72, 0x77, 0x23, 0xf2, 0xa0, + 0x4d, 0x49, 0xe8, 0xad, 0x3d, 0x03, 0x37, 0x3c, 0x37, 0x5f, 0xa9, 0x9a, 0xa3, 0x2c, 0x26, 0x02, 0x83, 0x22, 0x8c, + 0x84, 0xf5, 0x57, 0xff, 0x2b, 0x70, 0x50, 0x77, 0x7c, 0x67, 0xbd, 0xa0, 0xe9, 0x01, 0xbb, 0x1b, 0x75, 0x1d, 0x4a, + 0xab, 0x04, 0x05, 0x11, 0x32, 0x17, 0x86, 0x49, 0xdc, 0xbf, 0xef, 0xde, 0xdd, 0xfd, 0xfe, 0x58, 0x94, 0x5d, 0xdd, + 0x2d, 0xf6, 0x63, 0x4b, 0x3e, 0x9b, 0xb1, 0x91, 0xf9, 0x6a, 0xf0, 0x81, 0x8e, 0x49, 0xb7, 0x40, 0xfe, 0x21, 0xb3, + 0xe7, 0x61, 0x9b, 0x41, 0x23, 0xd1, 0xb5, 0x43, 0x32, 0x20, 0x07, 0x3a, 0xe4, 0x93, 0x0d, 0x3c, 0x97, 0x47, 0xdb, + 0xbc, 0xbb, 0xbc, 0xfe, 0x73, 0xb9, 0xf7, 0xa1, 0x2b, 0xea, 0x83, 0xc5, 0x9a, 0x59, 0xfe, 0xce, 0xc9, 0x22, 0x3b, + 0x70, 0xdb, 0xcd, 0x97, 0xe1, 0x14, 0xaf, 0x66, 0xcb, 0x7f, 0xf0, 0xff, 0xdb, 0xe9, 0xc2, 0x9b, 0x3d, 0xe9, 0x44, + 0xe3, 0x98, 0xe3, 0x96, 0xf7, 0xec, 0x4c, 0xbf, 0x6b, 0x33, 0x13, 0xd2, 0x83, 0x51, 0x34, 0xdb, 0x25, 0x9d, 0xc0, + 0xa8, 0x2e, 0x79, 0xb8, 0xb1, 0x4d, 0x29, 0x53, 0x26, 0x33, 0xd2, 0x42, 0x25, 0x73, 0x2b, 0xd4, 0xb9, 0xa0, 0x48, + 0xf3, 0x85, 0x01, 0x12, 0x75, 0x9b, 0xd3, 0xda, 0x95, 0x38, 0xcd, 0xfb, 0xe6, 0xd8, 0xce, 0xc8, 0x16, 0x25, 0xa0, + 0x4d, 0x99, 0x13, 0x9a, 0x4f, 0x9a, 0x42, 0xdd, 0xdd, 0xce, 0x74, 0x46, 0x6f, 0x93, 0x56, 0x75, 0xda, 0xd7, 0x77, + 0xfd, 0x67, 0x6b, 0xe4, 0x3d, 0x4d, 0x5a, 0xdb, 0x82, 0x74, 0x46, 0x72, 0x6a, 0x3a, 0x9f, 0x06, 0xca, 0xd0, 0x16, + 0x1e, 0x67, 0xbe, 0xf5, 0x22, 0x60, 0x4d, 0x96, 0xcc, 0xa6, 0xe8, 0x6d, 0xa9, 0xa8, 0x5b, 0xec, 0xd9, 0xbd, 0x93, + 0xc9, 0xda, 0xde, 0x1e, 0x10, 0x99, 0x62, 0x58, 0x7b, 0x44, 0xd8, 0x2e, 0xa2, 0x77, 0x00, 0xc7, 0x7d, 0xd2, 0x73, + 0xf8, 0xd4, 0xc8, 0xd7, 0x45, 0xf0, 0xa8, 0x94, 0x36, 0x3f, 0x38, 0x7b, 0xd1, 0x1d, 0x1b, 0x8c, 0x97, 0x0e, 0xb7, + 0xa0, 0xe6, 0xba, 0x6c, 0xba, 0xc6, 0xdd, 0xfd, 0xdf, 0xfe, 0xb6, 0xb5, 0xf0, 0x07, 0x8e, 0x0f, 0x32, 0xbb, 0xa1, + 0xbb, 0xb7, 0xfc, 0xa2, 0x8b, 0xb9, 0xf8, 0xb2, 0x9f, 0x66, 0x67, 0x46, 0xb9, 0x29, 0xc8, 0x4e, 0x45, 0xda, 0x63, + 0x12, 0x15, 0x03, 0xd8, 0xd3, 0x4c, 0x96, 0x64, 0x40, 0x0d, 0xab, 0x57, 0xdf, 0xd2, 0xa9, 0x3b, 0x35, 0x67, 0x6a, + 0xcf, 0x34, 0xf6, 0xb9, 0xf0, 0x88, 0xdd, 0x17, 0x6b, 0xd7, 0x69, 0x6b, 0x98, 0x82, 0xd3, 0x8d, 0x2f, 0xfe, 0xf8, + 0xcb, 0x86, 0x40, 0x8d, 0x51, 0xae, 0xf9, 0xaf, 0xb5, 0x03, 0x08, 0xde, 0xdf, 0x45, 0x98, 0x0b, 0xc8, 0xac, 0xba, + 0x7a, 0xaa, 0xf7, 0x23, 0xdb, 0xcd, 0x43, 0x11, 0x3b, 0x67, 0xbb, 0x17, 0x4f, 0x01, 0x14, 0x99, 0x25, 0x85, 0x9c, + 0xab, 0x36, 0xf4, 0xd2, 0x78, 0x97, 0x1e, 0xf6, 0xd3, 0x27, 0x98, 0x93, 0x43, 0x98, 0x3b, 0x08, 0x9a, 0xcd, 0x20, + 0x81, 0x14, 0xb9, 0x20, 0x66, 0x3f, 0x07, 0x47, 0x58, 0x23, 0x95, 0xc1, 0x34, 0x32, 0x46, 0xbb, 0xe7, 0xca, 0x58, + 0x30, 0x4f, 0x7b, 0x5f, 0xe9, 0xe2, 0xae, 0x77, 0xa0, 0x3a, 0x7b, 0x48, 0xca, 0xcd, 0xfa, 0x02, 0x23, 0xa0, 0xd3, + 0x83, 0x56, 0x3f, 0xfd, 0x39, 0x85, 0x6b, 0xd8, 0x2b, 0xbb, 0x8d, 0x95, 0xe8, 0x0e, 0x20, 0x07, 0xe2, 0x12, 0x4e, + 0x59, 0x7b, 0x9e, 0xfa, 0x77, 0xbf, 0xfe, 0x1e, 0xf9, 0x8b, 0xf3, 0x72, 0xe5, 0x87, 0x95, 0x6f, 0x6d, 0x61, 0x0b, + 0x94, 0x5e, 0xe3, 0xde, 0x2e, 0x31, 0x8d, 0x63, 0x69, 0xfa, 0x96, 0xf3, 0x89, 0x5e, 0xe1, 0x89, 0x0d, 0x54, 0x22, + 0x3c, 0xe2, 0x4a, 0x79, 0x68, 0xa3, 0xd9, 0xec, 0xfa, 0x6e, 0xee, 0x02, 0xd7, 0xff, 0xc2, 0x17, 0xd6, 0xfa, 0xed, + 0x7b, 0x1c, 0x26, 0x23, 0x82, 0x37, 0xfa, 0xa3, 0x30, 0x31, 0x42, 0xc9, 0xd9, 0xc9, 0xb0, 0xff, 0x30, 0x3a, 0x7a, + 0xe8, 0x3c, 0xfa, 0x19, 0x13, 0xa5, 0x66, 0x7c, 0xe7, 0x0f, 0x73, 0x39, 0x93, 0xe7, 0x52, 0xb1, 0x40, 0x6b, 0x12, + 0x01, 0x51, 0x31, 0x2a, 0x3a, 0x4c, 0x4e, 0xe3, 0x37, 0x94, 0xe7, 0x0d, 0x0b, 0xe2, 0x22, 0x08, 0x8a, 0x2f, 0x50, + 0xf6, 0x67, 0xd7, 0x0f, 0x5c, 0xdd, 0xb0, 0x63, 0x30, 0x43, 0x3d, 0xd1, 0xd0, 0x6a, 0x42, 0x90, 0xb1, 0xb5, 0x52, + 0x05, 0x01, 0x4a, 0x47, 0x42, 0x8a, 0x41, 0xcd, 0xac, 0xe5, 0x31, 0xe9, 0xaf, 0x5b, 0x06, 0xef, 0x8d, 0x8a, 0xe3, + 0x32, 0x5a, 0xb7, 0x6d, 0xd5, 0x70, 0x6d, 0xca, 0x38, 0x7a, 0x01, 0xa6, 0xc3, 0xce, 0x49, 0x06, 0x1a, 0x46, 0xfc, + 0xaf, 0x81, 0xe1, 0x42, 0x56, 0x9b, 0x6e, 0x17, 0x87, 0x76, 0xed, 0xc6, 0x7c, 0x22, 0xd8, 0x5f, 0x36, 0x4c, 0x23, + 0xcf, 0x7b, 0xfc, 0x32, 0xd8, 0x12, 0xef, 0x59, 0xf8, 0x69, 0x1f, 0x94, 0x9d, 0xaf, 0xc1, 0xca, 0xf8, 0xd9, 0x7c, + 0xbe, 0xfb, 0x72, 0xf5, 0x1d, 0x86, 0x34, 0x17, 0x05, 0x62, 0xcd, 0xeb, 0xe7, 0xf8, 0x54, 0xba, 0x1c, 0x27, 0xc2, + 0xfa, 0x44, 0x34, 0x6e, 0xd6, 0x95, 0x0b, 0x3f, 0xf2, 0xb6, 0xc2, 0x7d, 0xfd, 0x06, 0x3b, 0x28, 0x9f, 0x7c, 0xbf, + 0x1b, 0x0b, 0xc1, 0xd3, 0xa6, 0x24, 0xe4, 0x39, 0xd0, 0x5b, 0xb7, 0x5b, 0x45, 0x4b, 0xbf, 0x96, 0x87, 0x66, 0x99, + 0x87, 0xf3, 0xc9, 0x98, 0x80, 0x88, 0xe0, 0x40, 0xce, 0x42, 0xd1, 0xf4, 0x22, 0x4c, 0xba, 0x08, 0x3e, 0x35, 0x72, + 0x8e, 0x38, 0x9c, 0xc6, 0xfd, 0xae, 0x30, 0xfd, 0x4d, 0x9e, 0x74, 0x19, 0xfb, 0xe9, 0xef, 0xdb, 0x75, 0x11, 0xd2, + 0xef, 0x79, 0x36, 0xfb, 0x2f, 0x34, 0x42, 0xfe, 0x36, 0x8a, 0x8d, 0xc7, 0x28, 0x6f, 0x14, 0x95, 0x08, 0x11, 0xed, + 0x92, 0x48, 0x98, 0xcb, 0xfb, 0x55, 0xc2, 0xc7, 0xaf, 0xe8, 0x85, 0x33, 0xc7, 0x40, 0xa3, 0x8b, 0x1e, 0x4f, 0xd8, + 0xd8, 0xfd, 0x79, 0x1a, 0x63, 0x81, 0x35, 0xc3, 0x9f, 0x05, 0x80, 0x74, 0xda, 0xad, 0x00, 0xd1, 0x86, 0x26, 0xc8, + 0x70, 0x5d, 0xe7, 0x1a, 0xd6, 0x33, 0x87, 0xe0, 0xf3, 0x46, 0xc8, 0x0d, 0xf1, 0x1c, 0x82, 0x82, 0x7b, 0x70, 0x60, + 0x89, 0xe2, 0x9f, 0x59, 0x47, 0x3d, 0x77, 0x98, 0x58, 0xd2, 0x21, 0x0d, 0x89, 0x84, 0x2c, 0xd7, 0xdd, 0xab, 0x51, + 0x01, 0x3e, 0x66, 0xb2, 0x16, 0x54, 0x3c, 0x9b, 0x4d, 0x7e, 0x35, 0xbf, 0x13, 0xa5, 0xd7, 0xd1, 0x91, 0x36, 0x79, + 0x37, 0x58, 0x82, 0xce, 0xdf, 0x19, 0x05, 0x40, 0x2f, 0x55, 0x5a, 0x05, 0x66, 0x42, 0xd8, 0xc4, 0x86, 0xef, 0x18, + 0x26, 0xa3, 0xcd, 0x9c, 0xdf, 0x64, 0x36, 0x0b, 0x13, 0xc8, 0x60, 0x68, 0x15, 0x40, 0x96, 0xed, 0x11, 0xee, 0x52, + 0xda, 0x07, 0xd4, 0xbb, 0xb8, 0xec, 0x73, 0xf4, 0x39, 0x8d, 0x24, 0xec, 0x5e, 0xaa, 0x31, 0x41, 0x5c, 0x45, 0x4b, + 0xcc, 0xb1, 0xb5, 0xe4, 0xd0, 0x42, 0xf4, 0x8e, 0xd0, 0x61, 0x77, 0x97, 0x19, 0x6c, 0x95, 0xd8, 0x7f, 0x78, 0xac, + 0x64, 0x0e, 0x9e, 0xa5, 0x67, 0xc2, 0xd6, 0x88, 0x1d, 0x27, 0x0d, 0x17, 0x24, 0x88, 0x58, 0x08, 0x4f, 0xe7, 0x03, + 0x71, 0x46, 0xb5, 0x88, 0xff, 0xa3, 0xe3, 0x04, 0xfa, 0x4a, 0xa2, 0x88, 0xec, 0x46, 0xa7, 0xfd, 0x1c, 0x0a, 0x18, + 0x89, 0x23, 0x18, 0x85, 0x9f, 0xa1, 0xa4, 0xbb, 0x4c, 0x18, 0xa0, 0x5c, 0x48, 0xec, 0xf0, 0x84, 0x94, 0x98, 0x12, + 0x6a, 0xa4, 0x07, 0x09, 0xc9, 0xcb, 0x22, 0x00, 0x75, 0x08, 0xda, 0xa1, 0xf9, 0x2b, 0x43, 0x03, 0x0f, 0x2e, 0x5f, + 0xa1, 0x24, 0x32, 0x39, 0x8f, 0x51, 0x48, 0x72, 0xeb, 0xbc, 0xcb, 0x96, 0xb4, 0xb0, 0xbf, 0xd5, 0x28, 0xaa, 0x22, + 0x59, 0xdd, 0xcb, 0x1a, 0xe1, 0xd9, 0x9a, 0x49, 0xb0, 0x3e, 0xb9, 0x8e, 0xb5, 0x90, 0x93, 0x09, 0x4c, 0x8b, 0xf6, + 0x6f, 0xab, 0xe4, 0x22, 0xff, 0x4a, 0xcf, 0xe6, 0x85, 0xdf, 0x85, 0xae, 0x7a, 0xa9, 0x3f, 0x93, 0x76, 0xd0, 0x99, + 0x05, 0x47, 0x6a, 0x99, 0x8d, 0x3b, 0xe3, 0xf3, 0xa2, 0x1b, 0xd6, 0x5f, 0x26, 0x55, 0x12, 0xbd, 0xf0, 0x6b, 0x68, + 0x56, 0x61, 0x41, 0xf9, 0x0c, 0x62, 0x5e, 0x23, 0x9a, 0x8d, 0x36, 0x8c, 0x14, 0xe0, 0xc5, 0xe7, 0xe5, 0x39, 0x77, + 0xa0, 0x32, 0x2a, 0xcc, 0xe2, 0x52, 0x49, 0xf0, 0xbd, 0x70, 0xec, 0xd0, 0x3e, 0xd3, 0xa6, 0xc8, 0xde, 0x97, 0x40, + 0x27, 0x8f, 0x68, 0x03, 0x06, 0xee, 0x10, 0x6a, 0x52, 0xa0, 0xb3, 0x71, 0xb8, 0x45, 0xed, 0x2b, 0x33, 0xba, 0x2a, + 0x45, 0xd1, 0x3c, 0xcd, 0x18, 0xa4, 0xba, 0x55, 0x0b, 0x19, 0x19, 0xed, 0xa0, 0xcb, 0xe8, 0xa0, 0x84, 0x8f, 0xe5, + 0xac, 0xf0, 0x78, 0xc8, 0x70, 0x61, 0x92, 0x6c, 0x80, 0x4f, 0x8f, 0xfe, 0xef, 0x0f, 0xce, 0xa9, 0xa5, 0xe3, 0x91, + 0xbc, 0x62, 0x76, 0xc4, 0xd2, 0x4c, 0x41, 0xea, 0x72, 0x92, 0x22, 0x75, 0x8b, 0xa9, 0x65, 0x71, 0xb0, 0x1c, 0x55, + 0x44, 0x9d, 0xde, 0x9e, 0x0a, 0x0a, 0x07, 0x06, 0x2d, 0x8c, 0x34, 0x31, 0xdf, 0x2c, 0x59, 0x7b, 0xa5, 0xe8, 0xde, + 0xc9, 0x08, 0x55, 0xaa, 0x1b, 0x5e, 0xb9, 0x6c, 0xf0, 0xda, 0xdc, 0x7f, 0x90, 0xbf, 0x8b, 0x25, 0xb7, 0x72, 0x0c, + 0x66, 0x23, 0xcc, 0x89, 0xe8, 0xcd, 0x6b, 0x25, 0xe3, 0x6d, 0x5f, 0xcb, 0x70, 0x40, 0xe9, 0x58, 0x9a, 0xa5, 0xab, + 0x66, 0xe7, 0x9a, 0x5f, 0x42, 0x2e, 0xd8, 0x81, 0x98, 0x74, 0x66, 0xad, 0x16, 0xe6, 0xd7, 0x5e, 0x79, 0xe6, 0x48, + 0xcf, 0x49, 0xd0, 0x70, 0xbb, 0xc8, 0x5a, 0x83, 0x58, 0x6f, 0x99, 0xd3, 0x61, 0x4b, 0x5e, 0xbb, 0xb0, 0x29, 0x86, + 0xe1, 0xf8, 0xcc, 0xec, 0x13, 0xcc, 0xf6, 0x4c, 0xb4, 0xc7, 0xfe, 0x67, 0x36, 0x0b, 0x6d, 0x1a, 0x12, 0xae, 0xb8, + 0xf2, 0x41, 0x8a, 0xab, 0x89, 0x3c, 0x9c, 0xc7, 0x8c, 0xde, 0x5c, 0x71, 0x1d, 0x73, 0x7b, 0xb2, 0x17, 0x84, 0x99, + 0x03, 0xfb, 0x6e, 0xfc, 0x30, 0xaa, 0x12, 0x67, 0x52, 0x96, 0x2d, 0xc5, 0x52, 0x0c, 0xf2, 0xbc, 0x0e, 0x71, 0x28, + 0x95, 0x0b, 0x62, 0x57, 0x24, 0xb2, 0xed, 0x59, 0xb2, 0x78, 0xef, 0xfa, 0x69, 0x05, 0xd5, 0x4b, 0x7c, 0x24, 0xbb, + 0x3d, 0x13, 0xda, 0x72, 0x0b, 0xb2, 0x83, 0xae, 0x83, 0x3b, 0xd2, 0xa4, 0x04, 0x6f, 0x8a, 0x32, 0xfa, 0x4b, 0x1d, + 0x29, 0x15, 0x2d, 0xe6, 0x82, 0x99, 0x89, 0x14, 0xb3, 0xb5, 0x4d, 0x42, 0x80, 0x34, 0x49, 0x7b, 0x89, 0x2c, 0x6a, + 0x9e, 0x03, 0x86, 0x6d, 0x61, 0xc8, 0x3d, 0x5f, 0x02, 0x8c, 0xfa, 0x3e, 0x0f, 0x27, 0x73, 0x84, 0x4d, 0xa2, 0xe4, + 0xef, 0xb5, 0xb6, 0x5d, 0xc3, 0xd6, 0xd1, 0x3f, 0x34, 0x84, 0xaf, 0xa6, 0xb2, 0x86, 0x25, 0xcc, 0xaa, 0x10, 0xde, + 0x2a, 0x0d, 0x50, 0xa4, 0x2c, 0xeb, 0xc3, 0x92, 0x80, 0x09, 0x93, 0x82, 0x76, 0x88, 0xe5, 0x2a, 0x8d, 0xd9, 0x69, + 0x11, 0x5b, 0x73, 0x2f, 0x5b, 0x1c, 0x7f, 0xf5, 0xfb, 0x17, 0x47, 0xa4, 0x72, 0x3b, 0x7f, 0xed, 0x20, 0x3b, 0x60, + 0x64, 0xa1, 0x3f, 0x5b, 0x76, 0x74, 0xee, 0xbf, 0x9e, 0xe2, 0x77, 0x09, 0xfc, 0x3d, 0x72, 0x1c, 0x88, 0x87, 0xdc, + 0xb5, 0x9e, 0x0d, 0x54, 0xf7, 0xb8, 0xac, 0xee, 0x7b, 0xe5, 0x1c, 0xa9, 0x70, 0x12, 0x22, 0xdd, 0xe5, 0xb0, 0x04, + 0xab, 0xf7, 0xd7, 0x90, 0x6c, 0x0a, 0xa6, 0x89, 0xc2, 0x46, 0x59, 0xf3, 0xd5, 0x61, 0xb8, 0xd8, 0x60, 0x05, 0x97, + 0x70, 0x30, 0xf4, 0xda, 0x9b, 0x39, 0xbd, 0xd5, 0xec, 0x8e, 0x41, 0x13, 0xd9, 0x74, 0x77, 0x90, 0x8a, 0xed, 0x0d, + 0x09, 0x4f, 0xff, 0x73, 0x23, 0x07, 0x04, 0xc0, 0xd2, 0xac, 0x90, 0x64, 0xf0, 0x55, 0xce, 0x49, 0x26, 0x53, 0x4b, + 0xcd, 0x6e, 0x2b, 0x05, 0x92, 0xbd, 0xf0, 0xdf, 0xa2, 0xba, 0x19, 0xed, 0xa7, 0xe4, 0x0e, 0xfa, 0x26, 0x3b, 0x34, + 0xf0, 0xc6, 0x9c, 0xf6, 0xde, 0xd0, 0xc4, 0xe2, 0x2b, 0x80, 0x88, 0xc3, 0x01, 0x99, 0x78, 0xc6, 0xc2, 0x12, 0x90, + 0x4e, 0xf5, 0x30, 0x88, 0x09, 0x57, 0x91, 0x16, 0x9c, 0x99, 0x7c, 0xf7, 0x0e, 0x53, 0xe4, 0xd3, 0x3e, 0x83, 0xc6, + 0xec, 0xa0, 0x3a, 0x5d, 0x7b, 0x45, 0x87, 0x7e, 0x9d, 0x39, 0x4b, 0x24, 0x3d, 0xe9, 0xcb, 0x8d, 0x63, 0x99, 0x20, + 0x2d, 0x62, 0xca, 0x67, 0x2a, 0xe8, 0x73, 0xa6, 0xb7, 0x7d, 0x13, 0x78, 0x73, 0xd4, 0xb4, 0x32, 0x2a, 0x07, 0xb8, + 0x49, 0xba, 0x29, 0x83, 0x51, 0x91, 0xac, 0x87, 0x01, 0x2a, 0x41, 0x4e, 0x74, 0x1a, 0x1b, 0x6a, 0x8f, 0xc3, 0x20, + 0x71, 0x1b, 0x50, 0xef, 0x35, 0x33, 0x3a, 0x1a, 0xdd, 0xe7, 0xbf, 0xf0, 0xff, 0xc3, 0xf7, 0x7f, 0x16, 0x8d, 0x93, + 0x5e, 0xa8, 0xac, 0xb4, 0x40, 0xaa, 0x19, 0x81, 0x7e, 0x8f, 0x3b, 0xaf, 0xee, 0x61, 0x85, 0xd1, 0xa5, 0x9d, 0xdb, + 0xee, 0xf4, 0xb8, 0x7f, 0x7d, 0x0a, 0x3f, 0x7f, 0xf7, 0x75, 0xcd, 0xaf, 0xf4, 0x5e, 0x9e, 0x29, 0x87, 0xae, 0x71, + 0x23, 0x92, 0x04, 0xc6, 0xab, 0x6b, 0x14, 0x5a, 0x81, 0x2c, 0xbf, 0x40, 0xc1, 0xc7, 0xb7, 0x46, 0xbb, 0x4f, 0xbb, + 0x22, 0xc8, 0x84, 0xbc, 0x55, 0x10, 0xd6, 0x36, 0x1c, 0x0f, 0x6c, 0x16, 0x5d, 0xd0, 0xfb, 0x1d, 0xba, 0x86, 0x9f, + 0x32, 0x5f, 0x5e, 0xcd, 0x05, 0xdf, 0xe0, 0x74, 0x02, 0xba, 0xe5, 0xce, 0x7b, 0x15, 0xd8, 0x21, 0x67, 0xfd, 0xc8, + 0xe8, 0xde, 0x29, 0x64, 0xa3, 0xc4, 0xb4, 0x63, 0xa1, 0xed, 0xda, 0xa9, 0xdb, 0x21, 0x1e, 0xdf, 0x28, 0x05, 0x3c, + 0x3a, 0x6c, 0x6e, 0x9c, 0x34, 0x52, 0xb0, 0x6a, 0x6f, 0x7d, 0x3d, 0xb7, 0xb9, 0x15, 0xcb, 0x07, 0x5b, 0x6f, 0x20, + 0x09, 0xe9, 0xc6, 0x91, 0x33, 0xa5, 0x5e, 0x1b, 0xda, 0xa7, 0xd9, 0xca, 0xcd, 0x4d, 0xd2, 0x71, 0xaf, 0x9e, 0xc6, + 0x09, 0xe3, 0x38, 0x97, 0x54, 0x26, 0x4e, 0x7c, 0x89, 0xf9, 0xf2, 0x44, 0x6c, 0xa6, 0x25, 0x35, 0xba, 0xca, 0x75, + 0x67, 0x4a, 0x14, 0xa4, 0xe8, 0xf9, 0xdb, 0x2c, 0xe1, 0x8a, 0x6b, 0xc2, 0x33, 0x03, 0x35, 0xa1, 0x75, 0xee, 0x5e, + 0x66, 0x78, 0x70, 0x89, 0xfb, 0xe3, 0xc4, 0xbf, 0x50, 0x3d, 0xb1, 0xe5, 0x57, 0x94, 0xb1, 0xf1, 0x66, 0xdd, 0xdd, + 0xbf, 0xa7, 0xc4, 0x7d, 0x7e, 0x2a, 0x8e, 0xa2, 0xf5, 0xf6, 0xfd, 0xe4, 0x2a, 0xd6, 0xf3, 0xa9, 0xe0, 0x1b, 0x45, + 0x00, 0x9c, 0xf4, 0x68, 0x59, 0xee, 0xb4, 0x1a, 0x7c, 0x06, 0x02, 0x22, 0xdf, 0x9d, 0xb3, 0x6b, 0x7f, 0x3c, 0x25, + 0xd3, 0x66, 0x34, 0x97, 0x97, 0x41, 0xb3, 0xaf, 0x11, 0x00, 0xc8, 0x69, 0xcd, 0xc8, 0xc7, 0xf9, 0x10, 0x06, 0x97, + 0xcb, 0x24, 0x73, 0xbb, 0x05, 0x70, 0x01, 0xb9, 0x52, 0xbe, 0x5a, 0x57, 0x31, 0xd4, 0xcc, 0x8b, 0x70, 0x7c, 0xb5, + 0x97, 0x4f, 0xd1, 0x4e, 0x58, 0xda, 0xab, 0xb9, 0x8c, 0x04, 0xd6, 0xab, 0x0e, 0x11, 0x7a, 0xb2, 0x35, 0xf2, 0xf8, + 0x32, 0xf3, 0xdd, 0x96, 0x03, 0x6a, 0x07, 0x96, 0x57, 0x5b, 0xcd, 0x49, 0xd3, 0x6e, 0x79, 0x34, 0xdb, 0x33, 0xaa, + 0xa1, 0x60, 0x39, 0x77, 0xfb, 0x91, 0x1d, 0x67, 0x4b, 0x75, 0x35, 0xb7, 0xfa, 0x82, 0xb0, 0x2d, 0x16, 0xc8, 0xc7, + 0x11, 0xb0, 0xa6, 0x13, 0x5a, 0x92, 0x39, 0x28, 0x4d, 0x3b, 0x0a, 0x40, 0x07, 0xf0, 0x64, 0x1a, 0xf7, 0x94, 0xf4, + 0xdf, 0x81, 0xb7, 0x6b, 0x7d, 0xd2, 0xa1, 0x18, 0x05, 0xcf, 0x3f, 0x9c, 0x01, 0x38, 0xfd, 0xde, 0xda, 0xfb, 0xd9, + 0xbb, 0x35, 0xa0, 0xe6, 0x5a, 0xae, 0x1c, 0xc1, 0x7f, 0x2a, 0x32, 0x65, 0x45, 0xcc, 0xb7, 0x23, 0x54, 0xaa, 0xb0, + 0xdc, 0xab, 0x80, 0xbf, 0xdf, 0x0d, 0xb7, 0xff, 0xaf, 0x8a, 0xc9, 0x3d, 0xfc, 0xf9, 0xdf, 0xd6, 0xf0, 0x7f, 0xd9, + 0x6d, 0x58, 0x5b, 0xee, 0x7f, 0x6b, 0xc0, 0xf4, 0xbb, 0x02, 0x35, 0xc1, 0xf6, 0x6f, 0xdf, 0xb9, 0x24, 0x97, 0xf5, + 0xe1, 0xde, 0xc9, 0x4a, 0x0f, 0x53, 0x7a, 0x30, 0xf0, 0x08, 0xff, 0x7f, 0x96, 0x81, 0xec, 0x05, 0x85, 0xc9, 0xc2, + 0xfe, 0xfb, 0x59, 0x2a, 0xa0, 0x9f, 0x12, 0x65, 0x8d, 0x23, 0xde, 0xd6, 0x7e, 0x5a, 0xa3, 0x1f, 0x23, 0xa2, 0x58, + 0xa7, 0x82, 0x7e, 0x55, 0x9f, 0x27, 0x88, 0xef, 0x7d, 0x5c, 0xfa, 0x12, 0x2a, 0x86, 0x07, 0xca, 0xde, 0x5d, 0xc1, + 0xf9, 0x91, 0x6e, 0xc7, 0x45, 0xa1, 0xf9, 0x53, 0xe5, 0x8f, 0xdb, 0x7a, 0xae, 0xf2, 0x7e, 0x45, 0xda, 0x37, 0xb9, + 0xf5, 0x57, 0x51, 0xd2, 0xbd, 0x20, 0x8b, 0x2c, 0x16, 0x77, 0xe7, 0x22, 0xf9, 0xc4, 0xd9, 0x03, 0xdb, 0x39, 0x9b, + 0x47, 0x78, 0x32, 0xa7, 0xb1, 0x48, 0x44, 0x67, 0xe1, 0xf5, 0x40, 0x93, 0x8a, 0x5d, 0x1f, 0xe0, 0xbb, 0x0f, 0xfc, + 0xe4, 0x94, 0x2f, 0x7e, 0xf2, 0x57, 0x3e, 0x46, 0x8f, 0xf5, 0x29, 0x9b, 0x60, 0xf0, 0x4a, 0x97, 0x53, 0x3d, 0x7b, + 0x79, 0x68, 0x48, 0xf4, 0xa6, 0xc6, 0xca, 0x7e, 0xa0, 0x67, 0xab, 0xa9, 0xee, 0x92, 0xb1, 0x42, 0xcb, 0xbb, 0xe2, + 0xf6, 0xd1, 0xba, 0x1a, 0x5f, 0x69, 0xdc, 0x4d, 0xcb, 0xf7, 0x24, 0xea, 0x60, 0x71, 0xf3, 0xe3, 0x4e, 0xbd, 0x6d, + 0x5b, 0xe5, 0xbf, 0x0b, 0xd0, 0x1f, 0x6c, 0xf4, 0x4e, 0xe6, 0x30, 0xa7, 0x57, 0x7e, 0x9e, 0xe3, 0x95, 0xc3, 0x9c, + 0x5d, 0xd9, 0xcd, 0xb9, 0x95, 0xeb, 0x39, 0xff, 0xf0, 0x71, 0x70, 0x93, 0x27, 0xc1, 0x2e, 0x1f, 0x63, 0x88, 0xb3, + 0xb3, 0x0f, 0xc3, 0x2d, 0xe7, 0x8f, 0x11, 0xb7, 0x6d, 0xca, 0x0a, 0x52, 0x4f, 0x7d, 0x3c, 0xf9, 0xf8, 0xde, 0x7a, + 0x97, 0x7e, 0x65, 0xb3, 0xab, 0x3d, 0xad, 0xc6, 0xcb, 0x22, 0x8a, 0xc4, 0x5c, 0x6d, 0xca, 0xfb, 0xad, 0x1b, 0x66, + 0x02, 0x36, 0xed, 0xf9, 0xb6, 0xed, 0xc8, 0xcd, 0x95, 0xea, 0x9f, 0xcf, 0xa8, 0x4b, 0xe6, 0x3b, 0xf2, 0x48, 0x6a, + 0x66, 0xaf, 0xea, 0xcc, 0x3b, 0xd3, 0x44, 0x11, 0x24, 0x08, 0xa2, 0x7c, 0xbe, 0x90, 0xc9, 0xdc, 0xd2, 0x2a, 0x41, + 0xd0, 0xd8, 0x37, 0x60, 0x49, 0x99, 0xac, 0x5b, 0x57, 0xcc, 0x74, 0x06, 0xf2, 0x69, 0x0f, 0x6b, 0xc2, 0x9b, 0xc1, + 0xe1, 0x7c, 0xdd, 0xf5, 0xd9, 0xe5, 0xbd, 0xcf, 0x3d, 0xea, 0xb8, 0xbf, 0x15, 0x37, 0x20, 0x07, 0x73, 0x9f, 0x27, + 0x77, 0x21, 0x6b, 0xac, 0xd3, 0x86, 0x72, 0x4e, 0x75, 0x6d, 0xda, 0x60, 0x88, 0x5e, 0xfd, 0x42, 0x98, 0x48, 0x5b, + 0x3c, 0x9f, 0xd6, 0xd2, 0x37, 0x05, 0x2e, 0x6d, 0x66, 0xd0, 0x69, 0x58, 0x2c, 0x66, 0x12, 0xc2, 0x89, 0x8b, 0x7a, + 0xb4, 0xa9, 0x24, 0x89, 0xf1, 0xb3, 0xfe, 0x5a, 0x2e, 0x23, 0x38, 0x50, 0x0b, 0x4c, 0x5c, 0x17, 0x69, 0xf4, 0x1f, + 0xcc, 0x50, 0x6f, 0x9b, 0xfd, 0xa0, 0xcd, 0x8d, 0x29, 0x7c, 0x85, 0x7e, 0xd2, 0xf4, 0x0a, 0xb5, 0x7b, 0x11, 0x1f, + 0x89, 0x21, 0xf2, 0x01, 0x6c, 0x3b, 0xcc, 0x09, 0xb4, 0x80, 0x73, 0xc4, 0x18, 0x2c, 0x4e, 0x95, 0xea, 0x6c, 0x92, + 0xb7, 0x22, 0x46, 0x5b, 0xb2, 0x1d, 0x6d, 0xd5, 0xb9, 0x1c, 0x5b, 0xb4, 0xb9, 0x91, 0x7d, 0xf3, 0x9f, 0xab, 0x7c, + 0xa3, 0x7d, 0x7f, 0xb5, 0x0a, 0xec, 0x81, 0x91, 0xbf, 0x6b, 0x7f, 0x33, 0x97, 0xb2, 0xec, 0xff, 0x63, 0x72, 0x32, + 0x7b, 0x23, 0x81, 0xf8, 0x95, 0xe7, 0x69, 0x25, 0x9e, 0x84, 0x91, 0xfd, 0x8e, 0x3c, 0xa1, 0xca, 0xb7, 0xd1, 0x96, + 0x96, 0xe5, 0x7e, 0xb7, 0x96, 0x1f, 0x93, 0xe9, 0x5c, 0xbe, 0xf5, 0x84, 0x21, 0xb7, 0xe7, 0x89, 0x78, 0x9f, 0x00, + 0xe7, 0xe6, 0x78, 0x69, 0x9e, 0x7c, 0xe3, 0x91, 0x69, 0xc1, 0x16, 0x80, 0x37, 0x72, 0x76, 0x24, 0xca, 0x49, 0x22, + 0x2d, 0x86, 0x7a, 0xfc, 0xd6, 0xd9, 0xea, 0xef, 0xcd, 0x01, 0x57, 0x00, 0x26, 0x0f, 0xf8, 0x70, 0x24, 0x3f, 0x6e, + 0xdb, 0x09, 0xdb, 0x98, 0x35, 0xc4, 0xe4, 0x61, 0x72, 0x50, 0x7e, 0x15, 0xb4, 0x1a, 0xe9, 0x0b, 0x97, 0x93, 0xcc, + 0x02, 0xe7, 0x90, 0xb8, 0xef, 0x2f, 0x22, 0x9f, 0xfe, 0x5d, 0xe6, 0x4f, 0x0e, 0xce, 0x4c, 0xf1, 0x1f, 0xa3, 0xf1, + 0x40, 0x46, 0xe6, 0x45, 0xfd, 0x53, 0xa3, 0xb5, 0x54, 0x3b, 0x3b, 0x8a, 0x9b, 0x2b, 0x6b, 0x6f, 0xcd, 0x9a, 0x03, + 0xd6, 0x5b, 0x23, 0xf3, 0xc6, 0x32, 0xa2, 0x69, 0x23, 0x7e, 0x09, 0xe7, 0xd5, 0x71, 0x9d, 0x07, 0xe4, 0x37, 0x84, + 0x10, 0xe6, 0xd5, 0x7a, 0xcb, 0x53, 0xb8, 0x5e, 0x2f, 0x75, 0x34, 0xa7, 0xa1, 0xcf, 0x3c, 0x4b, 0x03, 0xcd, 0x80, + 0xe8, 0xd9, 0x79, 0xf5, 0x99, 0xd7, 0x99, 0xb9, 0x18, 0x8f, 0x4e, 0x68, 0xa6, 0x4e, 0xb8, 0xe7, 0x83, 0x19, 0x0b, + 0x14, 0x9b, 0xf8, 0x09, 0x49, 0xe0, 0xf1, 0x93, 0xa3, 0xbc, 0x73, 0x4e, 0xc9, 0xc3, 0x3b, 0x7e, 0x00, 0x0d, 0x40, + 0xe6, 0x1d, 0x24, 0xad, 0xb6, 0xbd, 0xc7, 0x6a, 0x14, 0x09, 0xbe, 0xcc, 0x95, 0xf1, 0xb4, 0xc9, 0x17, 0x6e, 0x36, + 0xb3, 0x7b, 0x57, 0x0f, 0x9e, 0x6f, 0x56, 0xbb, 0xbe, 0xcd, 0x9a, 0xcf, 0x6c, 0xfe, 0xe9, 0xd3, 0x22, 0x2e, 0x67, + 0x94, 0xb9, 0x9e, 0x53, 0xe8, 0x35, 0x23, 0x93, 0x0e, 0xdd, 0x73, 0x89, 0x9d, 0x90, 0x05, 0x4d, 0x6e, 0xe1, 0xa2, + 0x9a, 0x84, 0xfb, 0xda, 0xef, 0x84, 0xd4, 0xa9, 0x5a, 0xd4, 0x1b, 0xa3, 0x27, 0xbb, 0xf8, 0x19, 0x7b, 0xcc, 0x3b, + 0x33, 0x39, 0x91, 0x01, 0x78, 0x51, 0xd9, 0x14, 0x58, 0x9e, 0x35, 0x01, 0x04, 0x65, 0x58, 0x86, 0xd6, 0x12, 0x40, + 0x61, 0x6e, 0xca, 0x07, 0x19, 0xb4, 0xfc, 0x1c, 0x7f, 0x5d, 0x9e, 0x1b, 0x98, 0x37, 0x3e, 0x4e, 0x4e, 0xd0, 0x9c, + 0x14, 0xca, 0x85, 0x88, 0x0f, 0x0a, 0xa0, 0x46, 0x05, 0x9e, 0xd1, 0xbd, 0x0f, 0xf0, 0xdf, 0x0f, 0xfb, 0x75, 0x9d, + 0xf2, 0xef, 0x4f, 0xfe, 0x69, 0xee, 0x3d, 0xf1, 0xaf, 0x4b, 0x49, 0x0a, 0x08, 0x9e, 0xc2, 0xbe, 0xb9, 0xde, 0xbf, + 0x4d, 0xee, 0x55, 0xed, 0x6e, 0x23, 0xa3, 0x3a, 0xb1, 0x30, 0x94, 0xfe, 0x7a, 0xe4, 0xca, 0x37, 0x1f, 0x62, 0xdb, + 0xc3, 0xa6, 0x3f, 0xdc, 0xf7, 0xab, 0x7c, 0x73, 0x21, 0x53, 0xbb, 0x69, 0x45, 0x62, 0x95, 0x62, 0xc0, 0x95, 0x82, + 0x86, 0x39, 0x67, 0x0f, 0xdf, 0xce, 0xd6, 0x46, 0x1a, 0xf1, 0x86, 0xb8, 0x12, 0xab, 0xee, 0x93, 0x50, 0xf9, 0xf6, + 0xfe, 0xf5, 0xd2, 0xb2, 0xf3, 0xb9, 0xf5, 0x0b, 0xc9, 0x00, 0xb3, 0x40, 0xe2, 0x53, 0x71, 0xe4, 0x3e, 0x94, 0x74, + 0x92, 0xa2, 0x78, 0x0c, 0x6d, 0x81, 0x05, 0xa3, 0x21, 0x97, 0xc6, 0x00, 0x59, 0x6a, 0x89, 0x47, 0x5d, 0x4c, 0x2f, + 0xf8, 0xbe, 0xed, 0x06, 0xcd, 0xf7, 0x86, 0x27, 0x1f, 0x7a, 0x6d, 0x62, 0x04, 0xa5, 0x2b, 0xb0, 0xbd, 0xad, 0x18, + 0x91, 0xcb, 0xa5, 0xe4, 0x9f, 0x26, 0x0f, 0x50, 0xb8, 0xef, 0x9e, 0xf8, 0x5c, 0x2c, 0x14, 0xa5, 0xc8, 0x7c, 0x10, + 0x57, 0x83, 0x88, 0x93, 0x06, 0xaa, 0xae, 0x36, 0x76, 0x20, 0xcc, 0xb2, 0x31, 0xa5, 0xc6, 0x0b, 0x1a, 0x10, 0x44, + 0x88, 0x6c, 0x62, 0x05, 0x22, 0xd4, 0xc3, 0xfc, 0x70, 0x43, 0xc7, 0x8a, 0x3a, 0x45, 0x27, 0xa8, 0xa9, 0xb4, 0x86, + 0x34, 0x3d, 0x7a, 0x85, 0x0d, 0x8c, 0x4e, 0x9c, 0x4d, 0x57, 0xe1, 0xbd, 0xe1, 0xce, 0xbe, 0x37, 0x8f, 0x79, 0x2e, + 0xd3, 0x1e, 0x84, 0x4a, 0xc3, 0x58, 0x4f, 0xb7, 0xc4, 0xe9, 0x9e, 0x6d, 0xe6, 0x25, 0xd5, 0x7c, 0x77, 0xb7, 0x67, + 0xed, 0x39, 0x1b, 0x51, 0xbb, 0xad, 0x83, 0xa3, 0xdb, 0x60, 0xac, 0xcf, 0xbb, 0xbb, 0xbc, 0xa0, 0x3d, 0xa9, 0x62, + 0x22, 0x36, 0xd1, 0xa4, 0x01, 0x20, 0xda, 0x51, 0x9a, 0xec, 0xf3, 0x93, 0x9d, 0x1a, 0x08, 0xa5, 0x23, 0x28, 0x6d, + 0x63, 0x33, 0x51, 0x55, 0x6f, 0xf2, 0xb8, 0x8e, 0x9b, 0x30, 0x43, 0x41, 0xcd, 0x7f, 0x3f, 0x0a, 0xb6, 0xdc, 0x19, + 0x98, 0x20, 0xd6, 0x73, 0xdb, 0xd2, 0x5d, 0xc0, 0x03, 0x9c, 0xbe, 0xed, 0xc0, 0x9b, 0x46, 0x7c, 0x1e, 0x1e, 0xa7, + 0xc7, 0xba, 0x17, 0x6e, 0x89, 0x12, 0x23, 0x19, 0x41, 0x06, 0xb9, 0xf6, 0xd8, 0x07, 0x2f, 0x61, 0x91, 0xae, 0x23, + 0xc7, 0x0f, 0xe1, 0x82, 0xc2, 0x84, 0x70, 0x1a, 0xee, 0x5e, 0x14, 0x75, 0x9b, 0xdd, 0xee, 0x89, 0xd1, 0xce, 0x96, + 0xdc, 0xd5, 0x6e, 0x90, 0x79, 0x14, 0xe8, 0xdd, 0x2a, 0x8b, 0xb2, 0xb5, 0x23, 0x1f, 0x36, 0x93, 0x7c, 0x1c, 0x48, + 0xa6, 0xbe, 0xbb, 0x33, 0xb4, 0x3f, 0x90, 0x9d, 0xb6, 0xef, 0x12, 0x5a, 0x1f, 0xa0, 0x9a, 0x56, 0xed, 0xb6, 0x65, + 0xdb, 0xf6, 0x69, 0x19, 0x90, 0x7b, 0xac, 0x7d, 0xe9, 0x46, 0xc6, 0xff, 0xfc, 0x04, 0x23, 0xad, 0x3b, 0xf4, 0x0b, + 0x73, 0x97, 0x9d, 0x46, 0xb6, 0x37, 0x57, 0x4f, 0x51, 0x5a, 0xfb, 0xc0, 0x2c, 0x1a, 0x1f, 0x47, 0x60, 0x7c, 0x06, + 0x4d, 0x84, 0xc1, 0xcf, 0xf0, 0x8b, 0x85, 0x74, 0xb9, 0x22, 0xf7, 0x62, 0x02, 0xe8, 0xdd, 0xe8, 0xcf, 0x9e, 0x41, + 0xb5, 0x54, 0x65, 0x74, 0x9d, 0x14, 0xc5, 0xf4, 0xb7, 0xff, 0x9f, 0xab, 0x81, 0xfa, 0x83, 0x18, 0x85, 0xbe, 0xfc, + 0xe2, 0xe8, 0x5a, 0x57, 0x6c, 0x7a, 0xfe, 0x82, 0xee, 0x03, 0x2e, 0xf1, 0x82, 0x01, 0x3e, 0x8b, 0xc8, 0xdc, 0x24, + 0x73, 0xad, 0xd1, 0x69, 0xec, 0x6d, 0xb0, 0x77, 0x27, 0xff, 0x64, 0x10, 0xf7, 0x0b, 0x68, 0x3b, 0xd3, 0xdd, 0x20, + 0x83, 0x54, 0x9f, 0x13, 0xfd, 0xc3, 0xc0, 0x06, 0xf9, 0xc9, 0xa2, 0x5c, 0x46, 0x38, 0x9f, 0x14, 0x1d, 0x63, 0x15, + 0x62, 0x57, 0x21, 0xf7, 0x8d, 0x74, 0x37, 0x06, 0x76, 0xe8, 0x19, 0x0c, 0xfb, 0xdd, 0x69, 0x53, 0xa9, 0xa0, 0xbd, + 0xaa, 0x46, 0x93, 0xdd, 0x48, 0x6e, 0xed, 0x45, 0x6c, 0xf4, 0x43, 0xc0, 0x10, 0x37, 0x55, 0x8b, 0xdb, 0x01, 0x0b, + 0x87, 0x5d, 0x5c, 0x47, 0x77, 0x04, 0x99, 0x3f, 0x7c, 0x62, 0xc1, 0x15, 0xd9, 0xf9, 0xdf, 0x12, 0x4c, 0xdf, 0x3b, + 0xad, 0xcc, 0xff, 0x53, 0xcc, 0xfe, 0xd0, 0xf3, 0x8a, 0xac, 0x3f, 0x7b, 0xbf, 0x28, 0xba, 0x84, 0xcb, 0x2d, 0x12, + 0xf3, 0x29, 0x34, 0xfd, 0xf5, 0xd6, 0x7c, 0x23, 0x24, 0xee, 0x0f, 0x26, 0x04, 0x9b, 0x94, 0xc5, 0x18, 0x11, 0xfe, + 0xf5, 0xf6, 0xab, 0xf9, 0xd7, 0xa4, 0x25, 0x88, 0xa0, 0xaa, 0xf1, 0x4e, 0x7f, 0xcf, 0x68, 0xf9, 0x01, 0xfe, 0xfd, + 0x81, 0x3f, 0x39, 0xe5, 0xef, 0x9f, 0xfc, 0xd3, 0x2c, 0xbd, 0x25, 0x57, 0xf3, 0x19, 0x72, 0xb0, 0xac, 0x22, 0x01, + 0x2f, 0x5e, 0xcb, 0x91, 0x37, 0x3b, 0x88, 0x07, 0x32, 0xff, 0xea, 0x24, 0x2e, 0xd0, 0x31, 0xf2, 0x21, 0x4f, 0x4b, + 0xf2, 0x82, 0xb1, 0x3b, 0xaa, 0xa5, 0x99, 0xb6, 0xd5, 0xbb, 0x84, 0xda, 0xc5, 0x20, 0x4b, 0x30, 0xdf, 0xff, 0x24, + 0x72, 0x52, 0xd2, 0x98, 0x7f, 0x2b, 0x1f, 0x8f, 0xee, 0x59, 0x1a, 0x98, 0xa2, 0x62, 0xfe, 0xea, 0x45, 0xca, 0x93, + 0xd5, 0x1f, 0xa3, 0x11, 0xf7, 0x8d, 0x99, 0x85, 0xe8, 0x03, 0x3b, 0x43, 0x62, 0xe4, 0xb8, 0x7b, 0x71, 0xd2, 0xf8, + 0xad, 0x4e, 0x90, 0x78, 0xcb, 0x34, 0x48, 0x5f, 0x8b, 0x43, 0xb9, 0x56, 0x8d, 0x4f, 0x3c, 0x58, 0xa4, 0xfd, 0x6d, + 0x77, 0x96, 0xbe, 0xf5, 0xf2, 0xb8, 0x32, 0x7d, 0x99, 0x70, 0x17, 0xc6, 0x22, 0xce, 0x35, 0x56, 0x54, 0x24, 0x46, + 0xe0, 0x10, 0xc5, 0xdb, 0x02, 0x80, 0xd9, 0x28, 0x76, 0x71, 0xfc, 0xc7, 0xef, 0xda, 0xc2, 0x89, 0x44, 0x4b, 0x91, + 0xd4, 0xdc, 0x61, 0x7c, 0xa3, 0xf1, 0x98, 0xc1, 0x78, 0x4f, 0x54, 0xfd, 0xc5, 0xf2, 0x98, 0x95, 0x5a, 0x8f, 0x52, + 0x3c, 0x66, 0x7c, 0x69, 0x34, 0x58, 0x62, 0xcf, 0x43, 0x98, 0xfc, 0x4b, 0xa3, 0xe4, 0xf7, 0x52, 0xec, 0x6a, 0x69, + 0x24, 0xb6, 0x0e, 0xc4, 0x4c, 0x66, 0x71, 0x02, 0xeb, 0x4c, 0x75, 0x4f, 0xce, 0xc6, 0x4b, 0xa5, 0x79, 0x05, 0xad, + 0x0b, 0x7f, 0xa0, 0x9d, 0xe0, 0xd6, 0x5f, 0x3c, 0xbf, 0xe9, 0x56, 0x0e, 0x47, 0xae, 0xd8, 0x6b, 0xfd, 0x3d, 0xde, + 0xee, 0xa9, 0xfa, 0xcb, 0x35, 0xd0, 0x9d, 0x63, 0xb3, 0x49, 0x93, 0xe1, 0x60, 0xe4, 0x13, 0x40, 0x27, 0x48, 0x8e, + 0x66, 0x58, 0xa1, 0xe7, 0x5d, 0x02, 0xb3, 0x5f, 0xf2, 0xe2, 0x10, 0x25, 0x04, 0x7c, 0x6b, 0xb3, 0x98, 0x84, 0xe8, + 0xe1, 0xff, 0xb9, 0x97, 0x7e, 0xbd, 0xb8, 0x5d, 0x8e, 0xb4, 0x05, 0x72, 0xeb, 0x1c, 0x30, 0x0e, 0x7c, 0xb5, 0x31, + 0x50, 0x2e, 0x44, 0x66, 0x44, 0x53, 0x3a, 0x1b, 0x9c, 0x6e, 0x43, 0x3e, 0x5b, 0xf3, 0xf8, 0x46, 0x25, 0x3a, 0xa7, + 0x4e, 0xd2, 0x8c, 0x5b, 0x1b, 0xc4, 0x58, 0x2e, 0x44, 0x43, 0x2c, 0x74, 0x6c, 0x0d, 0x2e, 0x76, 0xdc, 0x0e, 0x8f, + 0xe7, 0xa2, 0x11, 0x3f, 0x92, 0x10, 0x11, 0x9c, 0x8b, 0x99, 0x18, 0xf2, 0xa1, 0x19, 0x01, 0xec, 0xe7, 0x79, 0x7c, + 0x61, 0x1a, 0x66, 0x8f, 0xe0, 0xd9, 0xcb, 0x13, 0xf1, 0x93, 0x46, 0x30, 0x44, 0xc8, 0x86, 0x49, 0x02, 0xd8, 0x63, + 0xb2, 0xd6, 0x6f, 0xdc, 0xda, 0xcb, 0xbe, 0xbf, 0x2c, 0x1b, 0xb7, 0xf3, 0xdf, 0xb1, 0xf8, 0x61, 0x12, 0xf5, 0xea, + 0x9d, 0x08, 0x61, 0x57, 0x8c, 0xde, 0xc9, 0x2a, 0x74, 0x55, 0x44, 0xdf, 0xf7, 0xf0, 0xb1, 0xc6, 0x06, 0xe1, 0x8f, + 0xe1, 0x29, 0x90, 0x37, 0x91, 0xdd, 0xbf, 0x13, 0x5c, 0xdc, 0xf8, 0xd1, 0x9e, 0xc2, 0xce, 0xd8, 0x21, 0x74, 0xa9, + 0x92, 0x49, 0xd1, 0x1d, 0x94, 0x26, 0x9a, 0x6a, 0x78, 0xa1, 0x40, 0x7c, 0x0c, 0x5b, 0x76, 0x35, 0xe3, 0x23, 0xc5, + 0x6f, 0xe3, 0xc5, 0x02, 0xa7, 0x98, 0x60, 0xf4, 0xf0, 0xc0, 0x84, 0xb1, 0x05, 0x6a, 0xf4, 0x10, 0x15, 0x53, 0xce, + 0x63, 0xaa, 0x4b, 0x16, 0x27, 0x7b, 0x65, 0x56, 0x6b, 0xa7, 0x4f, 0x93, 0x54, 0xac, 0x57, 0x58, 0x9e, 0x59, 0xbd, + 0x6a, 0x97, 0x09, 0xf0, 0xc1, 0xff, 0x38, 0x04, 0x26, 0x64, 0x5c, 0x75, 0x6a, 0xfb, 0x30, 0x46, 0x78, 0xf3, 0x3b, + 0xc9, 0x84, 0xb2, 0x49, 0x3e, 0x8c, 0x70, 0x67, 0xf7, 0x44, 0x77, 0x8a, 0x33, 0x26, 0x77, 0x51, 0x0d, 0x83, 0x99, + 0x4e, 0xd0, 0x87, 0xfb, 0x85, 0x39, 0x8f, 0x2b, 0xbc, 0xc7, 0xdf, 0x32, 0xaa, 0x13, 0xe2, 0xf5, 0x45, 0xba, 0x88, + 0x1f, 0x38, 0x05, 0xdb, 0x05, 0x54, 0xc3, 0x08, 0x5c, 0x87, 0xd3, 0xba, 0x23, 0x89, 0xe2, 0x8e, 0x4a, 0x50, 0xdf, + 0x48, 0xf9, 0xbb, 0x63, 0x23, 0x96, 0xa8, 0x1a, 0xe9, 0x2f, 0x4a, 0x79, 0x60, 0x87, 0xf4, 0xa4, 0xd4, 0xe8, 0x98, + 0xa9, 0x53, 0xd3, 0xe0, 0x72, 0x90, 0xbb, 0xe0, 0x95, 0xce, 0xec, 0xc1, 0xb9, 0x58, 0x78, 0xa1, 0x19, 0xa2, 0x1e, + 0xe1, 0xda, 0x78, 0xea, 0x92, 0xc4, 0xa9, 0x6a, 0x49, 0xc2, 0x72, 0xd2, 0xe9, 0x4a, 0x03, 0xf7, 0xea, 0x65, 0x8f, + 0x31, 0x90, 0x26, 0xb3, 0x04, 0xa1, 0x7a, 0x5e, 0x23, 0xbc, 0x46, 0x0a, 0x92, 0x76, 0x1e, 0xf6, 0x37, 0x09, 0x28, + 0x6f, 0xfd, 0x43, 0xbd, 0xa2, 0xc0, 0xf2, 0xa7, 0x82, 0xbc, 0x57, 0xeb, 0x6f, 0x33, 0xea, 0x46, 0x37, 0x09, 0xc3, + 0x6e, 0xfd, 0xc3, 0xc6, 0x2a, 0x35, 0x39, 0x9d, 0xf4, 0x6d, 0xb1, 0x7b, 0x00, 0xf2, 0x72, 0xb7, 0x37, 0xc4, 0x64, + 0x44, 0x10, 0x35, 0x07, 0x2e, 0x42, 0x75, 0xc6, 0xf8, 0x76, 0xa2, 0x1b, 0x35, 0xad, 0x11, 0x62, 0xc9, 0xea, 0x1a, + 0x17, 0x72, 0x57, 0x4c, 0x9c, 0x8c, 0x44, 0xe8, 0x96, 0xe4, 0x5c, 0x7f, 0x80, 0xc3, 0x07, 0x92, 0x29, 0x15, 0xc1, + 0x65, 0x82, 0xa4, 0xfa, 0xbc, 0x89, 0xa0, 0x5b, 0x0d, 0x10, 0x58, 0x02, 0xa9, 0x96, 0xb9, 0x59, 0xdc, 0x32, 0x22, + 0x70, 0x54, 0xcb, 0x57, 0x27, 0x52, 0x38, 0xa3, 0xd9, 0xce, 0x1f, 0x5f, 0xfa, 0xc8, 0xbc, 0x55, 0x85, 0x61, 0x49, + 0x01, 0xe8, 0xca, 0xf8, 0x92, 0x42, 0x90, 0x4a, 0xf9, 0xa2, 0x5b, 0xab, 0x9a, 0xb9, 0x4e, 0x6c, 0xe8, 0xc6, 0x0b, + 0xcc, 0xaf, 0xd5, 0x86, 0x81, 0xcd, 0xdd, 0xae, 0x65, 0xae, 0xd8, 0x20, 0xe7, 0x1d, 0xb5, 0x2d, 0x1f, 0x96, 0xd3, + 0xd1, 0xd5, 0x8c, 0x85, 0x8b, 0xdb, 0xa1, 0x48, 0x7d, 0x60, 0x32, 0xe4, 0x34, 0xba, 0x5f, 0x02, 0x32, 0xff, 0xb4, + 0x34, 0x6a, 0x37, 0x5f, 0x30, 0x7a, 0x0e, 0xbd, 0x8c, 0xe3, 0xd9, 0x45, 0xfe, 0x00, 0xa1, 0xec, 0x0d, 0x4e, 0xa1, + 0xb1, 0x01, 0x9d, 0x8b, 0xf0, 0x99, 0x18, 0x05, 0xd6, 0x91, 0x00, 0xbc, 0x38, 0xb7, 0x71, 0x74, 0x00, 0xa8, 0x35, + 0xaa, 0x80, 0xfb, 0xfd, 0xd1, 0x23, 0x87, 0x28, 0xe2, 0x86, 0xdc, 0x53, 0xd4, 0xa5, 0x3c, 0x9f, 0x48, 0x2b, 0x0b, + 0x1f, 0xa6, 0xc8, 0x2b, 0x7e, 0xa1, 0x17, 0x76, 0x69, 0xe7, 0xa7, 0xa5, 0x9b, 0xf7, 0x4b, 0x4d, 0x3c, 0x95, 0xa3, + 0x9f, 0x53, 0x35, 0x65, 0x50, 0xd2, 0xf5, 0xee, 0x22, 0xa2, 0x03, 0x73, 0x2c, 0x6c, 0xf7, 0x03, 0xa7, 0x9a, 0xb6, + 0xca, 0xe4, 0x6e, 0xe5, 0xa1, 0x02, 0x15, 0x1c, 0xa4, 0xeb, 0x65, 0x7b, 0xef, 0x3f, 0xb1, 0xe1, 0x87, 0x4d, 0xf9, + 0x30, 0xd5, 0x75, 0xda, 0xd3, 0xab, 0xa1, 0x0d, 0x91, 0x83, 0xb4, 0x90, 0xb6, 0xab, 0xc6, 0xbd, 0x35, 0x93, 0xd6, + 0xfe, 0xcd, 0xa6, 0x97, 0xed, 0x14, 0x3b, 0x7f, 0x8c, 0x7a, 0x05, 0x51, 0xe4, 0xfa, 0xbd, 0x74, 0xf5, 0x73, 0xe9, + 0x76, 0x2d, 0x48, 0x3f, 0x50, 0x05, 0xcd, 0x2c, 0xa7, 0xbf, 0x2c, 0xf9, 0xe6, 0x96, 0xd2, 0xc7, 0xf4, 0x87, 0x38, + 0xe7, 0xf8, 0xcc, 0xf0, 0x4c, 0x1d, 0x3d, 0xe8, 0x14, 0x11, 0xcd, 0x38, 0x9b, 0x67, 0xd1, 0x74, 0x16, 0xf0, 0x9a, + 0x54, 0x70, 0x57, 0x3f, 0x1c, 0x3a, 0xd6, 0xb4, 0x8b, 0x90, 0xc5, 0x3e, 0x7e, 0xd8, 0xb5, 0x23, 0xe8, 0x61, 0x14, + 0x14, 0x90, 0xe6, 0x5e, 0x32, 0xca, 0x26, 0x06, 0x7e, 0xeb, 0x25, 0xb0, 0xdd, 0xfb, 0xad, 0x7d, 0x64, 0x77, 0xe2, + 0x95, 0x79, 0x15, 0xf4, 0x8e, 0x44, 0x8d, 0x08, 0x55, 0xdb, 0x0e, 0x71, 0x7d, 0xe5, 0xd8, 0xc4, 0xa6, 0x2c, 0x57, + 0x4c, 0xb8, 0xf8, 0x0d, 0xa0, 0x20, 0x4b, 0x72, 0x08, 0x3a, 0x44, 0x4e, 0x1b, 0xdc, 0x39, 0x3a, 0xd5, 0xa3, 0x16, + 0x7f, 0xe6, 0x0b, 0xf4, 0xb8, 0x26, 0x94, 0x51, 0x40, 0x3b, 0x50, 0xf0, 0x99, 0xec, 0xa0, 0xab, 0x55, 0x76, 0x99, + 0xad, 0xb1, 0x81, 0x08, 0x10, 0x52, 0xd1, 0x9f, 0xa6, 0xa7, 0x05, 0x5d, 0xc2, 0xa5, 0x46, 0x0e, 0xda, 0x97, 0xca, + 0x30, 0x0e, 0x75, 0x74, 0x7d, 0x2a, 0x8f, 0x5f, 0x0d, 0x2c, 0x01, 0xe0, 0xe8, 0x33, 0x08, 0x3b, 0xf8, 0x6c, 0xe7, + 0x8a, 0xc5, 0x65, 0xd0, 0x1d, 0x18, 0xd2, 0xbe, 0xda, 0xb9, 0xea, 0xa7, 0xe8, 0xb7, 0xf6, 0x2e, 0xbe, 0x54, 0xd8, + 0x33, 0x2a, 0xb1, 0x46, 0xe8, 0x10, 0x99, 0x71, 0x8d, 0xbc, 0x7b, 0xcf, 0xbd, 0x1f, 0xdb, 0xe8, 0x15, 0x6c, 0xa1, + 0x4b, 0x48, 0x66, 0x05, 0x2e, 0xfc, 0x47, 0x80, 0x0b, 0x0f, 0x8d, 0x9e, 0x05, 0x56, 0x94, 0x32, 0x03, 0x37, 0x92, + 0xdf, 0xec, 0x1d, 0x2c, 0xb3, 0xf4, 0x96, 0xf0, 0x84, 0xb2, 0x36, 0xca, 0x02, 0x9b, 0x60, 0x9f, 0x1a, 0xa2, 0x3d, + 0x74, 0xfc, 0xa0, 0xfd, 0x09, 0xa7, 0x7f, 0x7f, 0xea, 0x09, 0x1a, 0x0b, 0x3e, 0x46, 0x16, 0xf1, 0x87, 0x69, 0x2f, + 0xdd, 0xed, 0x67, 0x86, 0x46, 0x88, 0x20, 0xde, 0x9a, 0xa5, 0x33, 0x52, 0x49, 0xa5, 0x99, 0x6a, 0xde, 0xef, 0x40, + 0xb0, 0x5b, 0x52, 0x82, 0xdd, 0x08, 0x8b, 0x27, 0xde, 0xb3, 0xf6, 0xe5, 0x05, 0x31, 0x61, 0x31, 0xc5, 0x64, 0xed, + 0x11, 0x40, 0xf1, 0x2e, 0xdc, 0x44, 0x94, 0x74, 0xe5, 0xc7, 0x22, 0x19, 0xc9, 0x9f, 0x49, 0x34, 0x17, 0x49, 0x49, + 0xaf, 0xdd, 0x65, 0x7a, 0xb6, 0x02, 0x55, 0x79, 0xbb, 0xe7, 0x46, 0xed, 0x11, 0x36, 0xbe, 0x1b, 0xd4, 0x81, 0x0f, + 0xc8, 0xf5, 0x2c, 0x71, 0x78, 0xff, 0x12, 0x06, 0x34, 0x14, 0xcb, 0x96, 0x10, 0x17, 0x39, 0xee, 0xf3, 0x95, 0xfa, + 0xc0, 0x38, 0x21, 0x2c, 0xf0, 0xf5, 0x22, 0xd0, 0x9a, 0x83, 0xc4, 0xf7, 0x2b, 0x7e, 0xe1, 0x89, 0x82, 0xfa, 0x78, + 0x71, 0xda, 0x3f, 0xea, 0x4d, 0x5f, 0xd5, 0x4c, 0x93, 0xf3, 0x89, 0x90, 0x50, 0x3e, 0xcf, 0xb3, 0x50, 0x3a, 0x86, + 0x49, 0x8e, 0x4f, 0xfa, 0xe2, 0x92, 0x2e, 0x7c, 0x0c, 0xab, 0xf6, 0x57, 0xa3, 0xb0, 0xea, 0xe2, 0x7d, 0xd2, 0x67, + 0xf4, 0xcb, 0x15, 0x56, 0x8d, 0xf6, 0x0b, 0x17, 0x46, 0x75, 0x28, 0xa9, 0xaf, 0x80, 0x00, 0x06, 0xee, 0x70, 0xc7, + 0xaf, 0x13, 0x35, 0x0d, 0x69, 0xb5, 0x86, 0x20, 0x17, 0xfa, 0x0f, 0x7e, 0x07, 0xbf, 0x6f, 0x39, 0x48, 0x22, 0x07, + 0x1a, 0x3a, 0x94, 0x06, 0x58, 0x69, 0x50, 0x19, 0x54, 0x90, 0xe1, 0xd1, 0xd9, 0x29, 0x72, 0x4b, 0xa2, 0x0a, 0x0c, + 0x35, 0xf5, 0xd4, 0x51, 0x99, 0x00, 0xaf, 0x40, 0xed, 0x07, 0x67, 0x15, 0x21, 0xfa, 0x6b, 0x49, 0x0b, 0xea, 0x14, + 0xe9, 0xf3, 0x4b, 0x0b, 0x40, 0xec, 0x2f, 0x22, 0xdd, 0x69, 0xf8, 0xa4, 0xee, 0x3b, 0x32, 0x00, 0xae, 0x7c, 0x03, + 0x22, 0x20, 0x92, 0xb8, 0xb3, 0x0c, 0x5a, 0xc9, 0x4f, 0xe4, 0x3a, 0x27, 0x51, 0xaa, 0x8b, 0xb2, 0x02, 0x74, 0x6b, + 0x28, 0xe9, 0x2f, 0xda, 0xd1, 0x84, 0xd7, 0xcf, 0x77, 0x38, 0x16, 0xe8, 0x9f, 0x8e, 0xff, 0xb5, 0x40, 0x5a, 0x1a, + 0x02, 0xd7, 0x5b, 0x65, 0x18, 0xd4, 0x77, 0x8a, 0xc0, 0xbc, 0x44, 0xa5, 0x01, 0x25, 0x01, 0xeb, 0x95, 0xfa, 0xfb, + 0x6f, 0x75, 0xf4, 0x15, 0xa8, 0xdd, 0x3a, 0xfa, 0xa9, 0xe7, 0x7b, 0xf8, 0x83, 0xf4, 0x56, 0x3b, 0x40, 0x7f, 0x9c, + 0x67, 0xaa, 0x3f, 0x74, 0x76, 0x3d, 0x62, 0x30, 0x27, 0xe0, 0x82, 0x8c, 0x73, 0x92, 0x1f, 0xc1, 0x56, 0xfc, 0x0f, + 0x8b, 0x54, 0x63, 0xd2, 0xfd, 0xa3, 0x15, 0x78, 0x0d, 0x5d, 0x6a, 0x72, 0x4e, 0xe1, 0x0a, 0xa8, 0x1c, 0xaa, 0xeb, + 0x9c, 0x8a, 0x95, 0x64, 0xe6, 0xbf, 0xac, 0x60, 0xf3, 0xa8, 0x14, 0xa7, 0xcb, 0x0f, 0xaf, 0x5c, 0x62, 0xed, 0xc9, + 0xc5, 0x2f, 0x6e, 0x18, 0x9f, 0x52, 0xef, 0xce, 0xf7, 0xd4, 0x93, 0x0f, 0x3b, 0x26, 0x3f, 0xc0, 0x4f, 0x1f, 0xf6, + 0x9d, 0xe5, 0x94, 0x4f, 0x3f, 0xf9, 0xa5, 0xdf, 0xea, 0xd7, 0x3c, 0xf7, 0x03, 0x77, 0x66, 0x3b, 0xec, 0x1f, 0x09, + 0x6f, 0xa6, 0xcb, 0xbb, 0x86, 0x65, 0xeb, 0x15, 0x93, 0xaf, 0x97, 0x42, 0x9f, 0xfe, 0xe2, 0xfa, 0xa3, 0x07, 0xee, + 0x3d, 0x87, 0x92, 0x66, 0x1c, 0xd5, 0xb9, 0x39, 0x6b, 0xb0, 0xa5, 0x6d, 0x1c, 0x4d, 0x82, 0xad, 0x81, 0x30, 0x19, + 0x1a, 0xa1, 0x89, 0x0a, 0x7a, 0xed, 0x82, 0x52, 0xc1, 0x50, 0x9a, 0xe3, 0xb8, 0x7e, 0x32, 0x09, 0x84, 0xda, 0x22, + 0xa2, 0x6e, 0x4d, 0xab, 0x6c, 0x7c, 0xb0, 0x10, 0x70, 0x06, 0x15, 0xc6, 0x90, 0xdc, 0xeb, 0x0e, 0x04, 0xa6, 0x90, + 0x34, 0xc4, 0xf8, 0x53, 0xf9, 0x38, 0x8a, 0xe2, 0xba, 0x0a, 0x5f, 0x1f, 0xdc, 0x74, 0xe3, 0x94, 0xa3, 0x84, 0x8a, + 0xf2, 0x8e, 0x04, 0x6a, 0x8a, 0x16, 0x6c, 0x29, 0x32, 0x97, 0x23, 0x16, 0xc9, 0x9d, 0xc6, 0xe5, 0x18, 0x42, 0x41, + 0x3a, 0xd0, 0xe9, 0xc4, 0x4a, 0x21, 0xb1, 0x55, 0x21, 0x24, 0x08, 0xcd, 0x6e, 0xcb, 0x6b, 0x15, 0x50, 0x4e, 0xea, + 0xdf, 0xd3, 0x16, 0xf8, 0xba, 0x97, 0xe7, 0xf8, 0xa6, 0x95, 0xce, 0xab, 0xdc, 0xf3, 0xfc, 0xe0, 0xf9, 0xed, 0xf6, + 0x7b, 0x7c, 0x34, 0x63, 0xd5, 0x84, 0x8d, 0x1f, 0xf7, 0x31, 0x21, 0xd4, 0x02, 0x55, 0x04, 0x08, 0xad, 0xb2, 0x06, + 0xd6, 0x75, 0xc8, 0x2c, 0xa1, 0xe1, 0xeb, 0x9f, 0x3a, 0xe4, 0x88, 0x1b, 0x6c, 0xc2, 0x9b, 0xb0, 0xc8, 0xc3, 0x51, + 0x47, 0x7b, 0x03, 0xae, 0xb5, 0x70, 0x40, 0x29, 0x50, 0x67, 0x4b, 0xce, 0x12, 0x41, 0xd4, 0x2d, 0xb3, 0x30, 0x55, + 0xe9, 0x2c, 0xaf, 0x3c, 0x3f, 0x82, 0x5e, 0xb3, 0xbf, 0xd6, 0x49, 0xf0, 0xe4, 0xe9, 0x6c, 0x69, 0x6d, 0x43, 0x81, + 0x9b, 0xd2, 0x2a, 0x9f, 0xac, 0x6d, 0x3c, 0xfa, 0xb1, 0x4f, 0x92, 0x12, 0xba, 0xc4, 0x27, 0x41, 0xea, 0x93, 0xbc, + 0x4b, 0x4b, 0xd7, 0x87, 0x2e, 0xb9, 0x36, 0x54, 0x35, 0x77, 0x23, 0xa6, 0xf2, 0xc3, 0x1b, 0x9a, 0x77, 0xf2, 0x2d, + 0xd2, 0xfd, 0x00, 0xff, 0xfb, 0xb0, 0x33, 0x4e, 0xf9, 0xef, 0x27, 0xff, 0x94, 0x47, 0x76, 0x1d, 0x42, 0xb5, 0x97, + 0x29, 0x78, 0xdf, 0xe6, 0xa3, 0x5f, 0xae, 0xad, 0x46, 0x71, 0x3d, 0xfe, 0xd9, 0xaf, 0x42, 0xf5, 0xf4, 0x17, 0xcb, + 0x8c, 0x4b, 0xdf, 0x4e, 0xd9, 0xcc, 0x2f, 0x8e, 0x39, 0xf3, 0xdc, 0x70, 0xd3, 0x2d, 0xda, 0x90, 0x5e, 0x21, 0xec, + 0xd4, 0xe5, 0x49, 0x17, 0x7d, 0xae, 0x88, 0x7b, 0x71, 0x12, 0x45, 0x0f, 0x51, 0x16, 0x87, 0xd6, 0x94, 0xd3, 0xcc, + 0xec, 0xa3, 0x58, 0x37, 0x1d, 0x29, 0x60, 0x08, 0x17, 0xb0, 0xe0, 0x17, 0x9f, 0x69, 0x3c, 0x63, 0x73, 0xe6, 0xf0, + 0x3f, 0x78, 0x50, 0xb2, 0xcc, 0x19, 0x0f, 0xf2, 0x55, 0x2d, 0x59, 0xbe, 0xe9, 0x60, 0xed, 0xe4, 0x7e, 0x00, 0x7f, + 0x99, 0x59, 0xd3, 0xe1, 0x92, 0x7e, 0x9b, 0xdd, 0xfa, 0x83, 0xd6, 0x52, 0x1e, 0x74, 0x10, 0xb4, 0xd6, 0xa7, 0xfd, + 0x4d, 0x4c, 0xea, 0x03, 0xbe, 0xd5, 0x1c, 0x6c, 0x97, 0xee, 0x9c, 0x47, 0x33, 0x45, 0xfb, 0x95, 0x69, 0x6e, 0xef, + 0xf0, 0x3a, 0x13, 0xad, 0x88, 0xb8, 0x3c, 0x54, 0xf1, 0xd9, 0x0d, 0xe7, 0xc8, 0xcc, 0xee, 0x70, 0x9f, 0x1e, 0x28, + 0x1e, 0x90, 0xcd, 0x15, 0xda, 0x79, 0xf0, 0x10, 0xab, 0xcd, 0x3c, 0xd4, 0x8e, 0xf9, 0xde, 0x74, 0x6e, 0x18, 0xaf, + 0x0c, 0xae, 0x49, 0xa3, 0xe0, 0x7a, 0xbe, 0x35, 0xde, 0x3d, 0x93, 0x3a, 0xea, 0x6c, 0x47, 0x1b, 0xd0, 0x2a, 0xb5, + 0x6b, 0xb2, 0x7a, 0x47, 0xb4, 0x9b, 0xc5, 0xbf, 0x5f, 0x85, 0xeb, 0x7b, 0x07, 0x7b, 0x7e, 0xe3, 0x30, 0x94, 0xa3, + 0x0f, 0xb1, 0xbb, 0xca, 0xff, 0x14, 0x2d, 0xab, 0x6e, 0xad, 0xbb, 0x3a, 0xed, 0xde, 0x49, 0xba, 0xa4, 0xac, 0x8f, + 0x60, 0x30, 0x39, 0x39, 0x77, 0x1d, 0xa5, 0x42, 0x3f, 0x86, 0x9f, 0xa4, 0xbc, 0x3c, 0x5d, 0x1f, 0xd6, 0x79, 0x09, + 0xbd, 0x70, 0xd8, 0x49, 0xeb, 0x1f, 0xbf, 0x5f, 0xe0, 0x13, 0x98, 0x9a, 0x5e, 0x37, 0xa3, 0x61, 0x51, 0x48, 0xda, + 0x1f, 0xc3, 0xce, 0xa5, 0xf1, 0x1b, 0xaa, 0xff, 0xe0, 0xa8, 0x22, 0xb8, 0x3a, 0x2e, 0xb5, 0xa2, 0x8a, 0xef, 0xb3, + 0x4d, 0xdd, 0x62, 0x41, 0xd8, 0xbf, 0x13, 0x39, 0x6a, 0x0c, 0x35, 0x55, 0x5c, 0x5b, 0x54, 0x04, 0x04, 0x89, 0x8b, + 0xfc, 0x5c, 0x3c, 0xac, 0xba, 0x17, 0xbc, 0x29, 0x2a, 0xd0, 0xed, 0x2b, 0x04, 0x55, 0x4e, 0x0a, 0xa6, 0x99, 0xec, + 0x5c, 0x17, 0x7d, 0x5a, 0x7f, 0xda, 0x52, 0xe8, 0x81, 0x70, 0x4c, 0x58, 0x82, 0x85, 0x8a, 0x3b, 0xc8, 0xc1, 0xd3, + 0xe3, 0xf6, 0x82, 0x1f, 0xab, 0x18, 0xed, 0xf8, 0x44, 0x32, 0x07, 0xeb, 0x92, 0x71, 0x03, 0x0f, 0x8e, 0x95, 0x86, + 0xe1, 0x58, 0x01, 0x70, 0x68, 0x76, 0x75, 0x22, 0x7f, 0x68, 0x46, 0xf0, 0x57, 0x24, 0x43, 0xeb, 0x12, 0x35, 0x85, + 0x06, 0xd6, 0x32, 0x7a, 0xae, 0x98, 0x78, 0x31, 0x05, 0x9d, 0x80, 0x20, 0xcb, 0x93, 0x43, 0x7a, 0xb8, 0x8b, 0x64, + 0x51, 0xbd, 0x67, 0x17, 0x8b, 0x48, 0x97, 0x81, 0x06, 0xd3, 0x20, 0xa4, 0xc3, 0x6a, 0xba, 0x6a, 0x6f, 0xee, 0x91, + 0xa5, 0x79, 0x4c, 0x8c, 0x95, 0x86, 0x9e, 0xd5, 0x38, 0xb0, 0xe1, 0x96, 0x09, 0x0b, 0x87, 0xc4, 0xd1, 0xc9, 0x61, + 0x15, 0x91, 0xa0, 0x59, 0xdf, 0x82, 0xac, 0x74, 0x00, 0x1b, 0xf0, 0x30, 0xaf, 0x2e, 0xb1, 0xbb, 0x6d, 0xcd, 0x22, + 0x9e, 0x59, 0x86, 0x61, 0x5d, 0xfc, 0xa7, 0xae, 0x39, 0x61, 0xfb, 0xdf, 0x3c, 0x1e, 0x1e, 0x8b, 0xae, 0x36, 0x16, + 0x4b, 0xb1, 0x07, 0x3d, 0xa2, 0xfb, 0xc3, 0xc7, 0xc3, 0x9b, 0xc0, 0x24, 0x3e, 0xe9, 0x67, 0x16, 0x34, 0x13, 0xdb, + 0xd9, 0xe8, 0x09, 0xfb, 0x68, 0x9b, 0x62, 0x87, 0x54, 0x60, 0x51, 0xd3, 0xd9, 0x09, 0xd5, 0x69, 0x68, 0x24, 0x69, + 0x86, 0x50, 0x58, 0xed, 0x0d, 0x7e, 0x11, 0x62, 0x6f, 0xaf, 0xfb, 0x7b, 0x0e, 0x62, 0x2d, 0x06, 0xa8, 0x41, 0x4d, + 0x8b, 0xc4, 0xee, 0xd2, 0xdd, 0x33, 0x5e, 0x02, 0x55, 0x1d, 0x95, 0x43, 0x0a, 0xb3, 0x5c, 0x8c, 0x68, 0x43, 0x27, + 0x59, 0x62, 0x1d, 0x93, 0xa9, 0x94, 0xb8, 0xfe, 0xbb, 0x04, 0x12, 0x14, 0x1e, 0xd9, 0x91, 0x70, 0xcb, 0x9f, 0x27, + 0xfc, 0xf9, 0x0b, 0xb6, 0x1a, 0x6c, 0xa7, 0x00, 0x5c, 0x3e, 0xfa, 0xfc, 0x0e, 0xe4, 0x2f, 0x54, 0x0c, 0xfc, 0xbc, + 0x70, 0xc2, 0x0b, 0xf0, 0x4f, 0xb0, 0x14, 0x89, 0xe9, 0x5e, 0x4a, 0xf3, 0x14, 0x6b, 0x98, 0x40, 0xaf, 0xcb, 0x7e, + 0xd9, 0x02, 0x87, 0x11, 0xc4, 0xa5, 0xee, 0xdb, 0x05, 0xb4, 0x51, 0x01, 0x28, 0x8b, 0x6a, 0xe0, 0x58, 0xc7, 0xdb, + 0x26, 0x45, 0xdc, 0xf4, 0xab, 0x04, 0x99, 0xd1, 0x2b, 0xb1, 0xb8, 0x0c, 0x5d, 0x5b, 0x39, 0x4e, 0x53, 0xf4, 0x99, + 0x0c, 0x36, 0xcc, 0x3a, 0x15, 0x81, 0x99, 0xf4, 0xf0, 0xa0, 0x6f, 0xd3, 0x9a, 0x6f, 0xd7, 0xf5, 0xf2, 0x6e, 0x93, + 0x5f, 0x77, 0x7c, 0xf9, 0xea, 0xc9, 0x85, 0x94, 0x48, 0x5b, 0xa0, 0x5b, 0xaa, 0xa7, 0xc5, 0xd6, 0xba, 0xd1, 0xf4, + 0xb8, 0xc2, 0xf0, 0x50, 0x19, 0x4d, 0xfd, 0x69, 0x52, 0x98, 0xcd, 0x16, 0xdd, 0xe8, 0x63, 0xff, 0x4a, 0x02, 0x0d, + 0x96, 0x2d, 0xf5, 0xde, 0x9b, 0x05, 0x0b, 0x7a, 0xeb, 0x20, 0x6b, 0xb9, 0xca, 0x91, 0x7b, 0x42, 0x51, 0x5d, 0x20, + 0x28, 0xf2, 0xb0, 0x48, 0x2b, 0xdc, 0x99, 0xbd, 0xcc, 0x06, 0xf4, 0x21, 0xe3, 0x4a, 0xee, 0x9c, 0x03, 0x25, 0xd3, + 0xe6, 0x4d, 0x7a, 0xef, 0x2f, 0x76, 0x7a, 0xec, 0xde, 0x41, 0x86, 0xe5, 0xff, 0x8d, 0xea, 0x6a, 0xbe, 0x25, 0x45, + 0x0a, 0x8f, 0xa2, 0x7d, 0xac, 0x4a, 0xe1, 0x3b, 0xc2, 0x56, 0x0a, 0x54, 0x07, 0xda, 0x57, 0xa4, 0x2e, 0x61, 0x6e, + 0xd6, 0x41, 0x60, 0x65, 0x3a, 0xfc, 0x2b, 0x52, 0x03, 0x7e, 0xb3, 0x05, 0x44, 0x88, 0x9d, 0xec, 0x21, 0x7c, 0x14, + 0x00, 0xc7, 0x15, 0x00, 0xcd, 0x88, 0xdc, 0xe0, 0xe4, 0xe0, 0x21, 0xee, 0x8f, 0x57, 0x77, 0x59, 0x51, 0xe1, 0xbd, + 0x45, 0x58, 0x07, 0xf0, 0xd7, 0x72, 0xb8, 0x9e, 0x97, 0xfe, 0xc2, 0x87, 0xea, 0x09, 0x2a, 0x77, 0xee, 0xeb, 0x82, + 0xa1, 0x3c, 0x95, 0x38, 0x36, 0xd0, 0x55, 0x79, 0x6b, 0xaf, 0xb4, 0x99, 0x04, 0xbb, 0x1b, 0xa1, 0xa9, 0x0e, 0xa3, + 0x21, 0x6e, 0x7e, 0x5b, 0xd9, 0xfa, 0xc3, 0xc5, 0xed, 0x9f, 0xdf, 0x9d, 0x0b, 0x3f, 0xc0, 0x25, 0x06, 0x6c, 0xea, + 0x20, 0xc4, 0x7e, 0xe7, 0xdc, 0x04, 0x42, 0x88, 0x3d, 0x06, 0x93, 0x29, 0x62, 0x26, 0x34, 0x95, 0xa3, 0x10, 0x82, + 0x49, 0xde, 0x52, 0x6d, 0xc8, 0x85, 0x07, 0xd9, 0x21, 0x33, 0x65, 0x6a, 0x13, 0xc2, 0x26, 0xdc, 0xb6, 0x8d, 0x75, + 0x73, 0x65, 0x31, 0x36, 0x77, 0x5b, 0x7d, 0x61, 0x45, 0xe8, 0xed, 0x5d, 0xdf, 0xea, 0xdc, 0xee, 0x9a, 0xfc, 0x08, + 0x3a, 0x64, 0xef, 0x8a, 0x0a, 0x07, 0xbb, 0x93, 0xd3, 0x45, 0xd2, 0x6c, 0x08, 0x24, 0x16, 0x08, 0x81, 0x5f, 0x68, + 0x68, 0x86, 0x8c, 0xf1, 0xc8, 0x2b, 0xdf, 0xb8, 0x2a, 0x3b, 0xf6, 0x12, 0x54, 0xbd, 0x87, 0x98, 0x73, 0x9f, 0x86, + 0x75, 0x1c, 0xc9, 0x61, 0xea, 0x66, 0xe8, 0x87, 0xb0, 0xdd, 0x24, 0x71, 0x99, 0x8e, 0xe4, 0x3e, 0xbb, 0xbf, 0xf4, + 0x7c, 0x37, 0x6a, 0xe1, 0x22, 0xda, 0x14, 0x50, 0x8f, 0x48, 0x76, 0xea, 0xda, 0x8b, 0xf4, 0x63, 0x8e, 0xc4, 0x5d, + 0xd9, 0x5a, 0xd2, 0xfc, 0x28, 0xa0, 0x21, 0x3a, 0xa3, 0xf8, 0xd2, 0xcf, 0x46, 0x7b, 0x9a, 0xff, 0xea, 0x87, 0xab, + 0xb3, 0xff, 0xd1, 0xae, 0x44, 0xa1, 0xe2, 0xc9, 0x31, 0xd0, 0x36, 0x5f, 0x92, 0x72, 0x1d, 0x4b, 0x64, 0x9c, 0xd7, + 0x86, 0x77, 0xcb, 0x06, 0x82, 0x3d, 0xea, 0x1c, 0xd1, 0xfa, 0x05, 0x76, 0x08, 0xdd, 0xd9, 0xd1, 0x40, 0xd7, 0x1e, + 0xfa, 0xe7, 0xfa, 0xdf, 0xfe, 0xe7, 0xd6, 0xae, 0x52, 0x6a, 0xa9, 0x1e, 0x72, 0x47, 0xe5, 0x25, 0x09, 0xd1, 0x01, + 0xa8, 0x48, 0x23, 0x70, 0x5f, 0x6e, 0xad, 0xf9, 0x5d, 0x59, 0x67, 0xfe, 0xa7, 0x05, 0x63, 0x24, 0x72, 0x7a, 0x43, + 0x48, 0xd8, 0x84, 0x24, 0x8e, 0x9d, 0xeb, 0x8c, 0xb3, 0xa4, 0x69, 0x11, 0xba, 0xba, 0x53, 0x8f, 0x04, 0xcb, 0x93, + 0x36, 0xcb, 0xb8, 0x21, 0x5b, 0x6e, 0xbb, 0x49, 0xfa, 0xe8, 0xe7, 0xae, 0xd5, 0x5c, 0x01, 0x41, 0x50, 0xb5, 0x2c, + 0x2f, 0x8d, 0xc8, 0xab, 0x0d, 0xf7, 0x65, 0xf9, 0x6b, 0xf7, 0x53, 0x18, 0x69, 0x51, 0x1b, 0x9c, 0x72, 0xd6, 0xad, + 0x7a, 0x50, 0x2d, 0x45, 0x19, 0x59, 0xf5, 0x21, 0xfa, 0x4f, 0x70, 0x83, 0x97, 0x77, 0x37, 0x47, 0x2f, 0xa6, 0x16, + 0x5e, 0x70, 0x24, 0x67, 0xe2, 0xee, 0xfc, 0x28, 0x4b, 0xec, 0x65, 0xd0, 0x97, 0x88, 0xea, 0x9c, 0x18, 0x6f, 0x8a, + 0x67, 0x06, 0xa2, 0xdb, 0xd4, 0xf7, 0x4d, 0x70, 0x90, 0xf1, 0x0d, 0xb2, 0x3f, 0x27, 0x48, 0x4e, 0x7d, 0xa2, 0xf1, + 0x32, 0x98, 0xb6, 0x23, 0x05, 0xc7, 0xc7, 0x76, 0x0f, 0x38, 0xde, 0xc8, 0xe5, 0x42, 0xf5, 0xad, 0xbb, 0xff, 0xc3, + 0xea, 0x61, 0x76, 0x06, 0xc7, 0x9d, 0x4d, 0xcb, 0x04, 0xaf, 0x58, 0xe2, 0x4f, 0xb5, 0x89, 0xdd, 0x9e, 0x4d, 0xba, + 0xe3, 0x72, 0x7b, 0x38, 0xbb, 0x0b, 0xad, 0x51, 0xee, 0x36, 0x7f, 0x01, 0x48, 0x21, 0xd0, 0x86, 0xfd, 0xa2, 0x70, + 0x10, 0xd4, 0x00, 0x1f, 0x00, 0x23, 0xb4, 0xc4, 0x62, 0x45, 0x9e, 0x68, 0x28, 0xad, 0x72, 0x46, 0x4c, 0x83, 0xe7, + 0x83, 0x77, 0x95, 0xc4, 0xa5, 0xf4, 0x77, 0x61, 0x73, 0x4b, 0x89, 0xcf, 0x9c, 0x7e, 0x7c, 0x9b, 0xea, 0xbb, 0x2e, + 0x39, 0x5b, 0xbe, 0xf7, 0x9c, 0x56, 0x1a, 0x3a, 0xb8, 0x83, 0x8f, 0xd5, 0x16, 0x42, 0x36, 0x6e, 0xa0, 0xdb, 0xec, + 0x0f, 0xb9, 0x67, 0xe7, 0xa9, 0x62, 0x4f, 0xb2, 0x5c, 0x58, 0xac, 0x08, 0x17, 0xae, 0xde, 0x2d, 0x02, 0x58, 0x90, + 0xef, 0x43, 0x1f, 0xcb, 0xac, 0xe4, 0xc7, 0x0f, 0xfc, 0xcf, 0x83, 0x00, 0x8b, 0x92, 0xe5, 0x34, 0xa1, 0xca, 0x9d, + 0x41, 0xcc, 0xb3, 0x9e, 0x91, 0x35, 0x9b, 0x7c, 0xe8, 0x00}; + +// Backwards compatibility alias +#define INDEX_GZ INDEX_BR + +#endif // USE_WEBSERVER_GZIP + +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 0c22c2f08d..0525c93096 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,6 +1,7 @@ #include "web_server.h" #ifdef USE_WEBSERVER #include "esphome/components/json/json_util.h" +#include "esphome/core/progmem.h" #include "esphome/components/network/util.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" @@ -28,6 +29,10 @@ #include "esphome/components/climate/climate.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif + #ifdef USE_WEBSERVER_LOCAL #if USE_WEBSERVER_VERSION == 2 #include "server_index_v2.h" @@ -36,8 +41,7 @@ #endif #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { static const char *const TAG = "web_server"; @@ -45,70 +49,145 @@ static const char *const TAG = "web_server"; static constexpr size_t PSTR_LOCAL_SIZE = 18; #define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1) -#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS -static const char *const HEADER_PNA_NAME = "Private-Network-Access-Name"; -static const char *const HEADER_PNA_ID = "Private-Network-Access-ID"; -static const char *const HEADER_CORS_REQ_PNA = "Access-Control-Request-Private-Network"; -static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network"; -#endif - // Parse URL and return match info -static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) { - UrlMatch match{}; +// URL formats (disambiguated by HTTP method for 3-segment case): +// GET /{domain}/{entity_name} - main device state +// POST /{domain}/{entity_name}/{action} - main device action +// GET /{domain}/{device_name}/{entity_name} - sub-device state (USE_DEVICES only) +// POST /{domain}/{device_name}/{entity_name}/{action} - sub-device action (USE_DEVICES only) +static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain, bool is_post = false) { + // URL must start with '/' and have content after it + if (url_len < 2 || url_ptr[0] != '/') + return UrlMatch{}; - // URL must start with '/' - if (url_len < 2 || url_ptr[0] != '/') { - return match; - } - - // Skip leading '/' - const char *start = url_ptr + 1; + const char *p = url_ptr + 1; const char *end = url_ptr + url_len; - // Find domain (everything up to next '/' or end) - const char *domain_end = (const char *) memchr(start, '/', end - start); - if (!domain_end) { - // No second slash found - original behavior returns invalid - return match; - } + // Helper to find next segment: returns pointer after '/' or nullptr if no more slashes + auto next_segment = [&end](const char *start) -> const char * { + const char *slash = (const char *) memchr(start, '/', end - start); + return slash ? slash + 1 : nullptr; + }; - // Set domain - match.domain = start; - match.domain_len = domain_end - start; + // Helper to make StringRef from segment start to next segment (or end) + auto make_ref = [&end](const char *start, const char *next_start) -> StringRef { + return StringRef(start, (next_start ? next_start - 1 : end) - start); + }; + + // Parse domain segment + const char *s1 = p; + const char *s2 = next_segment(s1); + + // Must have domain with trailing slash + if (!s2) + return UrlMatch{}; + + UrlMatch match{}; + match.domain = make_ref(s1, s2); match.valid = true; - if (only_domain) { + if (only_domain || s2 >= end) return match; - } - // Parse ID if present - if (domain_end + 1 >= end) { - return match; // Nothing after domain slash - } + // Parse remaining segments only when needed + const char *s3 = next_segment(s2); + const char *s4 = s3 ? next_segment(s3) : nullptr; - const char *id_start = domain_end + 1; - const char *id_end = (const char *) memchr(id_start, '/', end - id_start); + StringRef seg2 = make_ref(s2, s3); + StringRef seg3 = s3 ? make_ref(s3, s4) : StringRef(); + StringRef seg4 = s4 ? make_ref(s4, nullptr) : StringRef(); - if (!id_end) { - // No more slashes, entire remaining string is ID - match.id = id_start; - match.id_len = end - id_start; - return match; - } + // Reject empty segments + if (seg2.empty() || (s3 && seg3.empty()) || (s4 && seg4.empty())) + return UrlMatch{}; - // Set ID - match.id = id_start; - match.id_len = id_end - id_start; - - // Parse method if present - if (id_end + 1 < end) { - match.method = id_end + 1; - match.method_len = end - (id_end + 1); + // Interpret based on segment count + if (!s3) { + // 1 segment after domain: /{domain}/{entity} + match.id = seg2; + } else if (!s4) { + // 2 segments after domain: /{domain}/{X}/{Y} + // HTTP method disambiguates: GET = device/entity, POST = entity/action + if (is_post) { + match.id = seg2; + match.method = seg3; + return match; + } +#ifdef USE_DEVICES + match.device_name = seg2; + match.id = seg3; +#else + return UrlMatch{}; // 3-segment GET not supported without USE_DEVICES +#endif + } else { + // 3 segments after domain: /{domain}/{device}/{entity}/{action} +#ifdef USE_DEVICES + if (!is_post) { + return UrlMatch{}; // 4-segment GET not supported (action requires POST) + } + match.device_name = seg2; + match.id = seg3; + match.method = seg4; +#else + return UrlMatch{}; // Not supported without USE_DEVICES +#endif } return match; } +EntityMatchResult UrlMatch::match_entity(EntityBase *entity) const { + EntityMatchResult result{false, this->method.empty()}; + +#ifdef USE_DEVICES + Device *entity_device = entity->get_device(); + bool url_has_device = !this->device_name.empty(); + bool entity_has_device = (entity_device != nullptr); + + // Device matching: URL device segment must match entity's device + if (url_has_device != entity_has_device) { + return result; // Mismatch: one has device, other doesn't + } + if (url_has_device && this->device_name != entity_device->get_name()) { + return result; // Device name doesn't match + } +#endif + + // Try matching by entity name (new format) + if (this->id == entity->get_name()) { + result.matched = true; + return result; + } + + // Fall back to object_id (deprecated format) + char object_id_buf[OBJECT_ID_MAX_LEN]; + StringRef object_id = entity->get_object_id_to(object_id_buf); + if (this->id == object_id) { + result.matched = true; + // Log deprecation warning +#ifdef USE_DEVICES + Device *device = entity->get_device(); + if (device != nullptr) { + ESP_LOGW(TAG, + "Deprecated URL format: /%.*s/%.*s/%.*s - use entity name '/%.*s/%s/%s' instead. " + "Object ID URLs will be removed in 2026.7.0.", + (int) this->domain.size(), this->domain.c_str(), (int) this->device_name.size(), + this->device_name.c_str(), (int) this->id.size(), this->id.c_str(), (int) this->domain.size(), + this->domain.c_str(), device->get_name(), entity->get_name().c_str()); + } else +#endif + { + ESP_LOGW(TAG, + "Deprecated URL format: /%.*s/%.*s - use entity name '/%.*s/%s' instead. " + "Object ID URLs will be removed in 2026.7.0.", + (int) this->domain.size(), this->domain.c_str(), (int) this->id.size(), this->id.c_str(), + (int) this->domain.size(), this->domain.c_str(), entity->get_name().c_str()); + } + } + + return result; +} + #if !defined(USE_ESP32) && defined(USE_ARDUINO) // helper for allowing only unique entries in the queue void DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) { @@ -287,7 +366,9 @@ std::string WebServer::get_config_json() { JsonObject root = builder.root(); root[ESPHOME_F("title")] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); - root[ESPHOME_F("comment")] = App.get_comment_ref(); + char comment_buffer[ESPHOME_COMMENT_SIZE]; + App.get_comment_string(comment_buffer); + root[ESPHOME_F("comment")] = comment_buffer; #if defined(USE_WEBSERVER_OTA_DISABLED) || !defined(USE_WEBSERVER_OTA) root[ESPHOME_F("ota")] = false; // Note: USE_WEBSERVER_OTA_DISABLED only affects web_server, not captive_portal #else @@ -346,7 +427,11 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #else AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); #endif - response->addHeader("Content-Encoding", "gzip"); +#ifdef USE_WEBSERVER_GZIP + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); +#else + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("br")); +#endif request->send(response); } #elif USE_WEBSERVER_VERSION >= 2 @@ -366,10 +451,10 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse(200, ""); - response->addHeader(HEADER_CORS_ALLOW_PNA, "true"); - response->addHeader(HEADER_PNA_NAME, App.get_name().c_str()); + response->addHeader(ESPHOME_F("Access-Control-Allow-Private-Network"), ESPHOME_F("true")); + response->addHeader(ESPHOME_F("Private-Network-Access-Name"), App.get_name().c_str()); char mac_s[18]; - response->addHeader(HEADER_PNA_ID, get_mac_address_pretty_into_buffer(mac_s)); + response->addHeader(ESPHOME_F("Private-Network-Access-ID"), get_mac_address_pretty_into_buffer(mac_s)); request->send(response); } #endif @@ -383,7 +468,7 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #endif @@ -397,19 +482,61 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #endif // Helper functions to reduce code size by avoiding macro expansion +// Build unique id as: {domain}/{device_name}/{entity_name} or {domain}/{entity_name} +// Uses names (not object_id) to avoid UTF-8 collision issues static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) { - char id_buf[160]; // object_id can be up to 128 chars + prefix + dash + null - const auto &object_id = obj->get_object_id(); - snprintf(id_buf, sizeof(id_buf), "%s-%s", prefix, object_id.c_str()); + const StringRef &name = obj->get_name(); + size_t prefix_len = strlen(prefix); + size_t name_len = name.size(); + +#ifdef USE_DEVICES + Device *device = obj->get_device(); + const char *device_name = device ? device->get_name() : nullptr; + size_t device_len = device_name ? strlen(device_name) : 0; +#endif + + // Build id into stack buffer - ArduinoJson copies the string + // Format: {prefix}/{device?}/{name} + // Buffer sizes use constants from entity_base.h validated in core/config.py + // Note: Device name uses ESPHOME_FRIENDLY_NAME_MAX_LEN (sub-device max 120), not ESPHOME_DEVICE_NAME_MAX_LEN + // (hostname) +#ifdef USE_DEVICES + static constexpr size_t ID_BUF_SIZE = + ESPHOME_DOMAIN_MAX_LEN + 1 + ESPHOME_FRIENDLY_NAME_MAX_LEN + 1 + ESPHOME_FRIENDLY_NAME_MAX_LEN + 1; +#else + static constexpr size_t ID_BUF_SIZE = ESPHOME_DOMAIN_MAX_LEN + 1 + ESPHOME_FRIENDLY_NAME_MAX_LEN + 1; +#endif + char id_buf[ID_BUF_SIZE]; + char *p = id_buf; + memcpy(p, prefix, prefix_len); + p += prefix_len; + *p++ = '/'; +#ifdef USE_DEVICES + if (device_name) { + memcpy(p, device_name, device_len); + p += device_len; + *p++ = '/'; + } +#endif + memcpy(p, name.c_str(), name_len); + p[name_len] = '\0'; + root[ESPHOME_F("id")] = id_buf; + if (start_config == DETAIL_ALL) { - root[ESPHOME_F("name")] = obj->get_name(); + root[ESPHOME_F("domain")] = prefix; + root[ESPHOME_F("name")] = name; +#ifdef USE_DEVICES + if (device_name) { + root[ESPHOME_F("device")] = device_name; + } +#endif root[ESPHOME_F("icon")] = obj->get_icon_ref(); root[ESPHOME_F("entity_category")] = obj->get_entity_category(); bool is_disabled = obj->is_disabled_by_default(); @@ -428,7 +555,7 @@ static void set_json_value(JsonObject &root, EntityBase *obj, const char *prefix } template -static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const std::string &state, +static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const char *state, const T &value, JsonDetail start_config) { set_json_value(root, obj, prefix, value, start_config); root[ESPHOME_F("state")] = state; @@ -436,7 +563,7 @@ static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const c // Helper to get request detail parameter static JsonDetail get_request_detail(AsyncWebServerRequest *request) { - auto *param = request->getParam("detail"); + auto *param = request->getParam(ESPHOME_F("detail")); return (param && param->value() == "all") ? DETAIL_ALL : DETAIL_STATE; } @@ -448,12 +575,13 @@ void WebServer::on_sensor_update(sensor::Sensor *obj) { } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->sensor_json(obj, obj->state, detail); + std::string data = this->sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -461,19 +589,20 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::sensor_state_json_generator(WebServer *web_server, void *source) { - return web_server->sensor_json((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_STATE); + return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_STATE); } std::string WebServer::sensor_all_json_generator(WebServer *web_server, void *source) { - return web_server->sensor_json((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_ALL); + return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) { +std::string WebServer::sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); const auto uom_ref = obj->get_unit_of_measurement_ref(); - - std::string state = - std::isnan(value) ? "NA" : value_accuracy_with_uom_to_string(value, obj->get_accuracy_decimals(), uom_ref); + char buf[VALUE_ACCURACY_MAX_LEN]; + const char *state = std::isnan(value) + ? "NA" + : (value_accuracy_with_uom_to_buf(buf, value, obj->get_accuracy_decimals(), uom_ref), buf); set_json_icon_state_value(root, obj, "sensor", state, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -493,12 +622,13 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj) { } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->text_sensor_json(obj, obj->state, detail); + std::string data = this->text_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -506,19 +636,19 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const request->send(404); } std::string WebServer::text_sensor_state_json_generator(WebServer *web_server, void *source) { - return web_server->text_sensor_json((text_sensor::TextSensor *) (source), - ((text_sensor::TextSensor *) (source))->state, DETAIL_STATE); + return web_server->text_sensor_json_((text_sensor::TextSensor *) (source), + ((text_sensor::TextSensor *) (source))->state, DETAIL_STATE); } std::string WebServer::text_sensor_all_json_generator(WebServer *web_server, void *source) { - return web_server->text_sensor_json((text_sensor::TextSensor *) (source), - ((text_sensor::TextSensor *) (source))->state, DETAIL_ALL); + return web_server->text_sensor_json_((text_sensor::TextSensor *) (source), + ((text_sensor::TextSensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, - JsonDetail start_config) { +std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, + JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); - set_json_icon_state_value(root, obj, "text_sensor", value, value, start_config); + set_json_icon_state_value(root, obj, "text_sensor", value.c_str(), value.c_str(), start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); } @@ -535,12 +665,13 @@ void WebServer::on_switch_update(switch_::Switch *obj) { } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->switch_json(obj, obj->state, detail); + std::string data = this->switch_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -549,11 +680,11 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF }; SwitchAction action = NONE; - if (match.method_equals("toggle")) { + if (match.method_equals(ESPHOME_F("toggle"))) { action = TOGGLE; - } else if (match.method_equals("turn_on")) { + } else if (match.method_equals(ESPHOME_F("turn_on"))) { action = TURN_ON; - } else if (match.method_equals("turn_off")) { + } else if (match.method_equals(ESPHOME_F("turn_off"))) { action = TURN_OFF; } @@ -582,12 +713,12 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::switch_state_json_generator(WebServer *web_server, void *source) { - return web_server->switch_json((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_STATE); + return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_STATE); } std::string WebServer::switch_all_json_generator(WebServer *web_server, void *source) { - return web_server->switch_json((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_ALL); + return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_ALL); } -std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { +std::string WebServer::switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -604,13 +735,14 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail #ifdef USE_BUTTON void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->button_json(obj, detail); + std::string data = this->button_json_(obj, detail); request->send(200, "application/json", data.c_str()); - } else if (match.method_equals("press")) { + } else if (match.method_equals(ESPHOME_F("press"))) { this->defer([obj]() { obj->press(); }); request->send(200); return; @@ -622,12 +754,12 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::button_state_json_generator(WebServer *web_server, void *source) { - return web_server->button_json((button::Button *) (source), DETAIL_STATE); + return web_server->button_json_((button::Button *) (source), DETAIL_STATE); } std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) { - return web_server->button_json((button::Button *) (source), DETAIL_ALL); + return web_server->button_json_((button::Button *) (source), DETAIL_ALL); } -std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { +std::string WebServer::button_json_(button::Button *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -648,12 +780,13 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->binary_sensor_json(obj, obj->state, detail); + std::string data = this->binary_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -661,14 +794,14 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con request->send(404); } std::string WebServer::binary_sensor_state_json_generator(WebServer *web_server, void *source) { - return web_server->binary_sensor_json((binary_sensor::BinarySensor *) (source), - ((binary_sensor::BinarySensor *) (source))->state, DETAIL_STATE); + return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source), + ((binary_sensor::BinarySensor *) (source))->state, DETAIL_STATE); } std::string WebServer::binary_sensor_all_json_generator(WebServer *web_server, void *source) { - return web_server->binary_sensor_json((binary_sensor::BinarySensor *) (source), - ((binary_sensor::BinarySensor *) (source))->state, DETAIL_ALL); + return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source), + ((binary_sensor::BinarySensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { +std::string WebServer::binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -689,29 +822,30 @@ void WebServer::on_fan_update(fan::Fan *obj) { } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::Fan *obj : App.get_fans()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->fan_json(obj, detail); + std::string data = this->fan_json_(obj, detail); request->send(200, "application/json", data.c_str()); - } else if (match.method_equals("toggle")) { + } else if (match.method_equals(ESPHOME_F("toggle"))) { this->defer([obj]() { obj->toggle().perform(); }); request->send(200); } else { - bool is_on = match.method_equals("turn_on"); - bool is_off = match.method_equals("turn_off"); + bool is_on = match.method_equals(ESPHOME_F("turn_on")); + bool is_off = match.method_equals(ESPHOME_F("turn_off")); if (!is_on && !is_off) { request->send(404); return; } auto call = is_on ? obj->turn_on() : obj->turn_off(); - parse_int_param_(request, "speed_level", call, &decltype(call)::set_speed); + parse_int_param_(request, ESPHOME_F("speed_level"), call, &decltype(call)::set_speed); - if (request->hasParam("oscillation")) { - auto speed = request->getParam("oscillation")->value(); + if (request->hasParam(ESPHOME_F("oscillation"))) { + auto speed = request->getParam(ESPHOME_F("oscillation"))->value(); auto val = parse_on_off(speed.c_str()); switch (val) { case PARSE_ON: @@ -736,12 +870,12 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc request->send(404); } std::string WebServer::fan_state_json_generator(WebServer *web_server, void *source) { - return web_server->fan_json((fan::Fan *) (source), DETAIL_STATE); + return web_server->fan_json_((fan::Fan *) (source), DETAIL_STATE); } std::string WebServer::fan_all_json_generator(WebServer *web_server, void *source) { - return web_server->fan_json((fan::Fan *) (source), DETAIL_ALL); + return web_server->fan_json_((fan::Fan *) (source), DETAIL_ALL); } -std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { +std::string WebServer::fan_json_(fan::Fan *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -769,19 +903,20 @@ void WebServer::on_light_update(light::LightState *obj) { } void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->light_json(obj, detail); + std::string data = this->light_json_(obj, detail); request->send(200, "application/json", data.c_str()); - } else if (match.method_equals("toggle")) { + } else if (match.method_equals(ESPHOME_F("toggle"))) { this->defer([obj]() { obj->toggle().perform(); }); request->send(200); } else { - bool is_on = match.method_equals("turn_on"); - bool is_off = match.method_equals("turn_off"); + bool is_on = match.method_equals(ESPHOME_F("turn_on")); + bool is_off = match.method_equals(ESPHOME_F("turn_off")); if (!is_on && !is_off) { request->send(404); return; @@ -790,20 +925,20 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa if (is_on) { // Parse color parameters - parse_light_param_(request, "brightness", call, &decltype(call)::set_brightness, 255.0f); - parse_light_param_(request, "r", call, &decltype(call)::set_red, 255.0f); - parse_light_param_(request, "g", call, &decltype(call)::set_green, 255.0f); - parse_light_param_(request, "b", call, &decltype(call)::set_blue, 255.0f); - parse_light_param_(request, "white_value", call, &decltype(call)::set_white, 255.0f); - parse_light_param_(request, "color_temp", call, &decltype(call)::set_color_temperature); + parse_light_param_(request, ESPHOME_F("brightness"), call, &decltype(call)::set_brightness, 255.0f); + parse_light_param_(request, ESPHOME_F("r"), call, &decltype(call)::set_red, 255.0f); + parse_light_param_(request, ESPHOME_F("g"), call, &decltype(call)::set_green, 255.0f); + parse_light_param_(request, ESPHOME_F("b"), call, &decltype(call)::set_blue, 255.0f); + parse_light_param_(request, ESPHOME_F("white_value"), call, &decltype(call)::set_white, 255.0f); + parse_light_param_(request, ESPHOME_F("color_temp"), call, &decltype(call)::set_color_temperature); // Parse timing parameters - parse_light_param_uint_(request, "flash", call, &decltype(call)::set_flash_length, 1000); + parse_light_param_uint_(request, ESPHOME_F("flash"), call, &decltype(call)::set_flash_length, 1000); } - parse_light_param_uint_(request, "transition", call, &decltype(call)::set_transition_length, 1000); + parse_light_param_uint_(request, ESPHOME_F("transition"), call, &decltype(call)::set_transition_length, 1000); if (is_on) { - parse_string_param_(request, "effect", call, &decltype(call)::set_effect); + parse_string_param_(request, ESPHOME_F("effect"), call, &decltype(call)::set_effect); } this->defer([call]() mutable { call.perform(); }); @@ -814,12 +949,12 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::light_state_json_generator(WebServer *web_server, void *source) { - return web_server->light_json((light::LightState *) (source), DETAIL_STATE); + return web_server->light_json_((light::LightState *) (source), DETAIL_STATE); } std::string WebServer::light_all_json_generator(WebServer *web_server, void *source) { - return web_server->light_json((light::LightState *) (source), DETAIL_ALL); + return web_server->light_json_((light::LightState *) (source), DETAIL_ALL); } -std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) { +std::string WebServer::light_json_(light::LightState *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -847,12 +982,13 @@ void WebServer::on_cover_update(cover::Cover *obj) { } void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->cover_json(obj, detail); + std::string data = this->cover_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -879,20 +1015,20 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa } } - if (!found && !match.method_equals("set")) { + if (!found && !match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto traits = obj->get_traits(); - if ((request->hasParam("position") && !traits.get_supports_position()) || - (request->hasParam("tilt") && !traits.get_supports_tilt())) { + if ((request->hasParam(ESPHOME_F("position")) && !traits.get_supports_position()) || + (request->hasParam(ESPHOME_F("tilt")) && !traits.get_supports_tilt())) { request->send(409); return; } - parse_float_param_(request, "position", call, &decltype(call)::set_position); - parse_float_param_(request, "tilt", call, &decltype(call)::set_tilt); + parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position); + parse_float_param_(request, ESPHOME_F("tilt"), call, &decltype(call)::set_tilt); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -901,12 +1037,12 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::cover_state_json_generator(WebServer *web_server, void *source) { - return web_server->cover_json((cover::Cover *) (source), DETAIL_STATE); + return web_server->cover_json_((cover::Cover *) (source), DETAIL_STATE); } std::string WebServer::cover_all_json_generator(WebServer *web_server, void *source) { - return web_server->cover_json((cover::Cover *) (source), DETAIL_ALL); + return web_server->cover_json_((cover::Cover *) (source), DETAIL_ALL); } -std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { +std::string WebServer::cover_json_(cover::Cover *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -935,22 +1071,23 @@ void WebServer::on_number_update(number::Number *obj) { } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->number_json(obj, obj->state, detail); + std::string data = this->number_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("set")) { + if (!match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto call = obj->make_call(); - parse_float_param_(request, "value", call, &decltype(call)::set_value); + parse_float_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -960,31 +1097,30 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::number_state_json_generator(WebServer *web_server, void *source) { - return web_server->number_json((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_STATE); + return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_STATE); } std::string WebServer::number_all_json_generator(WebServer *web_server, void *source) { - return web_server->number_json((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_ALL); + return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_ALL); } -std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) { +std::string WebServer::number_json_(number::Number *obj, float value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); const auto uom_ref = obj->traits.get_unit_of_measurement_ref(); + const int8_t accuracy = step_to_accuracy_decimals(obj->traits.get_step()); - std::string val_str = std::isnan(value) - ? "\"NaN\"" - : value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step())); - std::string state_str = std::isnan(value) ? "NA" - : value_accuracy_with_uom_to_string( - value, step_to_accuracy_decimals(obj->traits.get_step()), uom_ref); + // Need two buffers: one for value, one for state with UOM + char val_buf[VALUE_ACCURACY_MAX_LEN]; + char state_buf[VALUE_ACCURACY_MAX_LEN]; + const char *val_str = std::isnan(value) ? "\"NaN\"" : (value_accuracy_to_buf(val_buf, value, accuracy), val_buf); + const char *state_str = + std::isnan(value) ? "NA" : (value_accuracy_with_uom_to_buf(state_buf, value, accuracy, uom_ref), state_buf); set_json_icon_state_value(root, obj, "number", state_str, val_str, start_config); if (start_config == DETAIL_ALL) { - root[ESPHOME_F("min_value")] = - value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step())); - root[ESPHOME_F("max_value")] = - value_accuracy_to_string(obj->traits.get_max_value(), step_to_accuracy_decimals(obj->traits.get_step())); - root[ESPHOME_F("step")] = - value_accuracy_to_string(obj->traits.get_step(), step_to_accuracy_decimals(obj->traits.get_step())); + // ArduinoJson copies the string immediately, so we can reuse val_buf + root[ESPHOME_F("min_value")] = (value_accuracy_to_buf(val_buf, obj->traits.get_min_value(), accuracy), val_buf); + root[ESPHOME_F("max_value")] = (value_accuracy_to_buf(val_buf, obj->traits.get_max_value(), accuracy), val_buf); + root[ESPHOME_F("step")] = (value_accuracy_to_buf(val_buf, obj->traits.get_step(), accuracy), val_buf); root[ESPHOME_F("mode")] = (int) obj->traits.get_mode(); if (!uom_ref.empty()) root[ESPHOME_F("uom")] = uom_ref; @@ -1003,27 +1139,28 @@ void WebServer::on_date_update(datetime::DateEntity *obj) { } void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_dates()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->date_json(obj, detail); + std::string data = this->date_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("set")) { + if (!match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto call = obj->make_call(); - if (!request->hasParam("value")) { + if (!request->hasParam(ESPHOME_F("value"))) { request->send(409); return; } - parse_string_param_(request, "value", call, &decltype(call)::set_date); + parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_date); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -1033,16 +1170,22 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat } std::string WebServer::date_state_json_generator(WebServer *web_server, void *source) { - return web_server->date_json((datetime::DateEntity *) (source), DETAIL_STATE); + return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_STATE); } std::string WebServer::date_all_json_generator(WebServer *web_server, void *source) { - return web_server->date_json((datetime::DateEntity *) (source), DETAIL_ALL); + return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_ALL); } -std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { +std::string WebServer::date_json_(datetime::DateEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); - std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); + // Format: YYYY-MM-DD (max 10 chars + null) + char value[12]; +#ifdef USE_ESP8266 + snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d"), obj->year, obj->month, obj->day); +#else + snprintf(value, sizeof(value), "%d-%02d-%02d", obj->year, obj->month, obj->day); +#endif set_json_icon_state_value(root, obj, "date", value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -1060,27 +1203,28 @@ void WebServer::on_time_update(datetime::TimeEntity *obj) { } void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_times()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->time_json(obj, detail); + std::string data = this->time_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("set")) { + if (!match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto call = obj->make_call(); - if (!request->hasParam("value")) { + if (!request->hasParam(ESPHOME_F("value"))) { request->send(409); return; } - parse_string_param_(request, "value", call, &decltype(call)::set_time); + parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_time); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -1089,16 +1233,22 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat request->send(404); } std::string WebServer::time_state_json_generator(WebServer *web_server, void *source) { - return web_server->time_json((datetime::TimeEntity *) (source), DETAIL_STATE); + return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_STATE); } std::string WebServer::time_all_json_generator(WebServer *web_server, void *source) { - return web_server->time_json((datetime::TimeEntity *) (source), DETAIL_ALL); + return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_ALL); } -std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) { +std::string WebServer::time_json_(datetime::TimeEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); - std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second); + // Format: HH:MM:SS (8 chars + null) + char value[12]; +#ifdef USE_ESP8266 + snprintf_P(value, sizeof(value), PSTR("%02d:%02d:%02d"), obj->hour, obj->minute, obj->second); +#else + snprintf(value, sizeof(value), "%02d:%02d:%02d", obj->hour, obj->minute, obj->second); +#endif set_json_icon_state_value(root, obj, "time", value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -1116,27 +1266,28 @@ void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) { } void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_datetimes()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->datetime_json(obj, detail); + std::string data = this->datetime_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("set")) { + if (!match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto call = obj->make_call(); - if (!request->hasParam("value")) { + if (!request->hasParam(ESPHOME_F("value"))) { request->send(409); return; } - parse_string_param_(request, "value", call, &decltype(call)::set_datetime); + parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_datetime); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -1145,17 +1296,24 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur request->send(404); } std::string WebServer::datetime_state_json_generator(WebServer *web_server, void *source) { - return web_server->datetime_json((datetime::DateTimeEntity *) (source), DETAIL_STATE); + return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_STATE); } std::string WebServer::datetime_all_json_generator(WebServer *web_server, void *source) { - return web_server->datetime_json((datetime::DateTimeEntity *) (source), DETAIL_ALL); + return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_ALL); } -std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) { +std::string WebServer::datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); - std::string value = - str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second); + // Format: YYYY-MM-DD HH:MM:SS (max 19 chars + null) + char value[24]; +#ifdef USE_ESP8266 + snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d %02d:%02d:%02d"), obj->year, obj->month, obj->day, obj->hour, + obj->minute, obj->second); +#else + snprintf(value, sizeof(value), "%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, + obj->second); +#endif set_json_icon_state_value(root, obj, "datetime", value, value, start_config); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); @@ -1173,22 +1331,23 @@ void WebServer::on_text_update(text::Text *obj) { } void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_texts()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->text_json(obj, obj->state, detail); + std::string data = this->text_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("set")) { + if (!match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto call = obj->make_call(); - parse_string_param_(request, "value", call, &decltype(call)::set_value); + parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -1198,17 +1357,17 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat } std::string WebServer::text_state_json_generator(WebServer *web_server, void *source) { - return web_server->text_json((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_STATE); + return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_STATE); } std::string WebServer::text_all_json_generator(WebServer *web_server, void *source) { - return web_server->text_json((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_ALL); + return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_ALL); } -std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) { +std::string WebServer::text_json_(text::Text *obj, const std::string &value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); - std::string state = obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD ? "********" : value; - set_json_icon_state_value(root, obj, "text", state, value, start_config); + const char *state = obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD ? "********" : value.c_str(); + set_json_icon_state_value(root, obj, "text", state, value.c_str(), start_config); root[ESPHOME_F("min_length")] = obj->traits.get_min_length(); root[ESPHOME_F("max_length")] = obj->traits.get_max_length(); root[ESPHOME_F("pattern")] = obj->traits.get_pattern_c_str(); @@ -1229,23 +1388,24 @@ void WebServer::on_select_update(select::Select *obj) { } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->select_json(obj, obj->has_state() ? obj->current_option() : "", detail); + std::string data = this->select_json_(obj, obj->has_state() ? obj->current_option() : StringRef(), detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("set")) { + if (!match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto call = obj->make_call(); - parse_string_param_(request, "option", call, &decltype(call)::set_option); + parse_string_param_(request, ESPHOME_F("option"), call, &decltype(call)::set_option); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -1255,17 +1415,18 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::select_state_json_generator(WebServer *web_server, void *source) { auto *obj = (select::Select *) (source); - return web_server->select_json(obj, obj->has_state() ? obj->current_option() : "", DETAIL_STATE); + return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : StringRef(), DETAIL_STATE); } std::string WebServer::select_all_json_generator(WebServer *web_server, void *source) { auto *obj = (select::Select *) (source); - return web_server->select_json(obj, obj->has_state() ? obj->current_option() : "", DETAIL_ALL); + return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : StringRef(), DETAIL_ALL); } -std::string WebServer::select_json(select::Select *obj, const char *value, JsonDetail start_config) { +std::string WebServer::select_json_(select::Select *obj, StringRef value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); - set_json_icon_state_value(root, obj, "select", value, value, start_config); + // value points to null-terminated string literals from codegen (via current_option()) + set_json_icon_state_value(root, obj, "select", value.c_str(), value.c_str(), start_config); if (start_config == DETAIL_ALL) { JsonArray opt = root[ESPHOME_F("option")].to(); for (auto &option : obj->traits.get_options()) { @@ -1286,17 +1447,18 @@ void WebServer::on_climate_update(climate::Climate *obj) { } void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_climates()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->climate_json(obj, detail); + std::string data = this->climate_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("set")) { + if (!match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } @@ -1304,14 +1466,15 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url auto call = obj->make_call(); // Parse string mode parameters - parse_string_param_(request, "mode", call, &decltype(call)::set_mode); - parse_string_param_(request, "fan_mode", call, &decltype(call)::set_fan_mode); - parse_string_param_(request, "swing_mode", call, &decltype(call)::set_swing_mode); + parse_string_param_(request, ESPHOME_F("mode"), call, &decltype(call)::set_mode); + parse_string_param_(request, ESPHOME_F("fan_mode"), call, &decltype(call)::set_fan_mode); + parse_string_param_(request, ESPHOME_F("swing_mode"), call, &decltype(call)::set_swing_mode); // Parse temperature parameters - parse_float_param_(request, "target_temperature_high", call, &decltype(call)::set_target_temperature_high); - parse_float_param_(request, "target_temperature_low", call, &decltype(call)::set_target_temperature_low); - parse_float_param_(request, "target_temperature", call, &decltype(call)::set_target_temperature); + parse_float_param_(request, ESPHOME_F("target_temperature_high"), call, + &decltype(call)::set_target_temperature_high); + parse_float_param_(request, ESPHOME_F("target_temperature_low"), call, &decltype(call)::set_target_temperature_low); + parse_float_param_(request, ESPHOME_F("target_temperature"), call, &decltype(call)::set_target_temperature); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -1321,13 +1484,13 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url } std::string WebServer::climate_state_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->climate_json((climate::Climate *) (source), DETAIL_STATE); + return web_server->climate_json_((climate::Climate *) (source), DETAIL_STATE); } std::string WebServer::climate_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL); + return web_server->climate_json_((climate::Climate *) (source), DETAIL_ALL); } -std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { +std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1336,6 +1499,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); char buf[PSTR_LOCAL_SIZE]; + char temp_buf[VALUE_ACCURACY_MAX_LEN]; if (start_config == DETAIL_ALL) { JsonArray opt = root[ESPHOME_F("modes")].to(); @@ -1372,8 +1536,10 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf bool has_state = false; root[ESPHOME_F("mode")] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); - root[ESPHOME_F("max_temp")] = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy); - root[ESPHOME_F("min_temp")] = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy); + root[ESPHOME_F("max_temp")] = + (value_accuracy_to_buf(temp_buf, traits.get_visual_max_temperature(), target_accuracy), temp_buf); + root[ESPHOME_F("min_temp")] = + (value_accuracy_to_buf(temp_buf, traits.get_visual_min_temperature(), target_accuracy), temp_buf); root[ESPHOME_F("step")] = traits.get_visual_target_temperature_step(); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { root[ESPHOME_F("action")] = PSTR_LOCAL(climate_action_to_string(obj->action)); @@ -1396,23 +1562,26 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf root[ESPHOME_F("swing_mode")] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { - if (!std::isnan(obj->current_temperature)) { - root[ESPHOME_F("current_temperature")] = value_accuracy_to_string(obj->current_temperature, current_accuracy); - } else { - root[ESPHOME_F("current_temperature")] = "NA"; - } + root[ESPHOME_F("current_temperature")] = + std::isnan(obj->current_temperature) + ? "NA" + : (value_accuracy_to_buf(temp_buf, obj->current_temperature, current_accuracy), temp_buf); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { - root[ESPHOME_F("target_temperature_low")] = value_accuracy_to_string(obj->target_temperature_low, target_accuracy); + root[ESPHOME_F("target_temperature_low")] = + (value_accuracy_to_buf(temp_buf, obj->target_temperature_low, target_accuracy), temp_buf); root[ESPHOME_F("target_temperature_high")] = - value_accuracy_to_string(obj->target_temperature_high, target_accuracy); + (value_accuracy_to_buf(temp_buf, obj->target_temperature_high, target_accuracy), temp_buf); if (!has_state) { - root[ESPHOME_F("state")] = value_accuracy_to_string( - (obj->target_temperature_high + obj->target_temperature_low) / 2.0f, target_accuracy); + root[ESPHOME_F("state")] = + (value_accuracy_to_buf(temp_buf, (obj->target_temperature_high + obj->target_temperature_low) / 2.0f, + target_accuracy), + temp_buf); } } else { - root[ESPHOME_F("target_temperature")] = value_accuracy_to_string(obj->target_temperature, target_accuracy); + root[ESPHOME_F("target_temperature")] = + (value_accuracy_to_buf(temp_buf, obj->target_temperature, target_accuracy), temp_buf); if (!has_state) root[ESPHOME_F("state")] = root[ESPHOME_F("target_temperature")]; } @@ -1430,12 +1599,13 @@ void WebServer::on_lock_update(lock::Lock *obj) { } void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (lock::Lock *obj : App.get_locks()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->lock_json(obj, obj->state, detail); + std::string data = this->lock_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1444,11 +1614,11 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat enum LockAction { NONE, LOCK, UNLOCK, OPEN }; LockAction action = NONE; - if (match.method_equals("lock")) { + if (match.method_equals(ESPHOME_F("lock"))) { action = LOCK; - } else if (match.method_equals("unlock")) { + } else if (match.method_equals(ESPHOME_F("unlock"))) { action = UNLOCK; - } else if (match.method_equals("open")) { + } else if (match.method_equals(ESPHOME_F("open"))) { action = OPEN; } @@ -1477,12 +1647,12 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat request->send(404); } std::string WebServer::lock_state_json_generator(WebServer *web_server, void *source) { - return web_server->lock_json((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_STATE); + return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_STATE); } std::string WebServer::lock_all_json_generator(WebServer *web_server, void *source) { - return web_server->lock_json((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_ALL); + return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_ALL); } -std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { +std::string WebServer::lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1504,12 +1674,13 @@ void WebServer::on_valve_update(valve::Valve *obj) { } void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (valve::Valve *obj : App.get_valves()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->valve_json(obj, detail); + std::string data = this->valve_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1536,18 +1707,18 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa } } - if (!found && !match.method_equals("set")) { + if (!found && !match.method_equals(ESPHOME_F("set"))) { request->send(404); return; } auto traits = obj->get_traits(); - if (request->hasParam("position") && !traits.get_supports_position()) { + if (request->hasParam(ESPHOME_F("position")) && !traits.get_supports_position()) { request->send(409); return; } - parse_float_param_(request, "position", call, &decltype(call)::set_position); + parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position); this->defer([call]() mutable { call.perform(); }); request->send(200); @@ -1556,12 +1727,12 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::valve_state_json_generator(WebServer *web_server, void *source) { - return web_server->valve_json((valve::Valve *) (source), DETAIL_STATE); + return web_server->valve_json_((valve::Valve *) (source), DETAIL_STATE); } std::string WebServer::valve_all_json_generator(WebServer *web_server, void *source) { - return web_server->valve_json((valve::Valve *) (source), DETAIL_ALL); + return web_server->valve_json_((valve::Valve *) (source), DETAIL_ALL); } -std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { +std::string WebServer::valve_json_(valve::Valve *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1588,18 +1759,19 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP } void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->alarm_control_panel_json(obj, obj->get_state(), detail); + std::string data = this->alarm_control_panel_json_(obj, obj->get_state(), detail); request->send(200, "application/json", data.c_str()); return; } auto call = obj->make_call(); - parse_string_param_(request, "code", call, &decltype(call)::set_code); + parse_string_param_(request, ESPHOME_F("code"), call, &decltype(call)::set_code); // Lookup table for alarm control panel methods static const struct { @@ -1634,18 +1806,18 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques request->send(404); } std::string WebServer::alarm_control_panel_state_json_generator(WebServer *web_server, void *source) { - return web_server->alarm_control_panel_json((alarm_control_panel::AlarmControlPanel *) (source), - ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), - DETAIL_STATE); + return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source), + ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), + DETAIL_STATE); } std::string WebServer::alarm_control_panel_all_json_generator(WebServer *web_server, void *source) { - return web_server->alarm_control_panel_json((alarm_control_panel::AlarmControlPanel *) (source), - ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), - DETAIL_ALL); + return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source), + ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), + DETAIL_ALL); } -std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, - alarm_control_panel::AlarmControlPanelState value, - JsonDetail start_config) { +std::string WebServer::alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, + alarm_control_panel::AlarmControlPanelState value, + JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1660,6 +1832,117 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro } #endif +#ifdef USE_WATER_HEATER +void WebServer::on_water_heater_update(water_heater::WaterHeater *obj) { + if (!this->include_internal_ && obj->is_internal()) + return; + this->events_.deferrable_send_state(obj, "state", water_heater_state_json_generator); +} +void WebServer::handle_water_heater_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (water_heater::WaterHeater *obj : App.get_water_heaters()) { + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) + continue; + + if (request->method() == HTTP_GET && entity_match.action_is_empty) { + auto detail = get_request_detail(request); + std::string data = this->water_heater_json_(obj, detail); + request->send(200, "application/json", data.c_str()); + return; + } + if (!match.method_equals(ESPHOME_F("set"))) { + request->send(404); + return; + } + auto call = obj->make_call(); + // Use base class reference for template deduction (make_call returns WaterHeaterCallInternal) + water_heater::WaterHeaterCall &base_call = call; + + // Parse mode parameter + parse_string_param_(request, ESPHOME_F("mode"), base_call, &water_heater::WaterHeaterCall::set_mode); + + // Parse temperature parameters + parse_float_param_(request, ESPHOME_F("target_temperature"), base_call, + &water_heater::WaterHeaterCall::set_target_temperature); + parse_float_param_(request, ESPHOME_F("target_temperature_low"), base_call, + &water_heater::WaterHeaterCall::set_target_temperature_low); + parse_float_param_(request, ESPHOME_F("target_temperature_high"), base_call, + &water_heater::WaterHeaterCall::set_target_temperature_high); + + // Parse away mode parameter + parse_bool_param_(request, ESPHOME_F("away"), base_call, &water_heater::WaterHeaterCall::set_away); + + // Parse on/off parameter + parse_bool_param_(request, ESPHOME_F("is_on"), base_call, &water_heater::WaterHeaterCall::set_on); + + this->defer([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} + +std::string WebServer::water_heater_state_json_generator(WebServer *web_server, void *source) { + return web_server->water_heater_json_(static_cast(source), DETAIL_STATE); +} +std::string WebServer::water_heater_all_json_generator(WebServer *web_server, void *source) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson + return web_server->water_heater_json_(static_cast(source), DETAIL_ALL); +} +std::string WebServer::water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config) { + json::JsonBuilder builder; + JsonObject root = builder.root(); + char buf[PSTR_LOCAL_SIZE]; + + const auto mode = obj->get_mode(); + const char *mode_s = PSTR_LOCAL(water_heater::water_heater_mode_to_string(mode)); + + set_json_icon_state_value(root, obj, "water_heater", mode_s, mode, start_config); + + auto traits = obj->get_traits(); + + if (start_config == DETAIL_ALL) { + JsonArray modes = root[ESPHOME_F("modes")].to(); + for (auto m : traits.get_supported_modes()) + modes.add(PSTR_LOCAL(water_heater::water_heater_mode_to_string(m))); + this->add_sorting_info_(root, obj); + } + + if (traits.get_supports_current_temperature()) { + float current = obj->get_current_temperature(); + if (!std::isnan(current)) + root[ESPHOME_F("current_temperature")] = current; + } + + if (traits.get_supports_two_point_target_temperature()) { + float low = obj->get_target_temperature_low(); + float high = obj->get_target_temperature_high(); + if (!std::isnan(low)) + root[ESPHOME_F("target_temperature_low")] = low; + if (!std::isnan(high)) + root[ESPHOME_F("target_temperature_high")] = high; + } else { + float target = obj->get_target_temperature(); + if (!std::isnan(target)) + root[ESPHOME_F("target_temperature")] = target; + } + + root[ESPHOME_F("min_temperature")] = traits.get_min_temperature(); + root[ESPHOME_F("max_temperature")] = traits.get_max_temperature(); + root[ESPHOME_F("step")] = traits.get_target_temperature_step(); + + if (traits.get_supports_away_mode()) { + root[ESPHOME_F("away")] = obj->is_away(); + } + + if (traits.has_feature_flags(water_heater::WATER_HEATER_SUPPORTS_ON_OFF)) { + root[ESPHOME_F("is_on")] = obj->is_on(); + } + + return builder.serialize(); +} +#endif + #ifdef USE_EVENT void WebServer::on_event(event::Event *obj) { if (!this->include_internal_ && obj->is_internal()) @@ -1669,13 +1952,14 @@ void WebServer::on_event(event::Event *obj) { void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (event::Event *obj : App.get_events()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; // Note: request->method() is always HTTP_GET here (canHandle ensures this) - if (match.method_empty()) { + if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->event_json(obj, "", detail); + std::string data = this->event_json_(obj, StringRef(), detail); request->send(200, "application/json", data.c_str()); return; } @@ -1683,21 +1967,18 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } -static std::string get_event_type(event::Event *event) { - const char *last_type = event ? event->get_last_event_type() : nullptr; - return last_type ? last_type : ""; -} +static StringRef get_event_type(event::Event *event) { return event ? event->get_last_event_type() : StringRef(); } std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); - return web_server->event_json(event, get_event_type(event), DETAIL_STATE); + return web_server->event_json_(event, get_event_type(event), DETAIL_STATE); } // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson std::string WebServer::event_all_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); - return web_server->event_json(event, get_event_type(event), DETAIL_ALL); + return web_server->event_json_(event, get_event_type(event), DETAIL_ALL); } -std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) { +std::string WebServer::event_json_(event::Event *obj, StringRef event_type, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1738,17 +2019,18 @@ void WebServer::on_update(update::UpdateEntity *obj) { } void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (update::UpdateEntity *obj : App.get_updates()) { - if (!match.id_equals_entity(obj)) + auto entity_match = match.match_entity(obj); + if (!entity_match.matched) continue; - if (request->method() == HTTP_GET && match.method_empty()) { + if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->update_json(obj, detail); + std::string data = this->update_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } - if (!match.method_equals("install")) { + if (!match.method_equals(ESPHOME_F("install"))) { request->send(404); return; } @@ -1761,13 +2043,13 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::update_state_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); + return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE); } std::string WebServer::update_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); + return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE); } -std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { +std::string WebServer::update_json_(update::UpdateEntity *obj, JsonDetail start_config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1812,7 +2094,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const { } #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS - if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) + if (method == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network"))) return true; #endif @@ -1893,6 +2175,9 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const { #endif #ifdef USE_UPDATE "update", +#endif +#ifdef USE_WATER_HEATER + "water_heater", #endif }; @@ -1945,113 +2230,119 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { #endif #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS - if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { + if (request->method() == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network"))) { this->handle_pna_cors_request(request); return; } #endif // Parse URL for component routing - UrlMatch match = match_url(url.c_str(), url.length(), false); + // Pass HTTP method to disambiguate 3-segment URLs (GET=sub-device state, POST=main device action) + UrlMatch match = match_url(url.c_str(), url.length(), false, request->method() == HTTP_POST); // Route to appropriate handler based on domain // NOLINTNEXTLINE(readability-simplify-boolean-expr) if (false) { // Start chain for else-if macro pattern } #ifdef USE_SENSOR - else if (match.domain_equals("sensor")) { + else if (match.domain_equals(ESPHOME_F("sensor"))) { this->handle_sensor_request(request, match); } #endif #ifdef USE_SWITCH - else if (match.domain_equals("switch")) { + else if (match.domain_equals(ESPHOME_F("switch"))) { this->handle_switch_request(request, match); } #endif #ifdef USE_BUTTON - else if (match.domain_equals("button")) { + else if (match.domain_equals(ESPHOME_F("button"))) { this->handle_button_request(request, match); } #endif #ifdef USE_BINARY_SENSOR - else if (match.domain_equals("binary_sensor")) { + else if (match.domain_equals(ESPHOME_F("binary_sensor"))) { this->handle_binary_sensor_request(request, match); } #endif #ifdef USE_FAN - else if (match.domain_equals("fan")) { + else if (match.domain_equals(ESPHOME_F("fan"))) { this->handle_fan_request(request, match); } #endif #ifdef USE_LIGHT - else if (match.domain_equals("light")) { + else if (match.domain_equals(ESPHOME_F("light"))) { this->handle_light_request(request, match); } #endif #ifdef USE_TEXT_SENSOR - else if (match.domain_equals("text_sensor")) { + else if (match.domain_equals(ESPHOME_F("text_sensor"))) { this->handle_text_sensor_request(request, match); } #endif #ifdef USE_COVER - else if (match.domain_equals("cover")) { + else if (match.domain_equals(ESPHOME_F("cover"))) { this->handle_cover_request(request, match); } #endif #ifdef USE_NUMBER - else if (match.domain_equals("number")) { + else if (match.domain_equals(ESPHOME_F("number"))) { this->handle_number_request(request, match); } #endif #ifdef USE_DATETIME_DATE - else if (match.domain_equals("date")) { + else if (match.domain_equals(ESPHOME_F("date"))) { this->handle_date_request(request, match); } #endif #ifdef USE_DATETIME_TIME - else if (match.domain_equals("time")) { + else if (match.domain_equals(ESPHOME_F("time"))) { this->handle_time_request(request, match); } #endif #ifdef USE_DATETIME_DATETIME - else if (match.domain_equals("datetime")) { + else if (match.domain_equals(ESPHOME_F("datetime"))) { this->handle_datetime_request(request, match); } #endif #ifdef USE_TEXT - else if (match.domain_equals("text")) { + else if (match.domain_equals(ESPHOME_F("text"))) { this->handle_text_request(request, match); } #endif #ifdef USE_SELECT - else if (match.domain_equals("select")) { + else if (match.domain_equals(ESPHOME_F("select"))) { this->handle_select_request(request, match); } #endif #ifdef USE_CLIMATE - else if (match.domain_equals("climate")) { + else if (match.domain_equals(ESPHOME_F("climate"))) { this->handle_climate_request(request, match); } #endif #ifdef USE_LOCK - else if (match.domain_equals("lock")) { + else if (match.domain_equals(ESPHOME_F("lock"))) { this->handle_lock_request(request, match); } #endif #ifdef USE_VALVE - else if (match.domain_equals("valve")) { + else if (match.domain_equals(ESPHOME_F("valve"))) { this->handle_valve_request(request, match); } #endif #ifdef USE_ALARM_CONTROL_PANEL - else if (match.domain_equals("alarm_control_panel")) { + else if (match.domain_equals(ESPHOME_F("alarm_control_panel"))) { this->handle_alarm_control_panel_request(request, match); } #endif #ifdef USE_UPDATE - else if (match.domain_equals("update")) { + else if (match.domain_equals(ESPHOME_F("update"))) { this->handle_update_request(request, match); } +#endif +#ifdef USE_WATER_HEATER + else if (match.domain_equals(ESPHOME_F("water_heater"))) { + this->handle_water_heater_request(request, match); + } #endif else { // No matching handler found - send 404 @@ -2083,6 +2374,5 @@ void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_na } #endif -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index bb69d57872..91625476f4 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -33,40 +33,44 @@ extern const uint8_t ESPHOME_WEBSERVER_JS_INCLUDE[] PROGMEM; extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE; #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { + +// Type for parameter names that can be stored in flash on ESP8266 +#ifdef USE_ESP8266 +using ParamNameType = const __FlashStringHelper *; +#else +using ParamNameType = const char *; +#endif + +/// Result of matching a URL against an entity +struct EntityMatchResult { + bool matched; ///< True if entity matched the URL + bool action_is_empty; ///< True if no action/method segment in URL +}; /// Internal helper struct that is used to parse incoming URLs struct UrlMatch { - const char *domain; ///< Pointer to domain within URL, for example "sensor" - const char *id; ///< Pointer to id within URL, for example "living_room_fan" - const char *method; ///< Pointer to method within URL, for example "turn_on" - uint8_t domain_len; ///< Length of domain string - uint8_t id_len; ///< Length of id string - uint8_t method_len; ///< Length of method string - bool valid; ///< Whether this match is valid + StringRef domain; ///< Domain within URL, for example "sensor" + StringRef id; ///< Entity name/id within URL, for example "Temperature" + StringRef method; ///< Method within URL, for example "turn_on" +#ifdef USE_DEVICES + StringRef device_name; ///< Device name within URL, empty for main device +#endif + bool valid{false}; ///< Whether this match is valid // Helper methods for string comparisons - bool domain_equals(const char *str) const { - return domain && domain_len == strlen(str) && memcmp(domain, str, domain_len) == 0; - } + bool domain_equals(const char *str) const { return this->domain == str; } + bool method_equals(const char *str) const { return this->method == str; } - bool id_equals_entity(EntityBase *entity) const { - // Zero-copy comparison using StringRef - StringRef static_ref = entity->get_object_id_ref_for_api_(); - if (!static_ref.empty()) { - return id && id_len == static_ref.size() && memcmp(id, static_ref.c_str(), id_len) == 0; - } - // Fallback to allocation (rare) - const auto &obj_id = entity->get_object_id(); - return id && id_len == obj_id.length() && memcmp(id, obj_id.c_str(), id_len) == 0; - } +#ifdef USE_ESP8266 + // Overloads for flash strings on ESP8266 + bool domain_equals(const __FlashStringHelper *str) const { return this->domain == str; } + bool method_equals(const __FlashStringHelper *str) const { return this->method == str; } +#endif - bool method_equals(const char *str) const { - return method && method_len == strlen(str) && memcmp(method, str, method_len) == 0; - } - - bool method_empty() const { return method_len == 0; } + /// Match entity by name first, then fall back to object_id with deprecation warning + /// Returns EntityMatchResult with match status and whether action segment is empty + EntityMatchResult match_entity(EntityBase *entity) const; }; #ifdef USE_WEBSERVER_SORTING @@ -275,8 +279,6 @@ class WebServer : public Controller, static std::string sensor_state_json_generator(WebServer *web_server, void *source); static std::string sensor_all_json_generator(WebServer *web_server, void *source); - /// Dump the sensor state with its value as a JSON string. - std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config); #endif #ifdef USE_SWITCH @@ -287,8 +289,6 @@ class WebServer : public Controller, static std::string switch_state_json_generator(WebServer *web_server, void *source); static std::string switch_all_json_generator(WebServer *web_server, void *source); - /// Dump the switch state with its value as a JSON string. - std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config); #endif #ifdef USE_BUTTON @@ -297,8 +297,6 @@ class WebServer : public Controller, static std::string button_state_json_generator(WebServer *web_server, void *source); static std::string button_all_json_generator(WebServer *web_server, void *source); - /// Dump the button details with its value as a JSON string. - std::string button_json(button::Button *obj, JsonDetail start_config); #endif #ifdef USE_BINARY_SENSOR @@ -309,8 +307,6 @@ class WebServer : public Controller, static std::string binary_sensor_state_json_generator(WebServer *web_server, void *source); static std::string binary_sensor_all_json_generator(WebServer *web_server, void *source); - /// Dump the binary sensor state with its value as a JSON string. - std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config); #endif #ifdef USE_FAN @@ -321,8 +317,6 @@ class WebServer : public Controller, static std::string fan_state_json_generator(WebServer *web_server, void *source); static std::string fan_all_json_generator(WebServer *web_server, void *source); - /// Dump the fan state as a JSON string. - std::string fan_json(fan::Fan *obj, JsonDetail start_config); #endif #ifdef USE_LIGHT @@ -333,8 +327,6 @@ class WebServer : public Controller, static std::string light_state_json_generator(WebServer *web_server, void *source); static std::string light_all_json_generator(WebServer *web_server, void *source); - /// Dump the light state as a JSON string. - std::string light_json(light::LightState *obj, JsonDetail start_config); #endif #ifdef USE_TEXT_SENSOR @@ -345,8 +337,6 @@ class WebServer : public Controller, static std::string text_sensor_state_json_generator(WebServer *web_server, void *source); static std::string text_sensor_all_json_generator(WebServer *web_server, void *source); - /// Dump the text sensor state with its value as a JSON string. - std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config); #endif #ifdef USE_COVER @@ -357,8 +347,6 @@ class WebServer : public Controller, static std::string cover_state_json_generator(WebServer *web_server, void *source); static std::string cover_all_json_generator(WebServer *web_server, void *source); - /// Dump the cover state as a JSON string. - std::string cover_json(cover::Cover *obj, JsonDetail start_config); #endif #ifdef USE_NUMBER @@ -368,8 +356,6 @@ class WebServer : public Controller, static std::string number_state_json_generator(WebServer *web_server, void *source); static std::string number_all_json_generator(WebServer *web_server, void *source); - /// Dump the number state with its value as a JSON string. - std::string number_json(number::Number *obj, float value, JsonDetail start_config); #endif #ifdef USE_DATETIME_DATE @@ -379,8 +365,6 @@ class WebServer : public Controller, static std::string date_state_json_generator(WebServer *web_server, void *source); static std::string date_all_json_generator(WebServer *web_server, void *source); - /// Dump the date state with its value as a JSON string. - std::string date_json(datetime::DateEntity *obj, JsonDetail start_config); #endif #ifdef USE_DATETIME_TIME @@ -390,8 +374,6 @@ class WebServer : public Controller, static std::string time_state_json_generator(WebServer *web_server, void *source); static std::string time_all_json_generator(WebServer *web_server, void *source); - /// Dump the time state with its value as a JSON string. - std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config); #endif #ifdef USE_DATETIME_DATETIME @@ -401,8 +383,6 @@ class WebServer : public Controller, static std::string datetime_state_json_generator(WebServer *web_server, void *source); static std::string datetime_all_json_generator(WebServer *web_server, void *source); - /// Dump the datetime state with its value as a JSON string. - std::string datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config); #endif #ifdef USE_TEXT @@ -412,8 +392,6 @@ class WebServer : public Controller, static std::string text_state_json_generator(WebServer *web_server, void *source); static std::string text_all_json_generator(WebServer *web_server, void *source); - /// Dump the text state with its value as a JSON string. - std::string text_json(text::Text *obj, const std::string &value, JsonDetail start_config); #endif #ifdef USE_SELECT @@ -423,8 +401,6 @@ class WebServer : public Controller, static std::string select_state_json_generator(WebServer *web_server, void *source); static std::string select_all_json_generator(WebServer *web_server, void *source); - /// Dump the select state with its value as a JSON string. - std::string select_json(select::Select *obj, const char *value, JsonDetail start_config); #endif #ifdef USE_CLIMATE @@ -434,8 +410,6 @@ class WebServer : public Controller, static std::string climate_state_json_generator(WebServer *web_server, void *source); static std::string climate_all_json_generator(WebServer *web_server, void *source); - /// Dump the climate details - std::string climate_json(climate::Climate *obj, JsonDetail start_config); #endif #ifdef USE_LOCK @@ -446,8 +420,6 @@ class WebServer : public Controller, static std::string lock_state_json_generator(WebServer *web_server, void *source); static std::string lock_all_json_generator(WebServer *web_server, void *source); - /// Dump the lock state with its value as a JSON string. - std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config); #endif #ifdef USE_VALVE @@ -458,8 +430,6 @@ class WebServer : public Controller, static std::string valve_state_json_generator(WebServer *web_server, void *source); static std::string valve_all_json_generator(WebServer *web_server, void *source); - /// Dump the valve state as a JSON string. - std::string valve_json(valve::Valve *obj, JsonDetail start_config); #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -470,9 +440,16 @@ class WebServer : public Controller, static std::string alarm_control_panel_state_json_generator(WebServer *web_server, void *source); static std::string alarm_control_panel_all_json_generator(WebServer *web_server, void *source); - /// Dump the alarm_control_panel state with its value as a JSON string. - std::string alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, - alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config); +#endif + +#ifdef USE_WATER_HEATER + void on_water_heater_update(water_heater::WaterHeater *obj) override; + + /// Handle a water_heater request under '/water_heater//'. + void handle_water_heater_request(AsyncWebServerRequest *request, const UrlMatch &match); + + static std::string water_heater_state_json_generator(WebServer *web_server, void *source); + static std::string water_heater_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_EVENT @@ -483,9 +460,6 @@ class WebServer : public Controller, /// Handle a event request under '/event'. void handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match); - - /// Dump the event details with its value as a JSON string. - std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config); #endif #ifdef USE_UPDATE @@ -496,8 +470,6 @@ class WebServer : public Controller, static std::string update_state_json_generator(WebServer *web_server, void *source); static std::string update_all_json_generator(WebServer *web_server, void *source); - /// Dump the update state with its value as a JSON string. - std::string update_json(update::UpdateEntity *obj, JsonDetail start_config); #endif /// Override the web handler's canHandle method. @@ -523,7 +495,7 @@ class WebServer : public Controller, #ifdef USE_LIGHT // Helper to parse and apply a float parameter with optional scaling template - void parse_light_param_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret (T::*setter)(float), + void parse_light_param_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(float), float scale = 1.0f) { if (request->hasParam(param_name)) { auto value = parse_number(request->getParam(param_name)->value().c_str()); @@ -535,7 +507,7 @@ class WebServer : public Controller, // Helper to parse and apply a uint32_t parameter with optional scaling template - void parse_light_param_uint_(AsyncWebServerRequest *request, const char *param_name, T &call, + void parse_light_param_uint_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(uint32_t), uint32_t scale = 1) { if (request->hasParam(param_name)) { auto value = parse_number(request->getParam(param_name)->value().c_str()); @@ -548,7 +520,7 @@ class WebServer : public Controller, // Generic helper to parse and apply a float parameter template - void parse_float_param_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret (T::*setter)(float)) { + void parse_float_param_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(float)) { if (request->hasParam(param_name)) { auto value = parse_number(request->getParam(param_name)->value().c_str()); if (value.has_value()) { @@ -559,7 +531,7 @@ class WebServer : public Controller, // Generic helper to parse and apply an int parameter template - void parse_int_param_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret (T::*setter)(int)) { + void parse_int_param_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(int)) { if (request->hasParam(param_name)) { auto value = parse_number(request->getParam(param_name)->value().c_str()); if (value.has_value()) { @@ -570,7 +542,7 @@ class WebServer : public Controller, // Generic helper to parse and apply a string parameter template - void parse_string_param_(AsyncWebServerRequest *request, const char *param_name, T &call, + void parse_string_param_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(const std::string &)) { if (request->hasParam(param_name)) { // .c_str() is required for Arduino framework where value() returns Arduino String instead of std::string @@ -579,6 +551,28 @@ class WebServer : public Controller, } } + // Generic helper to parse and apply a bool parameter + // Accepts: "on", "true", "1" (case-insensitive) as true + // Accepts: "off", "false", "0" (case-insensitive) as false + // Invalid values are ignored (setter not called) + template + void parse_bool_param_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(bool)) { + if (request->hasParam(param_name)) { + auto param_value = request->getParam(param_name)->value(); + // First check on/off (default), then true/false (custom) + auto val = parse_on_off(param_value.c_str()); + if (val == PARSE_NONE) { + val = parse_on_off(param_value.c_str(), "true", "false"); + } + if (val == PARSE_ON || param_value == "1") { + (call.*setter)(true); + } else if (val == PARSE_OFF || param_value == "0") { + (call.*setter)(false); + } + // PARSE_NONE/PARSE_TOGGLE: ignore invalid values + } + } + web_server_base::WebServerBase *base_; #ifdef USE_ESP32 AsyncEventSource events_{"/events", this}; @@ -597,8 +591,73 @@ class WebServer : public Controller, const char *js_include_{nullptr}; #endif bool expose_log_{true}; + + private: +#ifdef USE_SENSOR + std::string sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config); +#endif +#ifdef USE_SWITCH + std::string switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config); +#endif +#ifdef USE_BUTTON + std::string button_json_(button::Button *obj, JsonDetail start_config); +#endif +#ifdef USE_BINARY_SENSOR + std::string binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config); +#endif +#ifdef USE_FAN + std::string fan_json_(fan::Fan *obj, JsonDetail start_config); +#endif +#ifdef USE_LIGHT + std::string light_json_(light::LightState *obj, JsonDetail start_config); +#endif +#ifdef USE_TEXT_SENSOR + std::string text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config); +#endif +#ifdef USE_COVER + std::string cover_json_(cover::Cover *obj, JsonDetail start_config); +#endif +#ifdef USE_NUMBER + std::string number_json_(number::Number *obj, float value, JsonDetail start_config); +#endif +#ifdef USE_DATETIME_DATE + std::string date_json_(datetime::DateEntity *obj, JsonDetail start_config); +#endif +#ifdef USE_DATETIME_TIME + std::string time_json_(datetime::TimeEntity *obj, JsonDetail start_config); +#endif +#ifdef USE_DATETIME_DATETIME + std::string datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config); +#endif +#ifdef USE_TEXT + std::string text_json_(text::Text *obj, const std::string &value, JsonDetail start_config); +#endif +#ifdef USE_SELECT + std::string select_json_(select::Select *obj, StringRef value, JsonDetail start_config); +#endif +#ifdef USE_CLIMATE + std::string climate_json_(climate::Climate *obj, JsonDetail start_config); +#endif +#ifdef USE_LOCK + std::string lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config); +#endif +#ifdef USE_VALVE + std::string valve_json_(valve::Valve *obj, JsonDetail start_config); +#endif +#ifdef USE_ALARM_CONTROL_PANEL + std::string alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, + alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config); +#endif +#ifdef USE_EVENT + std::string event_json_(event::Event *obj, StringRef event_type, JsonDetail start_config); +#endif +#ifdef USE_WATER_HEATER + std::string water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config); +#endif +#ifdef USE_UPDATE + std::string update_json_(update::UpdateEntity *obj, JsonDetail start_config); +#endif }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index 4f0d0cd1a9..ae4bbfa557 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -3,8 +3,30 @@ #if USE_WEBSERVER_VERSION == 1 -namespace esphome { -namespace web_server { +namespace esphome::web_server { + +// Write HTML-escaped text to stream (escapes ", &, <, >) +static void write_html_escaped(AsyncResponseStream *stream, const char *text) { + for (const char *p = text; *p; ++p) { + switch (*p) { + case '"': + stream->print("""); + break; + case '&': + stream->print("&"); + break; + case '<': + stream->print("<"); + break; + case '>': + stream->print(">"); + break; + default: + stream->write(*p); + break; + } + } +} void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { @@ -15,9 +37,29 @@ void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string & stream->print("\" id=\""); stream->print(klass.c_str()); stream->print("-"); - stream->print(obj->get_object_id().c_str()); + char object_id_buf[OBJECT_ID_MAX_LEN]; + stream->print(obj->get_object_id_to(object_id_buf).c_str()); + // Add data attributes for hierarchical URL support + stream->print("\" data-domain=\""); + stream->print(klass.c_str()); + stream->print("\" data-name=\""); + write_html_escaped(stream, obj->get_name().c_str()); +#ifdef USE_DEVICES + Device *device = obj->get_device(); + if (device != nullptr) { + stream->print("\" data-device=\""); + write_html_escaped(stream, device->get_name()); + } +#endif stream->print("\">"); - stream->print(obj->get_name().c_str()); +#ifdef USE_DEVICES + if (device != nullptr) { + stream->print("["); + write_html_escaped(stream, device->get_name()); + stream->print("] "); + } +#endif + write_html_escaped(stream, obj->get_name().c_str()); stream->print(""); stream->print(action.c_str()); if (action_func) { @@ -160,7 +202,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream.print(""); for (auto const &option : select->traits.get_options()) { stream.print(""); } stream.print(""); @@ -190,6 +232,13 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { } #endif +#ifdef USE_WATER_HEATER + for (auto *obj : App.get_water_heaters()) { + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "water_heater", ""); + } +#endif + stream->print(ESPHOME_F("

See ESPHome Web API for " "REST API documentation.

")); #if defined(USE_WEBSERVER_OTA) && !defined(USE_WEBSERVER_OTA_DISABLED) @@ -214,6 +263,5 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { request->send(stream); } -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 4cf76eba0e..d5d75b395d 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -34,6 +34,8 @@ async def to_code(config): cg.add(cg.RawExpression(f"{web_server_base_ns}::global_web_server_base = {var}")) if CORE.is_esp32: + # Count for StaticVector in web_server_idf - matches headers added in init() + cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1) return if CORE.using_arduino: diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 54ec997671..0c25467f1b 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -100,7 +100,9 @@ class WebServerBase : public Component { } this->server_ = std::make_unique(this->port_); // All content is controlled and created by user - so allowing all origins is fine here. - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + // NOTE: Currently 1 header. If more are added, update in __init__.py: + // cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1) + DefaultHeaders::Instance().addHeader(ESPHOME_F("Access-Control-Allow-Origin"), ESPHOME_F("*")); this->server_->begin(); for (auto *handler : this->handlers_) diff --git a/esphome/components/web_server_idf/utils.cpp b/esphome/components/web_server_idf/utils.cpp index d5d34b520b..f27814062c 100644 --- a/esphome/components/web_server_idf/utils.cpp +++ b/esphome/components/web_server_idf/utils.cpp @@ -13,7 +13,8 @@ namespace web_server_idf { static const char *const TAG = "web_server_idf_utils"; -void url_decode(char *str) { +size_t url_decode(char *str) { + char *start = str; char *ptr = str, buf; for (; *str; str++, ptr++) { if (*str == '%') { @@ -31,7 +32,8 @@ void url_decode(char *str) { *ptr = *str; } } - *ptr = *str; + *ptr = '\0'; + return ptr - start; } bool request_has_header(httpd_req_t *req, const char *name) { return httpd_req_get_hdr_value_len(req, name); } diff --git a/esphome/components/web_server_idf/utils.h b/esphome/components/web_server_idf/utils.h index f70a5f0760..3a86aec7ac 100644 --- a/esphome/components/web_server_idf/utils.h +++ b/esphome/components/web_server_idf/utils.h @@ -8,6 +8,10 @@ namespace esphome { namespace web_server_idf { +/// Decode URL-encoded string in-place (e.g., %20 -> space, + -> space) +/// Returns the new length of the decoded string +size_t url_decode(char *str); + bool request_has_header(httpd_req_t *req, const char *name); optional request_get_header(httpd_req_t *req, const char *name); optional request_get_url_query(httpd_req_t *req); diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 8c3ad288c0..55d2040a3a 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -247,11 +247,20 @@ optional AsyncWebServerRequest::get_header(const char *name) const } std::string AsyncWebServerRequest::url() const { - auto *str = strchr(this->req_->uri, '?'); - if (str == nullptr) { - return this->req_->uri; + auto *query_start = strchr(this->req_->uri, '?'); + std::string result; + if (query_start == nullptr) { + result = this->req_->uri; + } else { + result = std::string(this->req_->uri, query_start - this->req_->uri); } - return std::string(this->req_->uri, str - this->req_->uri); + // Decode URL-encoded characters in-place (e.g., %20 -> space) + // This matches AsyncWebServer behavior on Arduino + if (!result.empty()) { + size_t new_len = url_decode(&result[0]); + result.resize(new_len); + } + return result; } std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); } @@ -300,8 +309,8 @@ void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code } httpd_resp_set_hdr(*this, "Accept-Ranges", "none"); - for (const auto &pair : DefaultHeaders::Instance().headers_) { - httpd_resp_set_hdr(*this, pair.first.c_str(), pair.second.c_str()); + for (const auto &header : DefaultHeaders::Instance().headers_) { + httpd_resp_set_hdr(*this, header.name, header.value); } delete this->rsp_; @@ -326,25 +335,38 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw return false; } - std::string user_info; - user_info += username; - user_info += ':'; - user_info += password; + // Build user:pass in stack buffer to avoid heap allocation + constexpr size_t max_user_info_len = 256; + char user_info[max_user_info_len]; + size_t user_len = strlen(username); + size_t pass_len = strlen(password); + size_t user_info_len = user_len + 1 + pass_len; + + if (user_info_len >= max_user_info_len) { + ESP_LOGW(TAG, "Credentials too long for authentication"); + return false; + } + + memcpy(user_info, username, user_len); + user_info[user_len] = ':'; + memcpy(user_info + user_len + 1, password, pass_len); + user_info[user_info_len] = '\0'; size_t n = 0, out; - esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast(user_info.c_str()), user_info.size()); + esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast(user_info), user_info_len); auto digest = std::unique_ptr(new char[n + 1]); esp_crypto_base64_encode(reinterpret_cast(digest.get()), n, &out, - reinterpret_cast(user_info.c_str()), user_info.size()); + reinterpret_cast(user_info), user_info_len); return strcmp(digest.get(), auth_str + auth_prefix_len) == 0; } void AsyncWebServerRequest::requestAuthentication(const char *realm) const { httpd_resp_set_hdr(*this, "Connection", "keep-alive"); - auto auth_val = str_sprintf("Basic realm=\"%s\"", realm ? realm : "Login Required"); - httpd_resp_set_hdr(*this, "WWW-Authenticate", auth_val.c_str()); + // Note: realm is never configured in ESPHome, always nullptr -> "Login Required" + (void) realm; // Unused - always use default + httpd_resp_set_hdr(*this, "WWW-Authenticate", "Basic realm=\"Login Required\""); httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr); } #endif @@ -473,8 +495,8 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); httpd_resp_set_hdr(req, "Connection", "keep-alive"); - for (const auto &pair : DefaultHeaders::Instance().headers_) { - httpd_resp_set_hdr(req, pair.first.c_str(), pair.second.c_str()); + for (const auto &header : DefaultHeaders::Instance().headers_) { + httpd_resp_set_hdr(req, header.name, header.value); } httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN); diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 5f9f598388..2a334a11e3 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -2,6 +2,7 @@ #ifdef USE_ESP32 #include "esphome/core/defines.h" +#include "esphome/core/helpers.h" #include #include @@ -80,6 +81,7 @@ class AsyncResponseStream : public AsyncWebServerResponse { void print(const std::string &str) { this->content_.append(str); } void print(float value); void printf(const char *fmt, ...) __attribute__((format(printf, 2, 3))); + void write(uint8_t c) { this->content_.push_back(static_cast(c)); } protected: std::string content_; @@ -326,6 +328,11 @@ class AsyncEventSource : public AsyncWebHandler { }; #endif // USE_WEBSERVER +struct HttpHeader { + const char *name; + const char *value; +}; + class DefaultHeaders { friend class AsyncWebServerRequest; #ifdef USE_WEBSERVER @@ -334,13 +341,14 @@ class DefaultHeaders { public: // NOLINTNEXTLINE(readability-identifier-naming) - void addHeader(const char *name, const char *value) { this->headers_.emplace_back(name, value); } + void addHeader(const char *name, const char *value) { this->headers_.push_back({name, value}); } // NOLINTNEXTLINE(readability-identifier-naming) static DefaultHeaders &Instance(); protected: - std::vector> headers_; + // Stack-allocated, no reallocation machinery. Count defined in web_server_base where headers are added. + StaticVector headers_; }; } // namespace web_server_idf diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp index ebe987cc65..3384a0572f 100644 --- a/esphome/components/weikai/weikai.cpp +++ b/esphome/components/weikai/weikai.cpp @@ -245,10 +245,8 @@ void WeikaiGPIOPin::setup() { this->pin_mode(this->flags_); } -std::string WeikaiGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via WeiKai %s", this->pin_, this->parent_->get_name()); - return buffer; +size_t WeikaiGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via WeiKai %s", this->pin_, this->parent_->get_name()); } /////////////////////////////////////////////////////////////////////////////// diff --git a/esphome/components/weikai/weikai.h b/esphome/components/weikai/weikai.h index 987278213a..a27c14106d 100644 --- a/esphome/components/weikai/weikai.h +++ b/esphome/components/weikai/weikai.h @@ -278,7 +278,7 @@ class WeikaiGPIOPin : public GPIOPin { gpio::Flags get_flags() const override { return this->flags_; } void setup() override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void pin_mode(gpio::Flags flags) override { this->parent_->set_pin_direction_(this->pin_, flags); } bool digital_read() override { return this->parent_->read_pin_val_(this->pin_) != this->inverted_; } void digital_write(bool value) override { this->parent_->write_pin_val_(this->pin_, value != this->inverted_); } diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index 1ac32f30da..6fe735362d 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -163,6 +163,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { } uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0}; + bool skip_footer = false; // Read all bytes. for (int i = 0; i < WHIRLPOOL_STATE_LENGTH; i++) { // Read bit @@ -170,6 +171,13 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_GAP)) return false; } + if (i == 14 && !data.is_valid()) { + // Remote control only sent 14 bytes, nothing more to read, not even the footer + ESP_LOGV(TAG, "Remote control only sent %d bytes", i); + skip_footer = true; + break; + } + for (int j = 0; j < 8; j++) { if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) { remote_state[i] |= 1 << j; @@ -183,7 +191,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]); } // Validate footer - if (!data.expect_mark(WHIRLPOOL_BIT_MARK)) { + if (!data.expect_mark(WHIRLPOOL_BIT_MARK) && !skip_footer) { ESP_LOGV(TAG, "Footer fail"); return false; } @@ -196,7 +204,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t i = 14; i < 20; i++) checksum20 ^= remote_state[i]; - if (checksum13 != remote_state[13] || checksum20 != remote_state[20]) { + if (checksum13 != remote_state[13] || (!skip_footer && checksum20 != remote_state[20])) { ESP_LOGVV(TAG, "Checksum fail"); return false; } diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 2c10506011..98266eb589 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -64,6 +64,7 @@ _LOGGER = logging.getLogger(__name__) NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4] CONF_SAVE = "save" CONF_MIN_AUTH_MODE = "min_auth_mode" +CONF_POST_CONNECT_ROAMING = "post_connect_roaming" # Maximum number of WiFi networks that can be configured # Limited to 127 because selected_sta_index_ is int8_t in C++ @@ -237,12 +238,21 @@ def _apply_min_auth_mode_default(config): def final_validate(config): has_sta = bool(config.get(CONF_NETWORKS, True)) has_ap = CONF_AP in config - has_improv = "esp32_improv" in fv.full_config.get() - has_improv_serial = "improv_serial" in fv.full_config.get() + full_config = fv.full_config.get() + has_improv = "esp32_improv" in full_config + has_improv_serial = "improv_serial" in full_config + has_captive_portal = "captive_portal" in full_config + has_web_server = "web_server" in full_config if not (has_sta or has_ap or has_improv or has_improv_serial): raise cv.Invalid( "Please specify at least an SSID or an Access Point to create." ) + if has_ap and not has_captive_portal and not has_web_server: + _LOGGER.warning( + "WiFi AP is configured but neither captive_portal nor web_server is enabled. " + "The AP will not be usable for configuration or monitoring. " + "Add 'captive_portal:' or 'web_server:' to your configuration." + ) FINAL_VALIDATE_SCHEMA = cv.All( @@ -348,11 +358,8 @@ CONFIG_SCHEMA = cv.All( cv.boolean, cv.only_on_esp32 ), cv.Optional(CONF_PASSIVE_SCAN, default=False): cv.boolean, - cv.Optional("enable_mdns"): cv.invalid( - "This option has been removed. Please use the [disabled] option under the " - "new mdns component instead." - ), cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, + cv.Optional(CONF_POST_CONNECT_ROAMING, default=True): cv.boolean, cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True), cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation( single=True @@ -468,7 +475,7 @@ async def to_code(config): ) cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT])) cg.add_define("USE_WIFI_AP") - elif CORE.is_esp32 and CORE.using_esp_idf: + elif CORE.is_esp32 and not CORE.using_arduino: add_idf_sdkconfig_option("CONFIG_ESP_WIFI_SOFTAP_SUPPORT", False) add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) @@ -495,6 +502,15 @@ async def to_code(config): if not config[CONF_ENABLE_ON_BOOT]: cg.add(var.set_enable_on_boot(False)) + # post_connect_roaming defaults to true in C++ - disable if user disabled it + # or if 802.11k/v is enabled (driver handles roaming natively) + if ( + not config[CONF_POST_CONNECT_ROAMING] + or config.get(CONF_ENABLE_BTM) + or config.get(CONF_ENABLE_RRM) + ): + cg.add(var.set_post_connect_roaming(False)) + if CORE.is_esp8266: cg.add_library("ESP8266WiFi", None) elif CORE.is_rp2040: @@ -513,7 +529,7 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True) # Apply high performance WiFi settings if high performance networking is enabled - if CORE.is_esp32 and CORE.using_esp_idf and has_high_performance_networking(): + if CORE.is_esp32 and has_high_performance_networking(): # Check if PSRAM is guaranteed (set by psram component during final validation) psram_guaranteed = psram_is_guaranteed() @@ -608,7 +624,11 @@ async def wifi_disable_to_code(config, action_id, template_arg, args): KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results" RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save" -WIFI_LISTENERS_KEY = "wifi_listeners" +# Keys for listener counts +IP_STATE_LISTENERS_KEY = "wifi_ip_state_listeners" +SCAN_RESULTS_LISTENERS_KEY = "wifi_scan_results_listeners" +CONNECT_STATE_LISTENERS_KEY = "wifi_connect_state_listeners" +POWER_SAVE_LISTENERS_KEY = "wifi_power_save_listeners" def request_wifi_scan_results(): @@ -634,15 +654,28 @@ def enable_runtime_power_save_control(): CORE.data[RUNTIME_POWER_SAVE_KEY] = True -def request_wifi_listeners() -> None: - """Request that WiFi state listeners be compiled in. +def request_wifi_ip_state_listener() -> None: + """Request an IP state listener slot.""" + CORE.data[IP_STATE_LISTENERS_KEY] = CORE.data.get(IP_STATE_LISTENERS_KEY, 0) + 1 - Components that need to be notified about WiFi state changes (IP address changes, - scan results, connection state) should call this function during their code generation. - This enables the add_ip_state_listener(), add_scan_results_listener(), - and add_connect_state_listener() APIs. - """ - CORE.data[WIFI_LISTENERS_KEY] = True + +def request_wifi_scan_results_listener() -> None: + """Request a scan results listener slot.""" + CORE.data[SCAN_RESULTS_LISTENERS_KEY] = ( + CORE.data.get(SCAN_RESULTS_LISTENERS_KEY, 0) + 1 + ) + + +def request_wifi_connect_state_listener() -> None: + """Request a connect state listener slot.""" + CORE.data[CONNECT_STATE_LISTENERS_KEY] = ( + CORE.data.get(CONNECT_STATE_LISTENERS_KEY, 0) + 1 + ) + + +def request_wifi_power_save_listener() -> None: + """Request a power save listener slot.""" + CORE.data[POWER_SAVE_LISTENERS_KEY] = CORE.data.get(POWER_SAVE_LISTENERS_KEY, 0) + 1 @coroutine_with_priority(CoroPriority.FINAL) @@ -654,8 +687,25 @@ async def final_step(): ) if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False): cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE") - if CORE.data.get(WIFI_LISTENERS_KEY, False): - cg.add_define("USE_WIFI_LISTENERS") + + # Generate listener defines - each listener type has its own #ifdef + ip_state_count = CORE.data.get(IP_STATE_LISTENERS_KEY, 0) + scan_results_count = CORE.data.get(SCAN_RESULTS_LISTENERS_KEY, 0) + connect_state_count = CORE.data.get(CONNECT_STATE_LISTENERS_KEY, 0) + power_save_count = CORE.data.get(POWER_SAVE_LISTENERS_KEY, 0) + + if ip_state_count: + cg.add_define("USE_WIFI_IP_STATE_LISTENERS") + cg.add_define("ESPHOME_WIFI_IP_STATE_LISTENERS", ip_state_count) + if scan_results_count: + cg.add_define("USE_WIFI_SCAN_RESULTS_LISTENERS") + cg.add_define("ESPHOME_WIFI_SCAN_RESULTS_LISTENERS", scan_results_count) + if connect_state_count: + cg.add_define("USE_WIFI_CONNECT_STATE_LISTENERS") + cg.add_define("ESPHOME_WIFI_CONNECT_STATE_LISTENERS", connect_state_count) + if power_save_count: + cg.add_define("USE_WIFI_POWER_SAVE_LISTENERS") + cg.add_define("ESPHOME_WIFI_POWER_SAVE_LISTENERS", power_save_count) @automation.register_action( diff --git a/esphome/components/wifi/automation.h b/esphome/components/wifi/automation.h index 7997baff65..fb0e71bcf6 100644 --- a/esphome/components/wifi/automation.h +++ b/esphome/components/wifi/automation.h @@ -45,7 +45,8 @@ template class WiFiConfigureAction : public Action, publi if (this->connecting_) return; // If already connected to the same AP, do nothing - if (global_wifi_component->wifi_ssid() == ssid) { + char ssid_buf[SSID_BUFFER_SIZE]; + if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), ssid.c_str()) == 0) { // Callback to notify the user that the connection was successful this->connect_trigger_->trigger(); return; @@ -94,7 +95,8 @@ template class WiFiConfigureAction : public Action, publi this->cancel_timeout("wifi-connect-timeout"); this->cancel_timeout("wifi-fallback-timeout"); this->connecting_ = false; - if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) { + char ssid_buf[SSID_BUFFER_SIZE]; + if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), this->new_sta_.get_ssid().c_str()) == 0) { // Callback to notify the user that the connection was successful this->connect_trigger_->trigger(); } else { diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index a5e8c4a59d..ff6284c073 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -2,6 +2,7 @@ #ifdef USE_WIFI #include #include +#include #ifdef USE_ESP32 #if (ESP_IDF_VERSION_MAJOR >= 5 && ESP_IDF_VERSION_MINOR >= 1) @@ -27,6 +28,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/string_ref.h" #include "esphome/core/util.h" #ifdef USE_CAPTIVE_PORTAL @@ -46,7 +48,7 @@ static const char *const TAG = "wifi"; /// The WiFi component uses a state machine with priority degradation to handle connection failures /// and automatically cycle through different BSSIDs in mesh networks or multiple configured networks. /// -/// Connection Flow: +/// Normal Connection Flow (SCAN_BASED): /// ┌──────────────────────────────────────────────────────────────────────┐ /// │ Fast Connect Path (Optional) │ /// ├──────────────────────────────────────────────────────────────────────┤ @@ -107,10 +109,13 @@ static const char *const TAG = "wifi"; /// │ (Skip Hidden1/Hidden2, try Hidden3 from example) │ /// │ - If none → Skip RETRY_HIDDEN, go to step 5 │ /// │ ↓ │ -/// │ 5. FAILED → RESTARTING_ADAPTER (skipped if AP/improv active) │ +/// │ 5. FAILED → RESTARTING_ADAPTER │ +/// │ - Normal: restart adapter, clear state │ +/// │ - AP/improv active: skip restart, just disconnect │ /// │ ↓ │ /// │ 6. Loop back to start: │ /// │ - If first network is hidden → EXPLICIT_HIDDEN (retry cycle) │ +/// │ - If AP/improv active → RETRY_HIDDEN (blind retry, see below) │ /// │ - Otherwise → SCAN_CONNECTING (rescan) │ /// │ ↓ │ /// │ 7. RESCAN → Apply stored priorities, sort again │ @@ -132,8 +137,10 @@ static const char *const TAG = "wifi"; /// - FAST_CONNECT_CYCLING_APS: Cycle through remaining configured networks (1 attempt each, fast_connect only) /// - EXPLICIT_HIDDEN: Try consecutive networks marked hidden:true before scanning (1 attempt per SSID) /// - SCAN_CONNECTING: Connect using scan results (2 attempts per BSSID) -/// - RETRY_HIDDEN: Try networks not found in scan (1 attempt per SSID, skipped if none found) -/// - RESTARTING_ADAPTER: Restart WiFi adapter to clear stuck state +/// - RETRY_HIDDEN: Behavior controlled by RetryHiddenMode: +/// * SCAN_BASED: Try networks not found in scan (truly hidden, 1 attempt per SSID) +/// * BLIND_RETRY: Cycle through ALL networks when scanning disabled (AP active) +/// - RESTARTING_ADAPTER: Restart WiFi adapter to clear stuck state (restart skipped if AP active) /// /// Hidden Network Handling: /// - Networks marked 'hidden: true' before first non-hidden → Tried in EXPLICIT_HIDDEN phase @@ -142,6 +149,88 @@ static const char *const TAG = "wifi"; /// - Networks not in scan results → Tried in RETRY_HIDDEN phase /// - Networks visible in scan + not marked hidden → Skipped in RETRY_HIDDEN phase /// - Networks marked 'hidden: true' always use hidden mode, even if broadcasting SSID +/// +/// ┌──────────────────────────────────────────────────────────────────────┐ +/// │ Captive Portal / Improv Mode (AP active, scanning disabled) │ +/// ├──────────────────────────────────────────────────────────────────────┤ +/// │ When captive_portal or esp32_improv is active, WiFi scanning is │ +/// │ disabled because it disrupts AP clients (radio leaves AP channel │ +/// │ to hop through other channels, causing client disconnections). │ +/// │ │ +/// │ Flow with RetryHiddenMode::BLIND_RETRY: │ +/// │ │ +/// │ 1. RESTARTING_ADAPTER → In this mode, skip adapter restart and │ +/// │ just disconnect (normal mode restarts the adapter) │ +/// │ - Sets retry_hidden_mode_ = BLIND_RETRY │ +/// │ - Enter extended cooldown (30s vs normal 500ms) │ +/// │ ↓ │ +/// │ 2. determine_next_phase_() returns RETRY_HIDDEN (skips scanning) │ +/// │ ↓ │ +/// │ 3. RETRY_HIDDEN with BLIND_RETRY mode: │ +/// │ - find_next_hidden_sta_() ignores scan_result_ │ +/// │ - ALL configured networks become candidates │ +/// │ - Cycles through networks: Net1 → Net2 → Net3 → ... │ +/// │ ↓ │ +/// │ 4. After exhausting all networks → Back to RESTARTING_ADAPTER │ +/// │ - Loop continues until connection succeeds or user configures │ +/// │ new credentials via captive portal │ +/// │ │ +/// │ The 30s cooldown gives users time to interact with captive portal │ +/// │ without constant connection attempts disrupting the AP. │ +/// └──────────────────────────────────────────────────────────────────────┘ +/// +/// ┌──────────────────────────────────────────────────────────────────────┐ +/// │ Post-Connect Roaming (for stationary devices) │ +/// ├──────────────────────────────────────────────────────────────────────┤ +/// │ Purpose: Handle AP reboot or power loss scenarios where device │ +/// │ connects to suboptimal AP and never switches back │ +/// │ │ +/// │ State Machine (RoamingState): │ +/// │ │ +/// │ ┌─────────────────────────────────────────────────────────────┐ │ +/// │ │ IDLE │ │ +/// │ │ (waiting for 5 min timer, attempts < 3) │ │ +/// │ └─────────────────────────┬───────────────────────────────────┘ │ +/// │ │ 5 min elapsed, RSSI < -49 dBm │ +/// │ ↓ │ +/// │ ┌─────────────────────────────────────────────────────────────┐ │ +/// │ │ SCANNING │ │ +/// │ │ (attempts++ in check_roaming_ before entering this state) │ │ +/// │ └─────────────────────────┬───────────────────────────────────┘ │ +/// │ │ │ +/// │ ┌──────────────┼──────────────┐ │ +/// │ ↓ ↓ ↓ │ +/// │ scan error no better AP +10 dB better AP │ +/// │ │ │ │ │ +/// │ ↓ ↓ ↓ │ +/// │ ┌──────────────────────────────┐ ┌──────────────────────────┐ │ +/// │ │ → IDLE │ │ CONNECTING │ │ +/// │ │ (counter preserved) │ │ (process_roaming_scan_) │ │ +/// │ └──────────────────────────────┘ └────────────┬─────────────┘ │ +/// │ │ │ +/// │ ┌───────────────────┴───────────────┐ │ +/// │ ↓ ↓ │ +/// │ SUCCESS FAILED │ +/// │ │ │ │ +/// │ ↓ ↓ │ +/// │ ┌──────────────────────────────────┐ ┌─────────────────────────┐ +/// │ │ → IDLE │ │ RECONNECTING │ +/// │ │ (counter reset to 0) │ │ (retry_connect called) │ +/// │ └──────────────────────────────────┘ └───────────┬─────────────┘ +/// │ │ │ +/// │ ↓ │ +/// │ ┌───────────────────────┐ │ +/// │ │ → IDLE │ │ +/// │ │ (counter preserved!) │ │ +/// │ └───────────────────────┘ │ +/// │ │ +/// │ Key behaviors: │ +/// │ - After 3 checks: attempts >= 3, stop checking │ +/// │ - Non-roaming disconnect: clear_roaming_state_() resets counter │ +/// │ - Scan error (SCANNING→IDLE): counter preserved │ +/// │ - Roaming success (CONNECTING→IDLE): counter reset (can roam again) │ +/// │ - Roaming fail (RECONNECTING→IDLE): counter preserved (ping-pong) │ +/// └──────────────────────────────────────────────────────────────────────┘ static const LogString *retry_phase_to_log_string(WiFiRetryPhase phase) { switch (phase) { @@ -277,7 +366,23 @@ bool WiFiComponent::ssid_was_seen_in_scan_(const std::string &ssid) const { } int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) { - // Find next SSID that wasn't in scan results (might be hidden) + // Find next SSID to try in RETRY_HIDDEN phase. + // + // This function operates in two modes based on retry_hidden_mode_: + // + // 1. SCAN_BASED mode: + // After SCAN_CONNECTING phase, only returns networks that were NOT visible + // in the scan (truly hidden networks that need probe requests). + // + // 2. BLIND_RETRY mode: + // When captive portal/improv is active, scanning is skipped to avoid + // disrupting the AP. In this mode, ALL configured networks are returned + // as candidates, cycling through them sequentially. This allows the device + // to keep trying all networks while users configure WiFi via captive portal. + // + // Additionally, if EXPLICIT_HIDDEN phase was executed (first network marked hidden:true), + // those networks are skipped here since they were already tried. + // bool include_explicit_hidden = !this->went_through_explicit_hidden_phase_(); // Start searching from start_index + 1 for (size_t i = start_index + 1; i < this->sta_.size(); i++) { @@ -294,9 +399,9 @@ int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) { } } - // If we didn't scan this cycle, treat all networks as potentially hidden - // Otherwise, only retry networks that weren't seen in the scan - if (!this->did_scan_this_cycle_ || !this->ssid_was_seen_in_scan_(sta.get_ssid())) { + // In BLIND_RETRY mode, treat all networks as candidates + // In SCAN_BASED mode, only retry networks that weren't seen in the scan + if (this->retry_hidden_mode_ == RetryHiddenMode::BLIND_RETRY || !this->ssid_was_seen_in_scan_(sta.get_ssid())) { ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast(i)); return static_cast(i); } @@ -316,7 +421,6 @@ void WiFiComponent::start_initial_connection_() { WiFiAP params = this->build_params_for_current_phase_(); this->start_connecting(params); } else { - ESP_LOGI(TAG, "Starting scan"); this->start_scanning(); } } @@ -368,14 +472,10 @@ void WiFiComponent::setup() { } void WiFiComponent::start() { - char mac_s[18]; - ESP_LOGCONFIG(TAG, - "Starting\n" - " Local MAC: %s", - get_mac_address_pretty_into_buffer(mac_s)); + ESP_LOGCONFIG(TAG, "Starting"); this->last_connected_ = millis(); - uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time_ref().c_str()) : 88491487UL; + uint32_t hash = this->has_sta() ? App.get_config_version_hash() : 88491487UL; this->pref_ = global_preferences->make_preference(hash, true); #ifdef USE_WIFI_FAST_CONNECT @@ -394,7 +494,7 @@ void WiFiComponent::start() { if (this->has_sta()) { this->wifi_sta_pre_setup_(); - if (this->output_power_.has_value() && !this->wifi_apply_output_power_(*this->output_power_)) { + if (!std::isnan(this->output_power_) && !this->wifi_apply_output_power_(this->output_power_)) { ESP_LOGV(TAG, "Setting Output Power Option failed"); } @@ -441,7 +541,7 @@ void WiFiComponent::start() { #ifdef USE_WIFI_AP } else if (this->has_ap()) { this->setup_ap_config_(); - if (this->output_power_.has_value() && !this->wifi_apply_output_power_(*this->output_power_)) { + if (!std::isnan(this->output_power_) && !this->wifi_apply_output_power_(this->output_power_)) { ESP_LOGV(TAG, "Setting Output Power Option failed"); } #ifdef USE_CAPTIVE_PORTAL @@ -488,7 +588,7 @@ void WiFiComponent::loop() { // Skip cooldown if new credentials were provided while connecting if (this->skip_cooldown_next_cycle_) { this->skip_cooldown_next_cycle_ = false; - this->check_connecting_finished(); + this->check_connecting_finished(now); break; } // Use longer cooldown when captive portal/improv is active to avoid disrupting user config @@ -499,7 +599,7 @@ void WiFiComponent::loop() { // a failure, or something tried to connect over and over // so we entered cooldown. In both cases we call // check_connecting_finished to continue the state machine. - this->check_connecting_finished(); + this->check_connecting_finished(now); } break; } @@ -510,7 +610,7 @@ void WiFiComponent::loop() { } case WIFI_COMPONENT_STATE_STA_CONNECTING: { this->status_set_warning(LOG_STR("associating to network")); - this->check_connecting_finished(); + this->check_connecting_finished(now); break; } @@ -524,6 +624,19 @@ void WiFiComponent::loop() { } else { this->status_clear_warning(); this->last_connected_ = now; + + // Post-connect roaming: check for better AP + if (this->post_connect_roaming_) { + if (this->roaming_state_ == RoamingState::SCANNING) { + if (this->scan_done_) { + this->process_roaming_scan_(); + } + // else: scan in progress, wait + } else if (this->roaming_state_ == RoamingState::IDLE && this->roaming_attempts_ < ROAMING_MAX_ATTEMPTS && + now - this->roaming_last_check_ >= ROAMING_CHECK_INTERVAL) { + this->check_roaming_(now); + } + } } break; } @@ -643,23 +756,26 @@ void WiFiComponent::setup_ap_config_() { } this->ap_setup_ = this->wifi_start_ap_(this->ap_); - auto ip_address = this->wifi_soft_ap_ip().str(); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "Setting up AP:\n" " AP SSID: '%s'\n" " AP Password: '%s'\n" " IP Address: %s", - this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), ip_address.c_str()); + this->ap_.get_ssid().c_str(), this->ap_.get_password().c_str(), this->wifi_soft_ap_ip().str_to(ip_buf)); #ifdef USE_WIFI_MANUAL_IP auto manual_ip = this->ap_.get_manual_ip(); if (manual_ip.has_value()) { + char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " AP Static IP: '%s'\n" " AP Gateway: '%s'\n" " AP Subnet: '%s'", - manual_ip->static_ip.str().c_str(), manual_ip->gateway.str().c_str(), - manual_ip->subnet.str().c_str()); + manual_ip->static_ip.str_to(static_ip_buf), manual_ip->gateway.str_to(gateway_buf), + manual_ip->subnet.str_to(subnet_buf)); } #endif @@ -680,8 +796,14 @@ float WiFiComponent::get_loop_priority() const { void WiFiComponent::init_sta(size_t count) { this->sta_.init(count); } void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); } +void WiFiComponent::clear_sta() { + // Clear roaming state - no more configured networks + this->clear_roaming_state_(); + this->sta_.clear(); + this->selected_sta_index_ = -1; +} void WiFiComponent::set_sta(const WiFiAP &ap) { - this->clear_sta(); + this->clear_sta(); // Also clears roaming state this->init_sta(1); this->add_sta(ap); this->selected_sta_index_ = 0; @@ -713,8 +835,8 @@ WiFiAP WiFiComponent::build_params_for_current_phase_() { case WiFiRetryPhase::RETRY_HIDDEN: // Hidden network mode: clear BSSID/channel to trigger probe request // (both explicit hidden and retry hidden use same behavior) - params.set_bssid(optional{}); - params.set_channel(optional{}); + params.clear_bssid(); + params.clear_channel(); break; case WiFiRetryPhase::SCAN_CONNECTING: @@ -766,21 +888,22 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { char bssid_s[18]; int8_t priority = 0; - if (ap.get_bssid().has_value()) { - format_mac_addr_upper(ap.get_bssid().value().data(), bssid_s); - priority = this->get_sta_priority(ap.get_bssid().value()); + if (ap.has_bssid()) { + format_mac_addr_upper(ap.get_bssid().data(), bssid_s); + priority = this->get_sta_priority(ap.get_bssid()); } ESP_LOGI(TAG, "Connecting to " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") " (priority %d, attempt %u/%u in phase %s)...", - ap.get_ssid().c_str(), ap.get_bssid().has_value() ? bssid_s : LOG_STR_LITERAL("any"), priority, - this->num_retried_ + 1, get_max_retries_for_phase(this->retry_phase_), - LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); + ap.get_ssid().c_str(), ap.has_bssid() ? bssid_s : LOG_STR_LITERAL("any"), priority, this->num_retried_ + 1, + get_max_retries_for_phase(this->retry_phase_), LOG_STR_ARG(retry_phase_to_log_string(this->retry_phase_))); #ifdef ESPHOME_LOG_HAS_VERBOSE - ESP_LOGV(TAG, "Connection Params:"); - ESP_LOGV(TAG, " SSID: '%s'", ap.get_ssid().c_str()); - if (ap.get_bssid().has_value()) { + ESP_LOGV(TAG, + "Connection Params:\n" + " SSID: '%s'", + ap.get_ssid().c_str()); + if (ap.has_bssid()) { ESP_LOGV(TAG, " BSSID: %s", bssid_s); } else { ESP_LOGV(TAG, " BSSID: Not Set"); @@ -788,36 +911,50 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { #ifdef USE_WIFI_WPA2_EAP if (ap.get_eap().has_value()) { - ESP_LOGV(TAG, " WPA2 Enterprise authentication configured:"); EAPAuth eap_config = ap.get_eap().value(); - ESP_LOGV(TAG, " Identity: " LOG_SECRET("'%s'"), eap_config.identity.c_str()); - ESP_LOGV(TAG, " Username: " LOG_SECRET("'%s'"), eap_config.username.c_str()); - ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), eap_config.password.c_str()); + // clang-format off + ESP_LOGV( + TAG, + " WPA2 Enterprise authentication configured:\n" + " Identity: " LOG_SECRET("'%s'") "\n" + " Username: " LOG_SECRET("'%s'") "\n" + " Password: " LOG_SECRET("'%s'"), + eap_config.identity.c_str(), eap_config.username.c_str(), eap_config.password.c_str()); + // clang-format on #if defined(USE_ESP32) && defined(USE_WIFI_WPA2_EAP) && ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGV(TAG, " TTLS Phase 2: " LOG_SECRET("'%s'"), eap_phase2_to_str(eap_config.ttls_phase_2)); #endif bool ca_cert_present = eap_config.ca_cert != nullptr && strlen(eap_config.ca_cert); bool client_cert_present = eap_config.client_cert != nullptr && strlen(eap_config.client_cert); bool client_key_present = eap_config.client_key != nullptr && strlen(eap_config.client_key); - ESP_LOGV(TAG, " CA Cert: %s", ca_cert_present ? "present" : "not present"); - ESP_LOGV(TAG, " Client Cert: %s", client_cert_present ? "present" : "not present"); - ESP_LOGV(TAG, " Client Key: %s", client_key_present ? "present" : "not present"); + ESP_LOGV(TAG, + " CA Cert: %s\n" + " Client Cert: %s\n" + " Client Key: %s", + ca_cert_present ? "present" : "not present", client_cert_present ? "present" : "not present", + client_key_present ? "present" : "not present"); } else { #endif ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), ap.get_password().c_str()); #ifdef USE_WIFI_WPA2_EAP } #endif - if (ap.get_channel().has_value()) { - ESP_LOGV(TAG, " Channel: %u", *ap.get_channel()); + if (ap.has_channel()) { + ESP_LOGV(TAG, " Channel: %u", ap.get_channel()); } else { ESP_LOGV(TAG, " Channel not set"); } #ifdef USE_WIFI_MANUAL_IP if (ap.get_manual_ip().has_value()) { ManualIP m = *ap.get_manual_ip(); - ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(), - m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str()); + char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str_to(static_ip_buf), + m.gateway.str_to(gateway_buf), m.subnet.str_to(subnet_buf), m.dns1.str_to(dns1_buf), + m.dns2.str_to(dns2_buf)); } else #endif { @@ -838,14 +975,6 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { } const LogString *get_signal_bars(int8_t rssi) { - // Check for disconnected sentinel value first - if (rssi == WIFI_RSSI_DISCONNECTED) { - // MULTIPLICATION SIGN - // Unicode: U+00D7, UTF-8: C3 97 - return LOG_STR("\033[0;31m" // red - "\xc3\x97\xc3\x97\xc3\x97\xc3\x97" - "\033[0m"); - } // LOWER ONE QUARTER BLOCK // Unicode: U+2582, UTF-8: E2 96 82 // LOWER HALF BLOCK @@ -890,37 +1019,40 @@ const LogString *get_signal_bars(int8_t rssi) { void WiFiComponent::print_connect_params_() { bssid_t bssid = wifi_bssid(); - char bssid_s[18]; + char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(bssid.data(), bssid_s); - - char mac_s[18]; - ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty_into_buffer(mac_s)); - if (this->is_disabled()) { - ESP_LOGCONFIG(TAG, " Disabled"); - return; - } + // Use stack buffers for IP address formatting to avoid heap allocations + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : wifi_sta_ip_addresses()) { if (ip.is_set()) { - ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str_to(ip_buf)); } } int8_t rssi = wifi_rssi(); + // Use stack buffers for SSID and all IP addresses to avoid heap allocations + char ssid_buf[SSID_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; + // clang-format off ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'") "\n" - " BSSID: " LOG_SECRET("%s") "\n" - " Hostname: '%s'\n" - " Signal strength: %d dB %s\n" - " Channel: %" PRId32 "\n" - " Subnet: %s\n" - " Gateway: %s\n" - " DNS1: %s\n" - " DNS2: %s", - wifi_ssid().c_str(), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), - get_wifi_channel(), wifi_subnet_mask_().str().c_str(), wifi_gateway_ip_().str().c_str(), - wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); + " BSSID: " LOG_SECRET("%s") "\n" + " Hostname: '%s'\n" + " Signal strength: %d dB %s\n" + " Channel: %" PRId32 "\n" + " Subnet: %s\n" + " Gateway: %s\n" + " DNS1: %s\n" + " DNS2: %s", + wifi_ssid_to(ssid_buf), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), + get_wifi_channel(), wifi_subnet_mask_().str_to(subnet_buf), wifi_gateway_ip_().str_to(gateway_buf), + wifi_dns_ip_(0).str_to(dns1_buf), wifi_dns_ip_(1).str_to(dns2_buf)); + // clang-format on #ifdef ESPHOME_LOG_HAS_VERBOSE - if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid().has_value()) { - ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(*config->get_bssid())); + if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) { + ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(config->get_bssid())); } #endif #ifdef USE_WIFI_11KV_SUPPORT @@ -1031,23 +1163,42 @@ template static void insertion_sort_scan_results(VectorType } } -// Helper function to log scan results - marked noinline to prevent re-inlining into loop +// Helper function to log matching scan results - marked noinline to prevent re-inlining into loop +// +// IMPORTANT: This function deliberately uses a SINGLE log call to minimize blocking. +// In environments with many matching networks (e.g., 18+ mesh APs), multiple log calls +// per network would block the main loop for an unacceptable duration. Each log call +// has overhead from UART transmission, so combining INFO+DEBUG into one line halves +// the blocking time. Do NOT split this into separate ESP_LOGI/ESP_LOGD calls. __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) { char bssid_s[18]; auto bssid = res.get_bssid(); format_mac_addr_upper(bssid.data(), bssid_s); - if (res.get_matches()) { - ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), - res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, - LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - ESP_LOGD(TAG, " Channel: %2u, RSSI: %3d dB, Priority: %4d", res.get_channel(), res.get_rssi(), res.get_priority()); - } else { - ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, - LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG + // Single combined log line with all details when DEBUG enabled + ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s Ch:%2u %3ddB P:%d", res.get_ssid().c_str(), + res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi())), res.get_channel(), res.get_rssi(), res.get_priority()); +#else + ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), + res.get_is_hidden() ? LOG_STR_LITERAL("(HIDDEN) ") : LOG_STR_LITERAL(""), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi()))); +#endif } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE +// Helper function to log non-matching scan results at verbose level +__attribute__((noinline)) static void log_scan_result_non_matching(const WiFiScanResult &res) { + char bssid_s[18]; + auto bssid = res.get_bssid(); + format_mac_addr_upper(bssid.data(), bssid_s); + + ESP_LOGV(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, + LOG_STR_ARG(get_signal_bars(res.get_rssi()))); +} +#endif + void WiFiComponent::check_scanning_finished() { if (!this->scan_done_) { if (millis() - this->action_started_ > WIFI_SCAN_TIMEOUT_MS) { @@ -1057,7 +1208,7 @@ void WiFiComponent::check_scanning_finished() { return; } this->scan_done_ = false; - this->did_scan_this_cycle_ = true; + this->retry_hidden_mode_ = RetryHiddenMode::SCAN_BASED; if (this->scan_result_.empty()) { ESP_LOGW(TAG, "No networks found"); @@ -1084,8 +1235,20 @@ void WiFiComponent::check_scanning_finished() { // Sort scan results using insertion sort for better memory efficiency insertion_sort_scan_results(this->scan_result_); + size_t non_matching_count = 0; for (auto &res : this->scan_result_) { - log_scan_result(res); + if (res.get_matches()) { + log_scan_result(res); + } else { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + log_scan_result_non_matching(res); +#else + non_matching_count++; +#endif + } + } + if (non_matching_count > 0) { + ESP_LOGD(TAG, "- %zu non-matching (VERBOSE to show)", non_matching_count); } // SYNCHRONIZATION POINT: Establish link between scan_result_[0] and selected_sta_index_ @@ -1129,18 +1292,27 @@ void WiFiComponent::check_scanning_finished() { } void WiFiComponent::dump_config() { + char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "WiFi:\n" + " Local MAC: %s\n" " Connected: %s", - YESNO(this->is_connected())); - this->print_connect_params_(); + get_mac_address_pretty_into_buffer(mac_s), YESNO(this->is_connected())); + if (this->is_disabled()) { + ESP_LOGCONFIG(TAG, " Disabled"); + return; + } + if (this->is_connected()) { + this->print_connect_params_(); + } } -void WiFiComponent::check_connecting_finished() { +void WiFiComponent::check_connecting_finished(uint32_t now) { auto status = this->wifi_sta_connect_status_(); if (status == WiFiSTAConnectStatus::CONNECTED) { - if (wifi_ssid().empty()) { + char ssid_buf[SSID_BUFFER_SIZE]; + if (wifi_ssid_to(ssid_buf)[0] == '\0') { ESP_LOGW(TAG, "Connection incomplete"); this->retry_connect(); return; @@ -1162,8 +1334,6 @@ void WiFiComponent::check_connecting_finished() { // the first connection as a failure. this->error_from_callback_ = false; - this->print_connect_params_(); - if (this->has_ap()) { #ifdef USE_CAPTIVE_PORTAL if (this->is_captive_portal_active_()) { @@ -1181,24 +1351,38 @@ void WiFiComponent::check_connecting_finished() { this->state_ = WIFI_COMPONENT_STATE_STA_CONNECTED; this->num_retried_ = 0; + this->print_connect_params_(); - // Clear priority tracking if all priorities are at minimum - this->clear_priorities_if_all_min_(); + // Reset roaming state on successful connection + this->roaming_last_check_ = now; + // Only preserve attempts if reconnecting after a failed roam attempt + // This prevents ping-pong between APs when a roam target is unreachable + if (this->roaming_state_ == RoamingState::CONNECTING) { + // Successful roam to better AP - reset attempts so we can roam again later + ESP_LOGD(TAG, "Roam successful"); + this->roaming_attempts_ = 0; + } else if (this->roaming_state_ == RoamingState::RECONNECTING) { + // Failed roam, reconnected via normal recovery - keep attempts to prevent ping-pong + ESP_LOGD(TAG, "Reconnected after failed roam (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); + } else { + // Normal connection (boot, credentials changed, etc.) + this->roaming_attempts_ = 0; + } + this->roaming_state_ = RoamingState::IDLE; + + // Clear all priority penalties - the next reconnect will happen when an AP disconnects, + // which means the landscape has likely changed and previous tracked failures are stale + this->clear_all_bssid_priorities_(); #ifdef USE_WIFI_FAST_CONNECT this->save_fast_connect_settings_(); #endif - // Free scan results memory unless a component needs them - if (!this->keep_scan_results_) { - this->scan_result_.clear(); - this->scan_result_.shrink_to_fit(); - } + this->release_scan_results_(); return; } - uint32_t now = millis(); if (now - this->action_started_ > WIFI_CONNECT_TIMEOUT_MS) { ESP_LOGW(TAG, "Connection timeout, aborting connection attempt"); this->wifi_disconnect_(); @@ -1329,8 +1513,23 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() { if (this->went_through_explicit_hidden_phase_()) { return WiFiRetryPhase::EXPLICIT_HIDDEN; } - // Skip scanning when captive portal/improv is active to avoid disrupting AP - // Even passive scans can cause brief AP disconnections on ESP32 + // Skip scanning when captive portal/improv is active to avoid disrupting AP. + // + // WHY SCANNING DISRUPTS AP MODE: + // WiFi scanning requires the radio to leave the AP's channel and hop through + // other channels to listen for beacons. During this time (even for passive scans), + // the AP cannot service connected clients - they experience disconnections or + // timeouts. On ESP32, even passive scans cause brief but noticeable disruptions + // that break captive portal HTTP requests and DNS lookups. + // + // BLIND RETRY MODE: + // When captive portal/improv is active, we use RETRY_HIDDEN as a "try all networks + // blindly" mode. Since retry_hidden_mode_ is set to BLIND_RETRY (in RESTARTING_ADAPTER + // transition), find_next_hidden_sta_() will treat ALL configured networks as + // candidates, cycling through them without requiring scan results. + // + // This allows users to configure WiFi via captive portal while the device keeps + // attempting to connect to all configured networks in sequence. if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) { return WiFiRetryPhase::RETRY_HIDDEN; } @@ -1399,19 +1598,19 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { break; case WiFiRetryPhase::RETRY_HIDDEN: - // Starting hidden mode - find first SSID that wasn't in scan results - if (old_phase == WiFiRetryPhase::SCAN_CONNECTING) { - // Keep scan results so we can skip SSIDs that were visible in the scan - // Don't clear scan_result_ - we need it to know which SSIDs are NOT hidden + // Always reset to first candidate when entering this phase. + // This phase can be entered from: + // - SCAN_CONNECTING: normal flow, find_next_hidden_sta_() skips networks visible in scan + // - RESTARTING_ADAPTER: captive portal active, find_next_hidden_sta_() tries ALL networks + // + // The retry_hidden_mode_ controls the behavior: + // - SCAN_BASED: scan_result_ is checked, visible networks are skipped + // - BLIND_RETRY: scan_result_ is ignored, all networks become candidates + // We don't clear scan_result_ here - the mode controls whether it's consulted. + this->selected_sta_index_ = this->find_next_hidden_sta_(-1); - // If first network is marked hidden, we went through EXPLICIT_HIDDEN phase - // In that case, skip networks marked hidden:true (already tried) - // Otherwise, include them (they haven't been tried yet) - this->selected_sta_index_ = this->find_next_hidden_sta_(-1); - - if (this->selected_sta_index_ == -1) { - ESP_LOGD(TAG, "All SSIDs visible or already tried, skipping hidden mode"); - } + if (this->selected_sta_index_ == -1) { + ESP_LOGD(TAG, "All SSIDs visible or already tried, skipping hidden mode"); } break; @@ -1427,7 +1626,11 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { this->wifi_disconnect_(); } // Clear scan flag - we're starting a new retry cycle - this->did_scan_this_cycle_ = false; + // This is critical for captive portal/improv flow: when determine_next_phase_() + // returns RETRY_HIDDEN (because scanning is skipped), find_next_hidden_sta_() + // will see BLIND_RETRY mode and treat ALL networks as candidates, + // effectively cycling through all configured networks without scan results. + this->retry_hidden_mode_ = RetryHiddenMode::BLIND_RETRY; // Always enter cooldown after restart (or skip-restart) to allow stabilization // Use extended cooldown when AP is active to avoid constant scanning that blocks DNS this->state_ = WIFI_COMPONENT_STATE_COOLDOWN; @@ -1442,9 +1645,15 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) { return false; // Did not start scan, can proceed with connection } +void WiFiComponent::clear_all_bssid_priorities_() { + if (!this->sta_priorities_.empty()) { + decltype(this->sta_priorities_)().swap(this->sta_priorities_); + } +} + /// Clear BSSID priority tracking if all priorities are at minimum (saves memory) /// At minimum priority, all BSSIDs are equally bad, so priority tracking is useless -/// Called after successful connection or after failed connection attempts +/// Called after failed connection attempts void WiFiComponent::clear_priorities_if_all_min_() { if (this->sta_priorities_.empty()) { return; @@ -1466,8 +1675,7 @@ void WiFiComponent::clear_priorities_if_all_min_() { // All priorities are at minimum - clear the vector to save memory and reset ESP_LOGD(TAG, "Clearing BSSID priorities (all at minimum)"); - this->sta_priorities_.clear(); - this->sta_priorities_.shrink_to_fit(); + this->clear_all_bssid_priorities_(); } /// Log failed connection attempt and decrease BSSID priority to avoid repeated failures @@ -1495,21 +1703,21 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { // Scan-based phase: always use best result (index 0) failed_bssid = this->scan_result_[0].get_bssid(); - } else if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_bssid()) { + } else if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) { // Config has specific BSSID (fast_connect or user-specified) - failed_bssid = *config->get_bssid(); + failed_bssid = config->get_bssid(); } if (!failed_bssid.has_value()) { return; // No BSSID to penalize } - // Get SSID for logging - std::string ssid; + // Get SSID for logging (use pointer to avoid copy) + const std::string *ssid = nullptr; if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { - ssid = this->scan_result_[0].get_ssid(); + ssid = &this->scan_result_[0].get_ssid(); } else if (const WiFiAP *config = this->get_selected_sta_()) { - ssid = config->get_ssid(); + ssid = &config->get_ssid(); } // Only decrease priority on the last attempt for this phase @@ -1529,8 +1737,8 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { } char bssid_s[18]; format_mac_addr_upper(failed_bssid.value().data(), bssid_s); - ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(), bssid_s, - old_priority, new_priority); + ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", + ssid != nullptr ? ssid->c_str() : "", bssid_s, old_priority, new_priority); // After adjusting priority, check if all priorities are now at minimum // If so, clear the vector to save memory and reset for fresh start @@ -1605,6 +1813,22 @@ void WiFiComponent::advance_to_next_target_or_increment_retry_() { } void WiFiComponent::retry_connect() { + // Handle roaming state transitions - preserve attempts counter to prevent ping-pong + // to unreachable APs after ROAMING_MAX_ATTEMPTS failures + if (this->roaming_state_ == RoamingState::CONNECTING) { + // Roam connection failed - transition to reconnecting + ESP_LOGD(TAG, "Roam failed, reconnecting (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); + this->roaming_state_ = RoamingState::RECONNECTING; + } else if (this->roaming_state_ == RoamingState::SCANNING) { + // Roam scan failed (e.g., scan error on ESP8266) - go back to idle, keep counter + ESP_LOGD(TAG, "Roam scan failed (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); + this->roaming_state_ = RoamingState::IDLE; + } else if (this->roaming_state_ == RoamingState::IDLE) { + // Not a roaming-triggered reconnect, reset state + this->clear_roaming_state_(); + } + // RECONNECTING: keep state and counter, still trying to reconnect + this->log_and_adjust_priority_for_failed_connect_(); // Determine next retry phase based on current state @@ -1765,24 +1989,27 @@ void WiFiComponent::save_fast_connect_settings_() { #endif void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; } -void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; } -void WiFiAP::set_bssid(optional bssid) { this->bssid_ = bssid; } +void WiFiAP::set_bssid(const bssid_t &bssid) { this->bssid_ = bssid; } +void WiFiAP::clear_bssid() { this->bssid_ = {}; } void WiFiAP::set_password(const std::string &password) { this->password_ = password; } #ifdef USE_WIFI_WPA2_EAP void WiFiAP::set_eap(optional eap_auth) { this->eap_ = std::move(eap_auth); } #endif -void WiFiAP::set_channel(optional channel) { this->channel_ = channel; } +void WiFiAP::set_channel(uint8_t channel) { this->channel_ = channel; } +void WiFiAP::clear_channel() { this->channel_ = 0; } #ifdef USE_WIFI_MANUAL_IP void WiFiAP::set_manual_ip(optional manual_ip) { this->manual_ip_ = manual_ip; } #endif void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; } const std::string &WiFiAP::get_ssid() const { return this->ssid_; } -const optional &WiFiAP::get_bssid() const { return this->bssid_; } +const bssid_t &WiFiAP::get_bssid() const { return this->bssid_; } +bool WiFiAP::has_bssid() const { return this->bssid_ != bssid_t{}; } const std::string &WiFiAP::get_password() const { return this->password_; } #ifdef USE_WIFI_WPA2_EAP const optional &WiFiAP::get_eap() const { return this->eap_; } #endif -const optional &WiFiAP::get_channel() const { return this->channel_; } +uint8_t WiFiAP::get_channel() const { return this->channel_; } +bool WiFiAP::has_channel() const { return this->channel_ != 0; } #ifdef USE_WIFI_MANUAL_IP const optional &WiFiAP::get_manual_ip() const { return this->manual_ip_; } #endif @@ -1810,7 +2037,7 @@ bool WiFiScanResult::matches(const WiFiAP &config) const { // network is configured without SSID - match other settings } // If BSSID configured, only match for correct BSSIDs - if (config.get_bssid().has_value() && *config.get_bssid() != this->bssid_) + if (config.has_bssid() && config.get_bssid() != this->bssid_) return false; #ifdef USE_WIFI_WPA2_EAP @@ -1828,7 +2055,7 @@ bool WiFiScanResult::matches(const WiFiAP &config) const { #endif // If channel configured, only match networks on that channel. - if (config.get_channel().has_value() && *config.get_channel() != this->channel_) { + if (config.has_channel() && config.get_channel() != this->channel_) { return false; } return true; @@ -1844,6 +2071,110 @@ bool WiFiScanResult::get_is_hidden() const { return this->is_hidden_; } bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this->bssid_ == rhs.bssid_; } +void WiFiComponent::clear_roaming_state_() { + this->roaming_attempts_ = 0; + this->roaming_last_check_ = 0; + this->roaming_state_ = RoamingState::IDLE; +} + +void WiFiComponent::release_scan_results_() { + if (!this->keep_scan_results_) { +#ifdef USE_RP2040 + // std::vector - use swap trick since shrink_to_fit is non-binding + decltype(this->scan_result_)().swap(this->scan_result_); +#else + // FixedVector::release() frees all memory + this->scan_result_.release(); +#endif + } +} + +void WiFiComponent::check_roaming_(uint32_t now) { + // Guard: not for hidden networks (may not appear in scan) + const WiFiAP *selected = this->get_selected_sta_(); + if (selected == nullptr || selected->get_hidden()) { + this->roaming_attempts_ = ROAMING_MAX_ATTEMPTS; // Stop checking forever + return; + } + + this->roaming_last_check_ = now; + this->roaming_attempts_++; + + // Guard: skip scan if signal is already good (no meaningful improvement possible) + int8_t rssi = this->wifi_rssi(); + if (rssi > ROAMING_GOOD_RSSI) { + ESP_LOGV(TAG, "Roam check skipped, signal good (%d dBm, attempt %u/%u)", rssi, this->roaming_attempts_, + ROAMING_MAX_ATTEMPTS); + return; + } + + ESP_LOGD(TAG, "Roam scan (%d dBm, attempt %u/%u)", rssi, this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); + this->roaming_state_ = RoamingState::SCANNING; + this->wifi_scan_start_(this->passive_scan_); +} + +void WiFiComponent::process_roaming_scan_() { + this->scan_done_ = false; + // Default to IDLE - will be set to CONNECTING if we find a better AP + this->roaming_state_ = RoamingState::IDLE; + + // Get current connection info + int8_t current_rssi = this->wifi_rssi(); + // Guard: must still be connected (RSSI may have become invalid during scan) + if (current_rssi == WIFI_RSSI_DISCONNECTED) { + this->release_scan_results_(); + return; + } + + char ssid_buf[SSID_BUFFER_SIZE]; + StringRef current_ssid(this->wifi_ssid_to(ssid_buf)); + bssid_t current_bssid = this->wifi_bssid(); + + // Find best candidate: same SSID, different BSSID + const WiFiScanResult *best = nullptr; + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + + for (const auto &result : this->scan_result_) { + // Must be same SSID, different BSSID + if (current_ssid != result.get_ssid() || result.get_bssid() == current_bssid) + continue; + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + format_mac_addr_upper(result.get_bssid().data(), bssid_buf); + ESP_LOGV(TAG, "Roam candidate %s %d dBm", bssid_buf, result.get_rssi()); +#endif + + // Track the best candidate + if (best == nullptr || result.get_rssi() > best->get_rssi()) { + best = &result; + } + } + + // Check if best candidate meets minimum improvement threshold + const WiFiAP *selected = this->get_selected_sta_(); + int8_t improvement = (best == nullptr) ? 0 : best->get_rssi() - current_rssi; + if (selected == nullptr || improvement < ROAMING_MIN_IMPROVEMENT) { + ESP_LOGV(TAG, "Roam best %+d dB (need +%d), attempt %u/%u", improvement, ROAMING_MIN_IMPROVEMENT, + this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); + this->release_scan_results_(); + return; + } + + format_mac_addr_upper(best->get_bssid().data(), bssid_buf); + ESP_LOGI(TAG, "Roaming to %s (%+d dB)", bssid_buf, improvement); + + WiFiAP roam_params = *selected; + apply_scan_result_to_params(roam_params, *best); + this->release_scan_results_(); + + // Mark as roaming attempt - affects retry behavior if connection fails + this->roaming_state_ = RoamingState::CONNECTING; + + // Connect directly - wifi_sta_connect_ handles disconnect internally + this->error_from_callback_ = false; + this->start_connecting(roam_params); +} + WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome::wifi diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index be94e9462b..dfc91fb5da 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -6,16 +6,12 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" +#include #include #include -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include -#include -#include -#endif - #ifdef USE_LIBRETINY #include #endif @@ -59,6 +55,9 @@ namespace esphome::wifi { /// Sentinel value for RSSI when WiFi is not connected static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127; +/// Buffer size for SSID (IEEE 802.11 max 32 bytes + null terminator) +static constexpr size_t SSID_BUFFER_SIZE = 33; + struct SavedWifiSettings { char ssid[33]; char password[65]; @@ -113,6 +112,28 @@ enum class WiFiRetryPhase : uint8_t { RESTARTING_ADAPTER, }; +/// Tracks post-connect roaming state machine +enum class RoamingState : uint8_t { + /// Not roaming, waiting for next check interval + IDLE, + /// Scanning for better AP + SCANNING, + /// Attempting to connect to better AP found in scan + CONNECTING, + /// Roam connection failed, reconnecting to any available AP + RECONNECTING, +}; + +/// Controls how RETRY_HIDDEN phase selects networks to try +enum class RetryHiddenMode : uint8_t { + /// Normal mode: scan completed, only try networks NOT visible in scan results + /// (truly hidden networks that need probe requests) + SCAN_BASED, + /// Blind retry mode: scanning disabled (captive portal/improv active), + /// try ALL configured networks sequentially without consulting scan results + BLIND_RETRY, +}; + /// Struct for setting static IPs in WiFiComponent. struct ManualIP { network::IPAddress static_ip; @@ -151,25 +172,28 @@ template using wifi_scan_vector_t = FixedVector; class WiFiAP { public: void set_ssid(const std::string &ssid); - void set_bssid(bssid_t bssid); - void set_bssid(optional bssid); + void set_bssid(const bssid_t &bssid); + void clear_bssid(); void set_password(const std::string &password); #ifdef USE_WIFI_WPA2_EAP void set_eap(optional eap_auth); #endif // USE_WIFI_WPA2_EAP - void set_channel(optional channel); + void set_channel(uint8_t channel); + void clear_channel(); void set_priority(int8_t priority) { priority_ = priority; } #ifdef USE_WIFI_MANUAL_IP void set_manual_ip(optional manual_ip); #endif void set_hidden(bool hidden); const std::string &get_ssid() const; - const optional &get_bssid() const; + const bssid_t &get_bssid() const; + bool has_bssid() const; const std::string &get_password() const; #ifdef USE_WIFI_WPA2_EAP const optional &get_eap() const; #endif // USE_WIFI_WPA2_EAP - const optional &get_channel() const; + uint8_t get_channel() const; + bool has_channel() const; int8_t get_priority() const { return priority_; } #ifdef USE_WIFI_MANUAL_IP const optional &get_manual_ip() const; @@ -179,16 +203,17 @@ class WiFiAP { protected: std::string ssid_; std::string password_; - optional bssid_; #ifdef USE_WIFI_WPA2_EAP optional eap_; #endif // USE_WIFI_WPA2_EAP #ifdef USE_WIFI_MANUAL_IP optional manual_ip_; #endif - optional channel_; - int8_t priority_{0}; - bool hidden_{false}; + // Group small types together to minimize padding + bssid_t bssid_{}; // 6 bytes, all zeros = any/not set + uint8_t channel_{0}; // 1 byte, 0 = auto/not set + int8_t priority_{0}; // 1 byte + bool hidden_{false}; // 1 byte (+ 3 bytes end padding to 4-byte align) }; class WiFiScanResult { @@ -242,10 +267,17 @@ enum WifiMinAuthMode : uint8_t { struct IDFWiFiEvent; #endif +#ifdef USE_LIBRETINY +struct LTWiFiEvent; +#endif + /** Listener interface for WiFi IP state changes. * * Components can implement this interface to receive IP address updates * without the overhead of std::function callbacks. + * + * @note Components must call wifi.request_wifi_ip_state_listener() in their + * Python to_code() to register for this listener type. */ class WiFiIPStateListener { public: @@ -257,6 +289,9 @@ class WiFiIPStateListener { * * Components can implement this interface to receive scan results * without the overhead of std::function callbacks. + * + * @note Components must call wifi.request_wifi_scan_results_listener() in their + * Python to_code() to register for this listener type. */ class WiFiScanResultsListener { public: @@ -267,16 +302,22 @@ class WiFiScanResultsListener { * * Components can implement this interface to receive connection updates * without the overhead of std::function callbacks. + * + * @note Components must call wifi.request_wifi_connect_state_listener() in their + * Python to_code() to register for this listener type. */ class WiFiConnectStateListener { public: - virtual void on_wifi_connect_state(const std::string &ssid, const bssid_t &bssid) = 0; + virtual void on_wifi_connect_state(StringRef ssid, std::span bssid) = 0; }; /** Listener interface for WiFi power save mode changes. * * Components can implement this interface to receive power save mode updates * without the overhead of std::function callbacks. + * + * @note Components must call wifi.request_wifi_power_save_listener() in their + * Python to_code() to register for this listener type. */ class WiFiPowerSaveListener { public: @@ -294,10 +335,7 @@ class WiFiComponent : public Component { WiFiAP get_sta() const; void init_sta(size_t count); void add_sta(const WiFiAP &ap); - void clear_sta() { - this->sta_.clear(); - this->selected_sta_index_ = -1; - } + void clear_sta(); #ifdef USE_WIFI_AP /** Setup an Access Point that should be created if no connection to a station can be made. @@ -321,7 +359,7 @@ class WiFiComponent : public Component { // Backward compatibility overload - ignores 'two' parameter void start_connecting(const WiFiAP &ap, bool /* two */) { this->start_connecting(ap); } - void check_connecting_finished(); + void check_connecting_finished(uint32_t now); void retry_connect(); @@ -402,38 +440,48 @@ class WiFiComponent : public Component { network::IPAddresses wifi_sta_ip_addresses(); std::string wifi_ssid(); + /// Write SSID to buffer without heap allocation. + /// Returns pointer to buffer, or empty string if not connected. + const char *wifi_ssid_to(std::span buffer); bssid_t wifi_bssid(); int8_t wifi_rssi(); void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } void set_keep_scan_results(bool keep_scan_results) { this->keep_scan_results_ = keep_scan_results; } + void set_post_connect_roaming(bool enabled) { this->post_connect_roaming_ = enabled; } Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }; Trigger<> *get_disconnect_trigger() const { return this->disconnect_trigger_; }; int32_t get_wifi_channel(); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_IP_STATE_LISTENERS /** Add a listener for IP state changes. * Listener receives: IP addresses, DNS address 1, DNS address 2 */ void add_ip_state_listener(WiFiIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); } +#endif // USE_WIFI_IP_STATE_LISTENERS +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS /// Add a listener for WiFi scan results void add_scan_results_listener(WiFiScanResultsListener *listener) { this->scan_results_listeners_.push_back(listener); } +#endif // USE_WIFI_SCAN_RESULTS_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS /** Add a listener for WiFi connection state changes. * Listener receives: SSID, BSSID */ void add_connect_state_listener(WiFiConnectStateListener *listener) { this->connect_state_listeners_.push_back(listener); } +#endif // USE_WIFI_CONNECT_STATE_LISTENERS +#ifdef USE_WIFI_POWER_SAVE_LISTENERS /** Add a listener for WiFi power save mode changes. * Listener receives: WiFiPowerSaveMode */ void add_power_save_listener(WiFiPowerSaveListener *listener) { this->power_save_listeners_.push_back(listener); } -#endif // USE_WIFI_LISTENERS +#endif // USE_WIFI_POWER_SAVE_LISTENERS #ifdef USE_WIFI_RUNTIME_POWER_SAVE /** Request high-performance mode (no power saving) for improved WiFi latency. @@ -497,6 +545,8 @@ class WiFiComponent : public Component { int8_t find_next_hidden_sta_(int8_t start_index); /// Log failed connection and decrease BSSID priority to avoid repeated attempts void log_and_adjust_priority_for_failed_connect_(); + /// Clear all BSSID priority penalties after successful connection (stale after disconnect) + void clear_all_bssid_priorities_(); /// Clear BSSID priority tracking if all priorities are at minimum (saves memory) void clear_priorities_if_all_min_(); /// Advance to next target (AP/SSID) within current phase, or increment retry counter @@ -560,16 +610,20 @@ class WiFiComponent : public Component { void save_fast_connect_settings_(); #endif + // Post-connect roaming methods + void check_roaming_(uint32_t now); + void process_roaming_scan_(); + void clear_roaming_state_(); + + /// Free scan results memory unless a component needs them + void release_scan_results_(); + #ifdef USE_ESP8266 static void wifi_event_callback(System_Event_t *event); void wifi_scan_done_callback_(void *arg, STATUS status); static void s_wifi_scan_done_callback(void *arg, STATUS status); #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO - void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); - void wifi_scan_done_callback_(); -#endif #ifdef USE_ESP32 void wifi_process_event_(IDFWiFiEvent *data); #endif @@ -581,6 +635,7 @@ class WiFiComponent : public Component { #ifdef USE_LIBRETINY void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info); + void wifi_process_event_(LTWiFiEvent *event); void wifi_scan_done_callback_(); #endif @@ -590,22 +645,35 @@ class WiFiComponent : public Component { #ifdef USE_WIFI_AP WiFiAP ap_; #endif - optional output_power_; -#ifdef USE_WIFI_LISTENERS - std::vector ip_state_listeners_; - std::vector scan_results_listeners_; - std::vector connect_state_listeners_; - std::vector power_save_listeners_; -#endif // USE_WIFI_LISTENERS + float output_power_{NAN}; +#ifdef USE_WIFI_IP_STATE_LISTENERS + StaticVector ip_state_listeners_; +#endif +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS + StaticVector scan_results_listeners_; +#endif +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + StaticVector connect_state_listeners_; +#endif +#ifdef USE_WIFI_POWER_SAVE_LISTENERS + StaticVector power_save_listeners_; +#endif ESPPreferenceObject pref_; #ifdef USE_WIFI_FAST_CONNECT ESPPreferenceObject fast_connect_pref_; #endif + // Post-connect roaming constants + static constexpr uint32_t ROAMING_CHECK_INTERVAL = 5 * 60 * 1000; // 5 minutes + static constexpr int8_t ROAMING_MIN_IMPROVEMENT = 10; // dB + static constexpr int8_t ROAMING_GOOD_RSSI = -49; // Skip scan if signal is excellent + static constexpr uint8_t ROAMING_MAX_ATTEMPTS = 3; + // Group all 32-bit integers together uint32_t action_started_; uint32_t last_connected_{0}; uint32_t reboot_timeout_{}; + uint32_t roaming_last_check_{0}; #ifdef USE_WIFI_AP uint32_t ap_timeout_{}; #endif @@ -620,6 +688,7 @@ class WiFiComponent : public Component { // Used to access password, manual_ip, priority, EAP settings, and hidden flag // int8_t limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS) int8_t selected_sta_index_{-1}; + uint8_t roaming_attempts_{0}; #if USE_NETWORK_IPV6 uint8_t num_ipv6_addresses_{0}; @@ -641,8 +710,10 @@ class WiFiComponent : public Component { bool enable_on_boot_{true}; bool got_ipv4_address_{false}; bool keep_scan_results_{false}; - bool did_scan_this_cycle_{false}; + RetryHiddenMode retry_hidden_mode_{RetryHiddenMode::BLIND_RETRY}; bool skip_cooldown_next_cycle_{false}; + bool post_connect_roaming_{true}; // Enabled by default + RoamingState roaming_state_{RoamingState::IDLE}; #if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE) WiFiPowerSaveMode configured_power_save_{WIFI_POWER_SAVE_NONE}; bool is_high_performance_mode_{false}; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 41594b947c..6fb5dd5769 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -105,7 +105,7 @@ bool WiFiComponent::wifi_apply_power_save_() { } wifi_fpm_auto_sleep_set_in_null_mode(1); bool success = wifi_set_sleep_type(power_save); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_POWER_SAVE_LISTENERS if (success) { for (auto *listener : this->power_save_listeners_) { listener->on_wifi_power_save(this->power_save_); @@ -257,9 +257,9 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { memcpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); memcpy(reinterpret_cast(conf.password), ap.get_password().c_str(), ap.get_password().size()); - if (ap.get_bssid().has_value()) { + if (ap.has_bssid()) { conf.bssid_set = 1; - memcpy(conf.bssid, ap.get_bssid()->data(), 6); + memcpy(conf.bssid, ap.get_bssid().data(), 6); } else { conf.bssid_set = 0; } @@ -371,7 +371,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { while (!connected) { uint8_t ipv6_addr_count = 0; for (auto addr : addrList) { - ESP_LOGV(TAG, "Address %s", addr.toString().c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "Address %s", network::IPAddress(addr.ipFromNetifNum()).str_to(ip_buf)); if (addr.isV6()) { ipv6_addr_count++; } @@ -381,8 +382,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { } #endif /* USE_NETWORK_IPV6 */ - if (ap.get_channel().has_value()) { - ret = wifi_set_channel(*ap.get_channel()); + if (ap.has_channel()) { + ret = wifi_set_channel(ap.get_channel()); if (!ret) { ESP_LOGV(TAG, "wifi_set_channel failed"); return false; @@ -413,21 +414,6 @@ const LogString *get_auth_mode_str(uint8_t mode) { return LOG_STR("UNKNOWN"); } } -#ifdef ipv4_addr -std::string format_ip_addr(struct ipv4_addr ip) { - char buf[20]; - sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} -#else -std::string format_ip_addr(struct ip_addr ip) { - char buf[20]; - sprintf(buf, "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} -#endif const LogString *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: @@ -518,18 +504,20 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { switch (event->event) { case EVENT_STAMODE_CONNECTED: { auto it = event->event_info.connected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; - ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(), +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, bssid_buf); + ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid, bssid_buf, it.channel); +#endif s_sta_connected = true; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); } +#endif // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here -#ifdef USE_WIFI_MANUAL_IP +#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP) if (const WiFiAP *config = global_wifi_component->get_selected_sta_(); config && config->get_manual_ip().has_value()) { for (auto *listener : global_wifi_component->ip_state_listeners_) { @@ -537,30 +525,33 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1)); } } -#endif #endif break; } case EVENT_STAMODE_DISCONNECTED: { auto it = event->event_info.disconnected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; if (it.reason == REASON_NO_AP_FOUND) { - ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, + (const char *) it.ssid); s_sta_connect_not_found = true; } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, - LOG_STR_ARG(get_disconnect_reason_str(it.reason))); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, + (const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason))); s_sta_connect_error = true; } s_sta_connected = false; s_sta_connecting = false; -#ifdef USE_WIFI_LISTENERS + // IMPORTANT: Set error flag BEFORE notifying listeners. + // This ensures is_connected() returns false during listener callbacks, + // which is critical for proper reconnection logic (e.g., roaming). + global_wifi_component->error_from_callback_ = true; +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + // Notify listeners AFTER setting error flag so they see correct state + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif break; @@ -582,10 +573,12 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_STAMODE_GOT_IP: { auto it = event->event_info.got_ip; - ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(), - format_ip_addr(it.mask).c_str()); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE], + mask_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf), + network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf)); s_sta_got_ip = true; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_IP_STATE_LISTENERS for (auto *listener : global_wifi_component->ip_state_listeners_) { listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(), global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1)); @@ -598,18 +591,30 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { break; } case EVENT_SOFTAPMODE_STACONNECTED: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.sta_connected; - ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", mac_buf, it.aid); +#endif break; } case EVENT_SOFTAPMODE_STADISCONNECTED: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.sta_disconnected; - ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", mac_buf, it.aid); +#endif break; } case EVENT_SOFTAPMODE_PROBEREQRECVED: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE auto it = event->event_info.ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi); +#endif break; } #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) @@ -620,9 +625,14 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { break; } case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto it = event->event_info.distribute_sta_ip; - ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), - format_ip_addr(it.ip).c_str(), it.aid); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", mac_buf, network::IPAddress(&it.ip).str_to(ip_buf), + it.aid); +#endif break; } #endif @@ -630,10 +640,6 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { break; } - if (event->event == EVENT_STAMODE_DISCONNECTED) { - global_wifi_component->error_from_callback_ = true; - } - WiFiMockClass::_event_callback(event); } @@ -765,7 +771,7 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) { it->is_hidden != 0); } this->scan_done_ = true; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS for (auto *listener : global_wifi_component->scan_results_listeners_) { listener->on_wifi_scan_results(global_wifi_component->scan_result_); } @@ -809,10 +815,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { network::IPAddress start_address = network::IPAddress(&info.ip); start_address += 99; lease.start_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; +#endif + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf)); start_address += 10; lease.end_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf)); if (!wifi_softap_set_dhcps_lease(&lease)) { ESP_LOGE(TAG, "Set SoftAP DHCP lease failed"); return false; @@ -855,7 +864,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { } memcpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); conf.ssid_len = static_cast(ap.get_ssid().size()); - conf.channel = ap.get_channel().value_or(1); + conf.channel = ap.has_channel() ? ap.get_channel() : 1; conf.ssid_hidden = ap.get_hidden(); conf.max_connection = 5; conf.beacon_interval = 100; @@ -912,6 +921,18 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + struct station_config conf {}; + if (!wifi_station_get_config(&conf)) { + buffer[0] = '\0'; + return buffer.data(); + } + // conf.ssid is uint8[32], not null-terminated if full + size_t len = strnlen(reinterpret_cast(conf.ssid), sizeof(conf.ssid)); + memcpy(buffer.data(), conf.ssid, len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { if (WiFi.status() != WL_CONNECTED) return WIFI_RSSI_DISCONNECTED; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index fb28018b07..848ec3e11c 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -281,7 +281,7 @@ bool WiFiComponent::wifi_apply_power_save_() { break; } bool success = esp_wifi_set_ps(power_save) == ESP_OK; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_POWER_SAVE_LISTENERS if (success) { for (auto *listener : this->power_save_listeners_) { listener->on_wifi_power_save(this->power_save_); @@ -339,14 +339,14 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.sta.rm_enabled = this->rrm_; #endif - if (ap.get_bssid().has_value()) { + if (ap.has_bssid()) { conf.sta.bssid_set = true; - memcpy(conf.sta.bssid, ap.get_bssid()->data(), 6); + memcpy(conf.sta.bssid, ap.get_bssid().data(), 6); } else { conf.sta.bssid_set = false; } - if (ap.get_channel().has_value()) { - conf.sta.channel = *ap.get_channel(); + if (ap.has_channel()) { + conf.sta.channel = ap.get_channel(); conf.sta.scan_method = WIFI_FAST_SCAN; } else { conf.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; @@ -734,52 +734,50 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_CONNECTED) { const auto &it = data->data.sta_connected; - char buf[33]; - assert(it.ssid_len <= 32); - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; - ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, - format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, bssid_buf); + ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len, + (const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode)); +#endif s_sta_connected = true; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); } +#endif // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here -#ifdef USE_WIFI_MANUAL_IP +#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP) if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } } -#endif #endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { const auto &it = data->data.sta_disconnected; - char buf[33]; - assert(it.ssid_len <= 32); - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; if (it.reason == WIFI_REASON_NO_AP_FOUND) { - ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, + (const char *) it.ssid); s_sta_connect_not_found = true; } else if (it.reason == WIFI_REASON_ROAMING) { - ESP_LOGI(TAG, "Disconnected ssid='%s' reason='Station Roaming'", buf); + ESP_LOGI(TAG, "Disconnected ssid='%.*s' reason='Station Roaming'", it.ssid_len, (const char *) it.ssid); return; } else { char bssid_s[18]; format_mac_addr_upper(it.bssid, bssid_s); - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, - get_disconnect_reason_str(it.reason)); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, + (const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason)); s_sta_connect_error = true; } s_sta_connected = false; s_sta_connecting = false; error_from_callback_ = true; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif @@ -790,7 +788,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { #endif /* USE_NETWORK_IPV6 */ ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw)); this->got_ipv4_address_ = true; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_IP_STATE_LISTENERS for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } @@ -801,7 +799,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { const auto &it = data->data.ip_got_ip6; ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip)); this->num_ipv6_addresses_++; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_IP_STATE_LISTENERS for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } @@ -845,7 +843,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { scan_result_.emplace_back(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN, ssid.empty()); } -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS for (auto *listener : this->scan_results_listeners_) { listener->on_wifi_scan_results(this->scan_result_); } @@ -860,16 +858,28 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { this->ap_started_ = false; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE const auto &it = data->data.ap_probe_req_rx; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi); +#endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE const auto &it = data->data.ap_staconnected; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str()); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client connected MAC=%s", mac_buf); +#endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE const auto &it = data->data.ap_stadisconnected; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str()); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", mac_buf); +#endif } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { const auto &it = data->data.ip_ap_staipassigned; @@ -968,10 +978,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { network::IPAddress start_address = network::IPAddress(&info.ip); start_address += 99; lease.start_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; +#endif + ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf)); start_address += 10; lease.end_ip = start_address; - ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); + ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf)); err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); if (err != ESP_OK) { @@ -983,8 +996,10 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional &manual_ip) { // Configure DHCP Option 114 (Captive Portal URI) if captive portal is enabled // This provides a standards-compliant way for clients to discover the captive portal if (captive_portal::global_captive_portal != nullptr) { - static char captive_portal_uri[32]; - snprintf(captive_portal_uri, sizeof(captive_portal_uri), "http://%s", network::IPAddress(&info.ip).str().c_str()); + // Buffer must be static - dhcps_set_option_info stores pointer, doesn't copy + static char captive_portal_uri[24]; // "http://" (7) + IPv4 max (15) + null + memcpy(captive_portal_uri, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates + network::IPAddress(&info.ip).str_to(captive_portal_uri + 7); err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_CAPTIVEPORTAL_URI, captive_portal_uri, strlen(captive_portal_uri)); if (err != ESP_OK) { @@ -1017,7 +1032,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; } memcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), ap.get_ssid().size()); - conf.ap.channel = ap.get_channel().value_or(1); + conf.ap.channel = ap.has_channel() ? ap.get_channel() : 1; conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; conf.ap.beacon_interval = 100; @@ -1091,6 +1106,19 @@ std::string WiFiComponent::wifi_ssid() { size_t len = strnlen(ssid_s, sizeof(info.ssid)); return {ssid_s, len}; } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + wifi_ap_record_t info{}; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + if (err != ESP_OK) { + buffer[0] = '\0'; + return buffer.data(); + } + // info.ssid is uint8[33], but only 32 bytes are SSID data + size_t len = strnlen(reinterpret_cast(info.ssid), 32); + memcpy(buffer.data(), info.ssid, len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { wifi_ap_record_t info; esp_err_t err = esp_wifi_sta_get_ap_info(&info); diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 1012b0be6c..162ed4e835 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -3,12 +3,16 @@ #ifdef USE_WIFI #ifdef USE_LIBRETINY +#include #include #include #include "lwip/ip_addr.h" #include "lwip/err.h" #include "lwip/dns.h" +#include +#include + #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -19,7 +23,76 @@ namespace esphome::wifi { static const char *const TAG = "wifi_lt"; -static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// Thread-safe event handling for LibreTiny WiFi +// +// LibreTiny's WiFi.onEvent() callback runs in the WiFi driver's thread context, +// not the main ESPHome loop. Without synchronization, modifying shared state +// (like connection status flags) from the callback causes race conditions: +// - The main loop may never see state changes (values cached in registers) +// - State changes may be visible in inconsistent order +// - LibreTiny targets (BK7231, RTL8720) lack atomic instructions (no LDREX/STREX) +// +// Solution: Queue events in the callback and process them in the main loop. +// This is the same approach used by ESP32 IDF's wifi_process_event_(). +// All state modifications happen in the main loop context, eliminating races. + +static constexpr size_t EVENT_QUEUE_SIZE = 16; // Max pending WiFi events before overflow +static QueueHandle_t s_event_queue = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static volatile uint32_t s_event_queue_overflow_count = + 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +// Event structure for queued WiFi events - contains a copy of event data +// to avoid lifetime issues with the original event data from the callback +struct LTWiFiEvent { + arduino_event_id_t event_id; + union { + struct { + uint8_t ssid[33]; + uint8_t ssid_len; + uint8_t bssid[6]; + uint8_t channel; + uint8_t authmode; + } sta_connected; + struct { + uint8_t ssid[33]; + uint8_t ssid_len; + uint8_t bssid[6]; + uint8_t reason; + } sta_disconnected; + struct { + uint8_t old_mode; + uint8_t new_mode; + } sta_authmode_change; + struct { + uint32_t status; + uint8_t number; + uint8_t scan_id; + } scan_done; + struct { + uint8_t mac[6]; + int rssi; + } ap_probe_req; + } data; +}; + +// Connection state machine - only modified from main loop after queue processing +enum class LTWiFiSTAState : uint8_t { + IDLE, // Not connecting + CONNECTING, // Connection in progress + CONNECTED, // Successfully connected with IP + ERROR_NOT_FOUND, // AP not found (probe failed) + ERROR_FAILED, // Connection failed (auth, timeout, etc.) +}; + +static LTWiFiSTAState s_sta_state = LTWiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +// Count of ignored disconnect events during connection - too many indicates real failure +static uint8_t s_ignored_disconnect_count = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// Threshold for ignored disconnect events before treating as connection failure +// LibreTiny sends spurious "Association Leave" events, but more than this many +// indicates the connection is failing repeatedly. Value of 3 balances fast failure +// detection with tolerance for occasional spurious events on successful connections. +static constexpr uint8_t IGNORED_DISCONNECT_THRESHOLD = 3; bool WiFiComponent::wifi_mode_(optional sta, optional ap) { uint8_t current_mode = WiFi.getMode(); @@ -71,7 +144,7 @@ bool WiFiComponent::wifi_sta_pre_setup_() { } bool WiFiComponent::wifi_apply_power_save_() { bool success = WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_POWER_SAVE_LISTENERS if (success) { for (auto *listener : this->power_save_listeners_) { listener->on_wifi_power_save(this->power_save_); @@ -136,11 +209,13 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); - s_sta_connecting = true; + // Reset state machine and disconnect counter before connecting + s_sta_state = LTWiFiSTAState::CONNECTING; + s_ignored_disconnect_count = 0; WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), - ap.get_channel().has_value() ? *ap.get_channel() : 0, - ap.get_bssid().has_value() ? ap.get_bssid()->data() : NULL); + ap.get_channel(), // 0 = auto + ap.has_bssid() ? ap.get_bssid().data() : NULL); if (status != WL_CONNECTED) { ESP_LOGW(TAG, "esp_wifi_connect failed: %d", status); return false; @@ -165,14 +240,6 @@ const char *get_auth_mode_str(uint8_t mode) { } } -using esphome_ip4_addr_t = IPAddress; - -std::string format_ip4_addr(const esphome_ip4_addr_t &ip) { - char buf[20]; - uint32_t addr = ip; - sprintf(buf, "%u.%u.%u.%u", uint8_t(addr >> 0), uint8_t(addr >> 8), uint8_t(addr >> 16), uint8_t(addr >> 24)); - return buf; -} const char *get_op_mode_str(uint8_t mode) { switch (mode) { case WIFI_OFF: @@ -271,16 +338,101 @@ const char *get_disconnect_reason_str(uint8_t reason) { using esphome_wifi_event_id_t = arduino_event_id_t; using esphome_wifi_event_info_t = arduino_event_info_t; +// Event callback - runs in WiFi driver thread context +// Only queues events for processing in main loop, no logging or state changes here void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) { + if (s_event_queue == nullptr) { + return; + } + + // Allocate on heap and fill directly to avoid extra memcpy + auto *to_send = new LTWiFiEvent{}; // NOLINT(cppcoreguidelines-owning-memory) + to_send->event_id = event; + + // Copy event-specific data switch (event) { + case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { + auto &it = info.wifi_sta_connected; + to_send->data.sta_connected.ssid_len = it.ssid_len; + memcpy(to_send->data.sta_connected.ssid, it.ssid, + std::min(static_cast(it.ssid_len), sizeof(to_send->data.sta_connected.ssid) - 1)); + memcpy(to_send->data.sta_connected.bssid, it.bssid, 6); + to_send->data.sta_connected.channel = it.channel; + to_send->data.sta_connected.authmode = it.authmode; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { + auto &it = info.wifi_sta_disconnected; + to_send->data.sta_disconnected.ssid_len = it.ssid_len; + memcpy(to_send->data.sta_disconnected.ssid, it.ssid, + std::min(static_cast(it.ssid_len), sizeof(to_send->data.sta_disconnected.ssid) - 1)); + memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6); + to_send->data.sta_disconnected.reason = it.reason; + break; + } + case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { + auto &it = info.wifi_sta_authmode_change; + to_send->data.sta_authmode_change.old_mode = it.old_mode; + to_send->data.sta_authmode_change.new_mode = it.new_mode; + break; + } + case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { + auto &it = info.wifi_scan_done; + to_send->data.scan_done.status = it.status; + to_send->data.scan_done.number = it.number; + to_send->data.scan_done.scan_id = it.scan_id; + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { + auto &it = info.wifi_ap_probereqrecved; + memcpy(to_send->data.ap_probe_req.mac, it.mac, 6); + to_send->data.ap_probe_req.rssi = it.rssi; + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { + auto &it = info.wifi_sta_connected; + memcpy(to_send->data.sta_connected.bssid, it.bssid, 6); + break; + } + case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { + auto &it = info.wifi_sta_disconnected; + memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6); + break; + } + case ESPHOME_EVENT_ID_WIFI_READY: + case ESPHOME_EVENT_ID_WIFI_STA_START: + case ESPHOME_EVENT_ID_WIFI_STA_STOP: + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: + case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: + case ESPHOME_EVENT_ID_WIFI_AP_START: + case ESPHOME_EVENT_ID_WIFI_AP_STOP: + case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: + // No additional data needed + break; + default: + // Unknown event, don't queue + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + return; + } + + // Queue event (don't block if queue is full) + if (xQueueSend(s_event_queue, &to_send, 0) != pdPASS) { + delete to_send; // NOLINT(cppcoreguidelines-owning-memory) + s_event_queue_overflow_count++; + } +} + +// Process a single event from the queue - runs in main loop context +void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { + switch (event->event_id) { case ESPHOME_EVENT_ID_WIFI_READY: { ESP_LOGV(TAG, "Ready"); break; } case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: { - auto it = info.wifi_scan_done; - ESP_LOGV(TAG, "Scan done: status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id); - + auto &it = event->data.scan_done; + ESP_LOGV(TAG, "Scan done: status=%" PRIu32 " number=%u scan_id=%u", it.status, it.number, it.scan_id); this->wifi_scan_done_callback_(); break; } @@ -291,60 +443,74 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_STOP: { ESP_LOGV(TAG, "STA stop"); - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::IDLE; break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { - auto it = info.wifi_sta_connected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; - ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, - format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); -#ifdef USE_WIFI_LISTENERS + auto &it = event->data.sta_connected; + char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, bssid_buf); + ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len, + (const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode)); + // Note: We don't set CONNECTED state here yet - wait for GOT_IP + // This matches ESP32 IDF behavior where s_sta_connected is set but + // wifi_sta_connect_status_() also checks got_ipv4_address_ +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); } +#endif // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here -#ifdef USE_WIFI_MANUAL_IP +#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP) if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { + s_sta_state = LTWiFiSTAState::CONNECTED; for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } } -#endif #endif break; } case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { - auto it = info.wifi_sta_disconnected; - char buf[33]; - memcpy(buf, it.ssid, it.ssid_len); - buf[it.ssid_len] = '\0'; + auto &it = event->data.sta_disconnected; // LibreTiny can send spurious disconnect events with empty ssid/bssid during connection. // These are typically "Association Leave" events that don't indicate actual failures: // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave' // [V][wifi_lt]: Connected ssid='WIFI' bssid=... channel=3, authmode=WPA2 PSK - // Without this check, the spurious events set s_sta_connecting=false, causing - // wifi_sta_connect_status_() to return IDLE. The main loop then sees - // "Unknown connection status 0" (wifi_component.cpp check_connecting_finished) - // and calls retry_connect(), aborting a connection that may succeed moments later. - // Real connection failures will have ssid/bssid populated, or we'll hit the connection timeout. - if (it.ssid_len == 0 && s_sta_connecting) { - ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)", - get_disconnect_reason_str(it.reason)); - break; + // Without this check, the spurious events would transition state to ERROR_FAILED, + // causing wifi_sta_connect_status_() to return an error. The main loop would then + // call retry_connect(), aborting a connection that may succeed moments later. + // Only ignore benign reasons - real failures like NO_AP_FOUND should still be processed. + // However, if we get too many of these events (IGNORED_DISCONNECT_THRESHOLD), treat it + // as a real connection failure to avoid waiting the full timeout for a failing connection. + if (it.ssid_len == 0 && s_sta_state == LTWiFiSTAState::CONNECTING && it.reason != WIFI_REASON_NO_AP_FOUND) { + s_ignored_disconnect_count++; + if (s_ignored_disconnect_count >= IGNORED_DISCONNECT_THRESHOLD) { + ESP_LOGW(TAG, "Too many disconnect events (%u) while connecting, treating as failure (reason=%s)", + s_ignored_disconnect_count, get_disconnect_reason_str(it.reason)); + s_sta_state = LTWiFiSTAState::ERROR_FAILED; + WiFi.disconnect(); + this->error_from_callback_ = true; + // Don't break - fall through to notify listeners + } else { + ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s, count=%u)", + get_disconnect_reason_str(it.reason), s_ignored_disconnect_count); + break; + } } if (it.reason == WIFI_REASON_NO_AP_FOUND) { - ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len, + (const char *) it.ssid); + s_sta_state = LTWiFiSTAState::ERROR_NOT_FOUND; } else { - char bssid_s[18]; + char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(it.bssid, bssid_s); - ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s, - get_disconnect_reason_str(it.reason)); + ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len, + (const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason)); + s_sta_state = LTWiFiSTAState::ERROR_FAILED; } uint8_t reason = it.reason; @@ -355,34 +521,33 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ this->error_from_callback_ = true; } - s_sta_connecting = false; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif break; } case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: { - auto it = info.wifi_sta_authmode_change; + auto &it = event->data.sta_authmode_change; ESP_LOGV(TAG, "Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), get_auth_mode_str(it.new_mode)); // Mitigate CVE-2020-12638 // https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) { ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting"); - // we can't call retry_connect() from this context, so disconnect immediately - // and notify main thread with error_from_callback_ WiFi.disconnect(); this->error_from_callback_ = true; + s_sta_state = LTWiFiSTAState::ERROR_FAILED; } break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: { - // auto it = info.got_ip.ip_info; - ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(), - format_ip4_addr(WiFi.gatewayIP()).c_str()); - s_sta_connecting = false; -#ifdef USE_WIFI_LISTENERS + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGV(TAG, "static_ip=%s gateway=%s", network::IPAddress(WiFi.localIP()).str_to(ip_buf), + network::IPAddress(WiFi.gatewayIP()).str_to(gw_buf)); + s_sta_state = LTWiFiSTAState::CONNECTED; +#ifdef USE_WIFI_IP_STATE_LISTENERS for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } @@ -390,9 +555,8 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { - // auto it = info.got_ip.ip_info; ESP_LOGV(TAG, "Got IPv6"); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_IP_STATE_LISTENERS for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } @@ -401,6 +565,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Lost IP"); + // Don't change state to IDLE - let the disconnect event handle that break; } case ESPHOME_EVENT_ID_WIFI_AP_START: { @@ -412,15 +577,21 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { - auto it = info.wifi_sta_connected; - auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + auto &it = event->data.sta_connected; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, mac_buf); + ESP_LOGV(TAG, "AP client connected MAC=%s", mac_buf); +#endif break; } case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { - auto it = info.wifi_sta_disconnected; - auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + auto &it = event->data.sta_disconnected; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, mac_buf); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", mac_buf); +#endif break; } case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { @@ -428,8 +599,12 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ break; } case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { - auto it = info.wifi_ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + auto &it = event->data.ap_probe_req; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.mac, mac_buf); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi); +#endif break; } default: @@ -437,23 +612,35 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } } void WiFiComponent::wifi_pre_setup_() { + // Create event queue for thread-safe event handling + // Events are pushed from WiFi callback thread and processed in main loop + s_event_queue = xQueueCreate(EVENT_QUEUE_SIZE, sizeof(LTWiFiEvent *)); + if (s_event_queue == nullptr) { + ESP_LOGE(TAG, "Failed to create event queue"); + return; + } + auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2); WiFi.onEvent(f); // Make sure WiFi is in clean state before anything starts this->wifi_mode_(false, false); } WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { - auto status = WiFi.status(); - if (status == WL_CONNECTED) { - return WiFiSTAConnectStatus::CONNECTED; - } else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) { - return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; - } else if (status == WL_NO_SSID_AVAIL) { - return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; - } else if (s_sta_connecting) { - return WiFiSTAConnectStatus::CONNECTING; + // Use state machine instead of querying WiFi.status() directly + // State is updated in main loop from queued events, ensuring thread safety + switch (s_sta_state) { + case LTWiFiSTAState::CONNECTED: + return WiFiSTAConnectStatus::CONNECTED; + case LTWiFiSTAState::ERROR_NOT_FOUND: + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + case LTWiFiSTAState::ERROR_FAILED: + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + case LTWiFiSTAState::CONNECTING: + return WiFiSTAConnectStatus::CONNECTING; + case LTWiFiSTAState::IDLE: + default: + return WiFiSTAConnectStatus::IDLE; } - return WiFiSTAConnectStatus::IDLE; } bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA @@ -490,7 +677,7 @@ void WiFiComponent::wifi_scan_done_callback_() { ssid.length() == 0); } WiFi.scanDelete(); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS for (auto *listener : this->scan_results_listeners_) { listener->on_wifi_scan_results(this->scan_result_); } @@ -530,16 +717,16 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { yield(); return WiFi.softAP(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(), - ap.get_channel().value_or(1), ap.get_hidden()); + ap.has_channel() ? ap.get_channel() : 1, ap.get_hidden()); } network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; } #endif // USE_WIFI_AP bool WiFiComponent::wifi_disconnect_() { - // Clear connecting flag first so disconnect events aren't ignored + // Reset state first so disconnect events aren't ignored // and wifi_sta_connect_status_() returns IDLE instead of CONNECTING - s_sta_connecting = false; + s_sta_state = LTWiFiSTAState::IDLE; return WiFi.disconnect(); } @@ -553,12 +740,42 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + // TODO: Find direct LibreTiny API to avoid Arduino String allocation + String ssid = WiFi.SSID(); + size_t len = std::min(static_cast(ssid.length()), SSID_BUFFER_SIZE - 1); + memcpy(buffer.data(), ssid.c_str(), len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } -void WiFiComponent::wifi_loop_() {} +void WiFiComponent::wifi_loop_() { + // Process all pending events from the queue + if (s_event_queue == nullptr) { + return; + } + + // Check for dropped events due to queue overflow + if (s_event_queue_overflow_count > 0) { + ESP_LOGW(TAG, "Event queue overflow, %" PRIu32 " events dropped", s_event_queue_overflow_count); + s_event_queue_overflow_count = 0; + } + + while (true) { + LTWiFiEvent *event; + if (xQueueReceive(s_event_queue, &event, 0) != pdTRUE) { + // No more events + break; + } + + wifi_process_event_(event); + delete event; // NOLINT(cppcoreguidelines-owning-memory) + } +} } // namespace esphome::wifi #endif // USE_LIBRETINY diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index c88aeb2d4f..29ac096d94 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -55,7 +55,7 @@ bool WiFiComponent::wifi_apply_power_save_() { } int ret = cyw43_wifi_pm(&cyw43_state, pm); bool success = ret == 0; -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_POWER_SAVE_LISTENERS if (success) { for (auto *listener : this->power_save_listeners_) { listener->on_wifi_power_save(this->power_save_); @@ -192,7 +192,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { } #endif - WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.get_channel().value_or(1)); + WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.has_channel() ? ap.get_channel() : 1); return true; } @@ -214,6 +214,14 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + // TODO: Find direct CYW43 API to avoid Arduino String allocation + String ssid = WiFi.SSID(); + size_t len = std::min(static_cast(ssid.length()), SSID_BUFFER_SIZE - 1); + memcpy(buffer.data(), ssid.c_str(), len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } @@ -237,7 +245,7 @@ void WiFiComponent::wifi_loop_() { if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) { this->scan_done_ = true; ESP_LOGV(TAG, "Scan done"); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS for (auto *listener : this->scan_results_listeners_) { listener->on_wifi_scan_results(this->scan_result_); } @@ -255,28 +263,31 @@ void WiFiComponent::wifi_loop_() { // Just connected s_sta_was_connected = true; ESP_LOGV(TAG, "Connected"); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + String ssid = WiFi.SSID(); + bssid_t bssid = this->wifi_bssid(); for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(ssid.c_str(), ssid.length()), bssid); } +#endif // For static IP configurations, notify IP listeners immediately as the IP is already configured -#ifdef USE_WIFI_MANUAL_IP +#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP) if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) { s_sta_had_ip = true; for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } } -#endif #endif } else if (!is_connected && s_sta_was_connected) { // Just disconnected s_sta_was_connected = false; s_sta_had_ip = false; ESP_LOGV(TAG, "Disconnected"); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif } @@ -294,7 +305,7 @@ void WiFiComponent::wifi_loop_() { // Just got IP address s_sta_had_ip = true; ESP_LOGV(TAG, "Got IP address"); -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_IP_STATE_LISTENERS for (auto *listener : this->ip_state_listeners_) { listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1)); } diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 8a7f192367..9ecb5b7490 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -69,16 +69,6 @@ CONFIG_SCHEMA = cv.Schema( } ) -# Keys that require WiFi listeners -_NETWORK_INFO_KEYS = { - CONF_SSID, - CONF_BSSID, - CONF_IP_ADDRESS, - CONF_DNS_ADDRESS, - CONF_SCAN_RESULTS, - CONF_POWER_SAVE_MODE, -} - async def setup_conf(config, key): if key in config: @@ -88,16 +78,28 @@ async def setup_conf(config, key): async def to_code(config): - # Request WiFi listeners for any sensor that needs them - if _NETWORK_INFO_KEYS.intersection(config): - wifi.request_wifi_listeners() + # Request specific WiFi listeners based on which sensors are configured + # SSID and BSSID use WiFiConnectStateListener + if CONF_SSID in config or CONF_BSSID in config: + wifi.request_wifi_connect_state_listener() + + # IP address and DNS use WiFiIPStateListener + if CONF_IP_ADDRESS in config or CONF_DNS_ADDRESS in config: + wifi.request_wifi_ip_state_listener() + + # Scan results use WiFiScanResultsListener + if CONF_SCAN_RESULTS in config: + wifi.request_wifi_scan_results_listener() + wifi.request_wifi_scan_results() + + # Power save mode uses WiFiPowerSaveListener + if CONF_POWER_SAVE_MODE in config: + wifi.request_wifi_power_save_listener() await setup_conf(config, CONF_SSID) await setup_conf(config, CONF_BSSID) await setup_conf(config, CONF_MAC_ADDRESS) - if CONF_SCAN_RESULTS in config: - await setup_conf(config, CONF_SCAN_RESULTS) - wifi.request_wifi_scan_results() + await setup_conf(config, CONF_SCAN_RESULTS) await setup_conf(config, CONF_DNS_ADDRESS) await setup_conf(config, CONF_POWER_SAVE_MODE) if conf := config.get(CONF_IP_ADDRESS): diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 56cf49028c..a63b30b892 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -10,9 +10,7 @@ namespace esphome::wifi_info { static const char *const TAG = "wifi_info"; -#ifdef USE_WIFI_LISTENERS - -static constexpr size_t MAX_STATE_LENGTH = 255; +#ifdef USE_WIFI_IP_STATE_LISTENERS /******************** * IPAddressWiFiInfo @@ -24,12 +22,15 @@ void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); void IPAddressWiFiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2) { - this->publish_state(ips[0].str()); + char buf[network::IP_ADDRESS_BUFFER_SIZE]; + ips[0].str_to(buf); + this->publish_state(buf); uint8_t sensor = 0; for (const auto &ip : ips) { if (ip.is_set()) { if (this->ip_sensors_[sensor] != nullptr) { - this->ip_sensors_[sensor]->publish_state(ip.str()); + ip.str_to(buf); + this->ip_sensors_[sensor]->publish_state(buf); } sensor++; } @@ -46,10 +47,19 @@ void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this void DNSAddressWifiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2) { - std::string dns_results = dns1.str() + " " + dns2.str(); - this->publish_state(dns_results); + // IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot + char buf[network::IP_ADDRESS_BUFFER_SIZE * 2]; + dns1.str_to(buf); + size_t len1 = strlen(buf); + buf[len1] = ' '; + dns2.str_to(buf + len1 + 1); + this->publish_state(buf); } +#endif // USE_WIFI_IP_STATE_LISTENERS + +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS + /********************** * ScanResultsWiFiInfo *********************/ @@ -58,24 +68,42 @@ void ScanResultsWiFiInfo::setup() { wifi::global_wifi_component->add_scan_result void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); } +// Format: "SSID: -XXdB\n" - caller must ensure ssid_len + 9 bytes available in buffer +static char *format_scan_entry(char *buf, const char *ssid, size_t ssid_len, int8_t rssi) { + memcpy(buf, ssid, ssid_len); + buf += ssid_len; + *buf++ = ':'; + *buf++ = ' '; + buf = int8_to_str(buf, rssi); + *buf++ = 'd'; + *buf++ = 'B'; + *buf++ = '\n'; + return buf; +} + void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t &results) { - std::string scan_results; + char buf[MAX_STATE_LEN + 1]; + char *ptr = buf; + const char *end = buf + MAX_STATE_LEN; + for (const auto &scan : results) { if (scan.get_is_hidden()) continue; + const std::string &ssid = scan.get_ssid(); + // Max space: ssid + ": " (2) + "-128" (4) + "dB\n" (3) = ssid + 9 + if (ptr + ssid.size() + 9 > end) + break; + ptr = format_scan_entry(ptr, ssid.c_str(), ssid.size(), scan.get_rssi()); + } - scan_results += scan.get_ssid(); - scan_results += ": "; - scan_results += esphome::to_string(scan.get_rssi()); - scan_results += "dB\n"; - } - // There's a limit of 255 characters per state; longer states just don't get sent so we truncate it - if (scan_results.length() > MAX_STATE_LENGTH) { - scan_results.resize(MAX_STATE_LENGTH); - } - this->publish_state(scan_results); + *ptr = '\0'; + this->publish_state(buf); } +#endif // USE_WIFI_SCAN_RESULTS_LISTENERS + +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + /*************** * SSIDWiFiInfo **************/ @@ -84,8 +112,8 @@ void SSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_list void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } -void SSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { - this->publish_state(ssid); +void SSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { + this->publish_state(ssid.c_str(), ssid.size()); } /**************** @@ -96,7 +124,7 @@ void BSSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_lis void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); } -void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { +void BSSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { char buf[18] = "unknown"; if (mac_address_is_valid(bssid.data())) { format_mac_addr_upper(bssid.data(), buf); @@ -104,6 +132,10 @@ void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::b this->publish_state(buf); } +#endif // USE_WIFI_CONNECT_STATE_LISTENERS + +#ifdef USE_WIFI_POWER_SAVE_LISTENERS + /************************ * PowerSaveModeWiFiInfo ***********************/ @@ -160,7 +192,7 @@ void PowerSaveModeWiFiInfo::on_wifi_power_save(wifi::WiFiPowerSaveMode mode) { this->publish_state(mode_str); } -#endif +#endif // USE_WIFI_POWER_SAVE_LISTENERS /********************* * MacAddressWifiInfo diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index b2242372da..8ef35a5f5d 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -2,14 +2,16 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/wifi/wifi_component.h" #ifdef USE_WIFI #include +#include namespace esphome::wifi_info { -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_IP_STATE_LISTENERS class IPAddressWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiIPStateListener { public: void setup() override; @@ -33,7 +35,9 @@ class DNSAddressWifiInfo final : public Component, public text_sensor::TextSenso void on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2) override; }; +#endif // USE_WIFI_IP_STATE_LISTENERS +#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS class ScanResultsWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiScanResultsListener { @@ -45,14 +49,16 @@ class ScanResultsWiFiInfo final : public Component, // WiFiScanResultsListener interface void on_wifi_scan_results(const wifi::wifi_scan_vector_t &results) override; }; +#endif // USE_WIFI_SCAN_RESULTS_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS class SSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener { public: void setup() override; void dump_config() override; // WiFiConnectStateListener interface - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; + void on_wifi_connect_state(StringRef ssid, std::span bssid) override; }; class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener { @@ -61,9 +67,11 @@ class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pu void dump_config() override; // WiFiConnectStateListener interface - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; + void on_wifi_connect_state(StringRef ssid, std::span bssid) override; }; +#endif // USE_WIFI_CONNECT_STATE_LISTENERS +#ifdef USE_WIFI_POWER_SAVE_LISTENERS class PowerSaveModeWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiPowerSaveListener { @@ -74,7 +82,7 @@ class PowerSaveModeWiFiInfo final : public Component, // WiFiPowerSaveListener interface void on_wifi_power_save(wifi::WiFiPowerSaveMode mode) override; }; -#endif +#endif // USE_WIFI_POWER_SAVE_LISTENERS class MacAddressWifiInfo final : public Component, public text_sensor::TextSensor { public: diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 82cb90c745..075cfd96c6 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -25,6 +25,6 @@ CONFIG_SCHEMA = sensor.sensor_schema( async def to_code(config): - wifi.request_wifi_listeners() + wifi.request_wifi_connect_state_listener() var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 9f581f1eb2..9ff4cc54a0 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -2,18 +2,20 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/wifi/wifi_component.h" #ifdef USE_WIFI +#include namespace esphome::wifi_signal { -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS class WiFiSignalSensor : public sensor::Sensor, public PollingComponent, public wifi::WiFiConnectStateListener { #else class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { #endif public: -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS void setup() override { wifi::global_wifi_component->add_connect_state_listener(this); } #endif void update() override { @@ -26,9 +28,9 @@ class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } -#ifdef USE_WIFI_LISTENERS +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS // WiFiConnectStateListener interface - update RSSI immediately on connect - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override { this->update(); } + void on_wifi_connect_state(StringRef ssid, std::span bssid) override { this->update(); } #endif }; diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index 2de6f0d2e3..7810a40ae1 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -131,15 +131,21 @@ void Wireguard::update() { } void Wireguard::dump_config() { - ESP_LOGCONFIG(TAG, "WireGuard:"); - ESP_LOGCONFIG(TAG, " Address: %s", this->address_.c_str()); - ESP_LOGCONFIG(TAG, " Netmask: %s", this->netmask_.c_str()); - ESP_LOGCONFIG(TAG, " Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str()); - ESP_LOGCONFIG(TAG, " Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str()); - ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_); - ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str()); - ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"), - (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + // clang-format off + ESP_LOGCONFIG( + TAG, + "WireGuard:\n" + " Address: %s\n" + " Netmask: %s\n" + " Private Key: " LOG_SECRET("%s") "\n" + " Peer Endpoint: " LOG_SECRET("%s") "\n" + " Peer Port: " LOG_SECRET("%d") "\n" + " Peer Public Key: " LOG_SECRET("%s") "\n" + " Peer Pre-shared Key: " LOG_SECRET("%s"), + this->address_.c_str(), this->netmask_.c_str(), mask_key(this->private_key_).c_str(), + this->peer_endpoint_.c_str(), this->peer_port_, this->peer_public_key_.c_str(), + (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + // clang-format on ESP_LOGCONFIG(TAG, " Peer Allowed IPs:"); for (auto &allowed_ip : this->allowed_ips_) { ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str()); diff --git a/esphome/components/wl_134/wl_134.cpp b/esphome/components/wl_134/wl_134.cpp index 403f8bd1b3..20a145d183 100644 --- a/esphome/components/wl_134/wl_134.cpp +++ b/esphome/components/wl_134/wl_134.cpp @@ -68,12 +68,15 @@ Wl134Component::Rfid134Error Wl134Component::read_packet_() { reading.reserved1 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED1]), RFID134_PACKET_CHECKSUM - RFID134_PACKET_RESERVED1); - ESP_LOGV(TAG, "Tag id: %012lld", reading.id); - ESP_LOGV(TAG, "Country: %03d", reading.country); - ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false"); - ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false"); - ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0); - ESP_LOGV(TAG, "Reserved1: %" PRId32, reading.reserved1); + ESP_LOGV(TAG, + "Tag id: %012lld\n" + "Country: %03d\n" + "isData: %s\n" + "isAnimal: %s\n" + "Reserved0: %d\n" + "Reserved1: %" PRId32, + reading.id, reading.country, reading.isData ? "true" : "false", reading.isAnimal ? "true" : "false", + reading.reserved0, reading.reserved1); char buf[20]; sprintf(buf, "%03d%012lld", reading.country, reading.id); diff --git a/esphome/components/x9c/x9c.cpp b/esphome/components/x9c/x9c.cpp index 5cd4fba8c0..8f66c46015 100644 --- a/esphome/components/x9c/x9c.cpp +++ b/esphome/components/x9c/x9c.cpp @@ -62,14 +62,14 @@ void X9cOutput::write_state(float state) { } void X9cOutput::dump_config() { - ESP_LOGCONFIG(TAG, "X9C Potentiometer Output:"); - LOG_PIN(" Chip Select Pin: ", this->cs_pin_); - LOG_PIN(" Increment Pin: ", this->inc_pin_); - LOG_PIN(" Up/Down Pin: ", this->ud_pin_); ESP_LOGCONFIG(TAG, + "X9C Potentiometer Output:\n" " Initial Value: %f\n" " Step Delay: %d", this->initial_value_, this->step_delay_); + LOG_PIN(" Chip Select Pin: ", this->cs_pin_); + LOG_PIN(" Increment Pin: ", this->inc_pin_); + LOG_PIN(" Up/Down Pin: ", this->ud_pin_); LOG_FLOAT_OUTPUT(this); } diff --git a/esphome/components/xgzp68xx/xgzp68xx.cpp b/esphome/components/xgzp68xx/xgzp68xx.cpp index 2b0824de0a..b5b786c105 100644 --- a/esphome/components/xgzp68xx/xgzp68xx.cpp +++ b/esphome/components/xgzp68xx/xgzp68xx.cpp @@ -72,8 +72,10 @@ void XGZP68XXComponent::update() { temperature_raw = encode_uint16(data[3], data[4]); // Convert the pressure data to hPa - ESP_LOGV(TAG, "Got raw pressure=%" PRIu32 ", raw temperature=%u", pressure_raw, temperature_raw); - ESP_LOGV(TAG, "K value is %u", this->k_value_); + ESP_LOGV(TAG, + "Got raw pressure=%" PRIu32 ", raw temperature=%u\n" + "K value is %u", + pressure_raw, temperature_raw, this->k_value_); // Sign extend the pressure float pressure_in_pa = (float) (((int32_t) pressure_raw << 8) >> 8); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 564870d74e..0018d35f1f 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -12,6 +12,9 @@ namespace xiaomi_ble { static const char *const TAG = "xiaomi_ble"; +// Maximum bytes to log in very verbose hex output (covers largest packet of ~24 bytes) +static constexpr size_t XIAOMI_MAX_LOG_BYTES = 32; + bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) { // button pressed, 3 bytes, only byte 3 is used for supported devices so far if ((value_type == 0x1001) && (value_length == 3)) { @@ -263,7 +266,10 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address) { if ((raw.size() != 19) && ((raw.size() < 22) || (raw.size() > 24))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); - ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty_to(hex_buf, raw.data(), raw.size())); return false; } @@ -320,12 +326,17 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c memcpy(mac_address + 4, mac_reverse + 1, 1); memcpy(mac_address + 5, mac_reverse, 1); ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); - ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str()); - ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); - ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); - ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); - ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str()); - ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(mac_address, mac_buf); + char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, " MAC address : %s", mac_buf); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty_to(hex_buf, raw.data(), raw.size())); + ESP_LOGVV(TAG, " Key : %s", format_hex_pretty_to(hex_buf, vector.key, vector.keysize)); + ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty_to(hex_buf, vector.iv, vector.ivsize)); + ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty_to(hex_buf, vector.ciphertext, vector.datasize)); + ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty_to(hex_buf, vector.tag, vector.tagsize)); mbedtls_ccm_free(&ctx); return false; } @@ -341,20 +352,23 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c raw[0] &= ~0x08; ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed."); - ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(), - static_cast(raw[4])); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(XIAOMI_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", + format_hex_pretty_to(hex_buf, raw.data() + cipher_pos, vector.datasize), static_cast(raw[4])); mbedtls_ccm_free(&ctx); return true; } -bool report_xiaomi_results(const optional &result, const std::string &address) { +bool report_xiaomi_results(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_xiaomi_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got Xiaomi %s (%s):", result->name.c_str(), address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi %s (%s):", result->name.c_str(), address); if (result->temperature.has_value()) { ESP_LOGD(TAG, " Temperature: %.1f°C", *result->temperature); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 77fb04fd78..42609a998b 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -71,7 +71,7 @@ bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_ bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult &result); optional parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data); bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address); -bool report_xiaomi_results(const optional &result, const std::string &address); +bool report_xiaomi_results(const optional &result, const char *address); class XiaomiListener : public esp32_ble_tracker::ESPBTDeviceListener { public: diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 4642768f90..82a04f0d6e 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgd1.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_cgd1 { static const char *const TAG = "xiaomi_cgd1"; +static constexpr size_t CGD1_BINDKEY_SIZE = 16; + void XiaomiCGD1::dump_config() { + char bindkey_hex[format_hex_pretty_size(CGD1_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi CGD1\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, CGD1_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -23,7 +27,9 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -42,7 +48,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -57,17 +63,7 @@ bool XiaomiCGD1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGD1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGD1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgd1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h index 393795439b..4a34eea32a 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.h @@ -13,7 +13,7 @@ namespace xiaomi_cgd1 { class XiaomiCGD1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 0dcbcbd05c..39ece3e091 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgdk2.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_cgdk2 { static const char *const TAG = "xiaomi_cgdk2"; +static constexpr size_t CGDK2_BINDKEY_SIZE = 16; + void XiaomiCGDK2::dump_config() { + char bindkey_hex[format_hex_pretty_size(CGDK2_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi CGDK2\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, CGDK2_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -23,7 +27,9 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -42,7 +48,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -57,17 +63,7 @@ bool XiaomiCGDK2::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGDK2::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGDK2::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgdk2 } // namespace esphome diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h index 1f5ef89869..ed917e2bbd 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.h @@ -13,7 +13,7 @@ namespace xiaomi_cgdk2 { class XiaomiCGDK2 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index f9fffa3f20..448592db16 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgg1.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_cgg1 { static const char *const TAG = "xiaomi_cgg1"; +static constexpr size_t CGG1_BINDKEY_SIZE = 16; + void XiaomiCGG1::dump_config() { + char bindkey_hex[format_hex_pretty_size(CGG1_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi CGG1\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, CGG1_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -23,7 +27,9 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -42,7 +48,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -57,17 +63,7 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGG1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGG1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgg1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h index 52904fd75e..c560bddd69 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h @@ -13,7 +13,7 @@ namespace xiaomi_cgg1 { class XiaomiCGG1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index db63beea89..8813f6479b 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -1,4 +1,5 @@ #include "xiaomi_cgpr1.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -21,7 +22,9 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -40,7 +43,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->idle_time.has_value() && this->idle_time_ != nullptr) @@ -57,17 +60,7 @@ bool XiaomiCGPR1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiCGPR1::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiCGPR1::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_cgpr1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h index 124f9411a1..82bbbfa58d 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.h @@ -16,7 +16,7 @@ class XiaomiCGPR1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index 990346e01e..159b6df80b 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -21,7 +21,9 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -39,7 +41,7 @@ bool XiaomiGCLS002::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 30990b121d..e10754d832 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -22,7 +22,9 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -40,7 +42,7 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp index 2bc52b8085..028d797ac1 100644 --- a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp +++ b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp @@ -23,7 +23,8 @@ bool XiaomiHHCCJCY10::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf)); bool success = false; for (auto &service_data : device.get_service_datas()) { diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index 3ae29088bb..2d2447db27 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -19,7 +19,9 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -37,7 +39,7 @@ bool XiaomiHHCCPOT002::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->moisture.has_value() && this->moisture_ != nullptr) diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index 1efebc2849..8216a92e54 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -21,7 +21,9 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -39,7 +41,7 @@ bool XiaomiJQJCY01YM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index a6f27c58b9..e140835d03 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -20,7 +20,9 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiLYWSD02::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp index dff1228f64..2dd60d4ecb 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp @@ -1,4 +1,5 @@ #include "xiaomi_lywsd02mmc.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_lywsd02mmc { static const char *const TAG = "xiaomi_lywsd02mmc"; +static constexpr size_t LYWSD02MMC_BINDKEY_SIZE = 16; + void XiaomiLYWSD02MMC::dump_config() { + char bindkey_hex[format_hex_pretty_size(LYWSD02MMC_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi LYWSD02MMC\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, LYWSD02MMC_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -23,7 +27,9 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -42,7 +48,7 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -57,17 +63,7 @@ bool XiaomiLYWSD02MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -void XiaomiLYWSD02MMC::set_bindkey(const std::string &bindkey) { - memset(this->bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - this->bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiLYWSD02MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_lywsd02mmc } // namespace esphome diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h index e1e0fcae40..968604fee6 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_lywsd02mmc { class XiaomiLYWSD02MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { this->address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index fb0165a21f..b11bbdc40c 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -1,4 +1,5 @@ #include "xiaomi_lywsd03mmc.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_lywsd03mmc { static const char *const TAG = "xiaomi_lywsd03mmc"; +static constexpr size_t LYWSD03MMC_BINDKEY_SIZE = 16; + void XiaomiLYWSD03MMC::dump_config() { + char bindkey_hex[format_hex_pretty_size(LYWSD03MMC_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, LYWSD03MMC_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -23,7 +27,9 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +52,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -61,17 +67,7 @@ bool XiaomiLYWSD03MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &device return success; } -void XiaomiLYWSD03MMC::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiLYWSD03MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_lywsd03mmc } // namespace esphome diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h index 3c7907479a..d890e5ed12 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_lywsd03mmc { class XiaomiLYWSD03MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index 749ca83afb..65991ffa0e 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -20,7 +20,9 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiLYWSDCGQ::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp index e613faec7e..1097b9c1e8 100644 --- a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp @@ -20,7 +20,9 @@ bool XiaomiMHOC303::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiMHOC303::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index 90b654873b..10cd15ddbd 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -1,4 +1,5 @@ #include "xiaomi_mhoc401.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,11 +9,14 @@ namespace xiaomi_mhoc401 { static const char *const TAG = "xiaomi_mhoc401"; +static constexpr size_t MHOC401_BINDKEY_SIZE = 16; + void XiaomiMHOC401::dump_config() { + char bindkey_hex[format_hex_pretty_size(MHOC401_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi MHOC401\n" " Bindkey: %s", - format_hex_pretty(this->bindkey_, 16).c_str()); + format_hex_pretty_to(bindkey_hex, this->bindkey_, MHOC401_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -23,7 +27,9 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +52,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -61,17 +67,7 @@ bool XiaomiMHOC401::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return success; } -void XiaomiMHOC401::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiMHOC401::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_mhoc401 } // namespace esphome diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h index 1acdaa88af..13547e45d9 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.h @@ -13,7 +13,7 @@ namespace xiaomi_mhoc401 { class XiaomiMHOC401 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 29c9de1652..e4f77fb915 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -1,4 +1,5 @@ #include "xiaomi_miscale.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -19,7 +20,9 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -30,7 +33,7 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!parse_message_(service_data.data, *res)) continue; - if (!report_results_(res, device.address_str())) + if (!report_results_(res, addr_str)) continue; if (res->weight.has_value() && this->weight_ != nullptr) @@ -61,9 +64,10 @@ optional XiaomiMiscale::parse_header_(const esp32_ble_tracker::Serv } else if (service_data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x181B) && service_data.data.size() == 13) { result.version = 2; } else { + char uuid_buf[esp32_ble::UUID_STR_LEN]; ESP_LOGVV(TAG, "parse_header(): Couldn't identify scale version or data size was not correct. UUID: %s, data_size: %d", - service_data.uuid.to_string().c_str(), service_data.data.size()); + service_data.uuid.to_str(uuid_buf), service_data.data.size()); return {}; } @@ -145,13 +149,13 @@ bool XiaomiMiscale::parse_message_v2_(const std::vector &message, Parse return true; } -bool XiaomiMiscale::report_results_(const optional &result, const std::string &address) { +bool XiaomiMiscale::report_results_(const optional &result, const char *address) { if (!result.has_value()) { ESP_LOGVV(TAG, "report_results(): no results available."); return false; } - ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address.c_str()); + ESP_LOGD(TAG, "Got Xiaomi Miscale v%d (%s):", result->version, address); if (result->weight.has_value()) { ESP_LOGD(TAG, " Weight: %.2fkg", *result->weight); diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 10d308ef6c..3d793e07ac 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -37,7 +37,7 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis bool parse_message_(const std::vector &message, ParseResult &result); bool parse_message_v1_(const std::vector &message, ParseResult &result); bool parse_message_v2_(const std::vector &message, ParseResult &result); - bool report_results_(const optional &result, const std::string &address); + bool report_results_(const optional &result, const char *address); }; } // namespace xiaomi_miscale diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index 16c0b42279..ec03c851cd 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -1,4 +1,5 @@ #include "xiaomi_mjyd02yla.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -22,7 +23,9 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -41,7 +44,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->idle_time.has_value() && this->idle_time_ != nullptr) @@ -60,17 +63,7 @@ bool XiaomiMJYD02YLA::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return success; } -void XiaomiMJYD02YLA::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiMJYD02YLA::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_mjyd02yla } // namespace esphome diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h index e1b4055696..bf9dcaf844 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.h @@ -16,7 +16,7 @@ class XiaomiMJYD02YLA : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index 1a8e72bd2c..a3f9325946 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -18,7 +18,9 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -36,7 +38,7 @@ bool XiaomiMUE4094RT::parse_device(const esp32_ble_tracker::ESPBTDevice &device) if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->has_motion.has_value()) { diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp index 498e724368..ee3ad316e1 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -1,4 +1,5 @@ #include "xiaomi_rtcgq02lm.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,9 +9,12 @@ namespace xiaomi_rtcgq02lm { static const char *const TAG = "xiaomi_rtcgq02lm"; +static constexpr size_t RTCGQ02LM_BINDKEY_SIZE = 16; + void XiaomiRTCGQ02LM::dump_config() { + char bindkey_hex[format_hex_pretty_size(RTCGQ02LM_BINDKEY_SIZE)]; ESP_LOGCONFIG(TAG, "Xiaomi RTCGQ02LM"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, RTCGQ02LM_BINDKEY_SIZE, '.')); #ifdef USE_BINARY_SENSOR LOG_BINARY_SENSOR(" ", "Motion", this->motion_); LOG_BINARY_SENSOR(" ", "Light", this->light_); @@ -26,7 +30,9 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -46,7 +52,7 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } #ifdef USE_BINARY_SENSOR @@ -73,17 +79,7 @@ bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return success; } -void XiaomiRTCGQ02LM::set_bindkey(const std::string &bindkey) { - memset(bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiRTCGQ02LM::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_rtcgq02lm } // namespace esphome diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h index ae00a28ac9..87dfc0b62b 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h @@ -19,7 +19,7 @@ namespace xiaomi_rtcgq02lm { class XiaomiRTCGQ02LM : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; }; - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; void dump_config() override; diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index b57bf5cd05..b0e02e2372 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -20,7 +20,9 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -38,7 +40,7 @@ bool XiaomiWX08ZM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { continue; } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->is_active.has_value()) { diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index f8712e7fd4..50cf5f2d76 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -1,4 +1,5 @@ #include "xiaomi_xmwsdj04mmc.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #ifdef USE_ESP32 @@ -8,9 +9,14 @@ namespace xiaomi_xmwsdj04mmc { static const char *const TAG = "xiaomi_xmwsdj04mmc"; +static constexpr size_t XMWSDJ04MMC_BINDKEY_SIZE = 16; + void XiaomiXMWSDJ04MMC::dump_config() { - ESP_LOGCONFIG(TAG, "Xiaomi XMWSDJ04MMC"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); + char bindkey_hex[format_hex_pretty_size(XMWSDJ04MMC_BINDKEY_SIZE)]; + ESP_LOGCONFIG(TAG, + "Xiaomi XMWSDJ04MMC\n" + " Bindkey: %s", + format_hex_pretty_to(bindkey_hex, this->bindkey_, XMWSDJ04MMC_BINDKEY_SIZE, '.')); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -21,7 +27,9 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); return false; } - ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + const char *addr_str = device.address_str_to(addr_buf); + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str); bool success = false; for (auto &service_data : device.get_service_datas()) { @@ -44,7 +52,7 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic // see https://github.com/custom-components/sensor.mitemp_bt/issues/7#issuecomment-595948254 *res->humidity = trunc(*res->humidity); } - if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + if (!(xiaomi_ble::report_xiaomi_results(res, addr_str))) { continue; } if (res->temperature.has_value() && this->temperature_ != nullptr) @@ -59,17 +67,7 @@ bool XiaomiXMWSDJ04MMC::parse_device(const esp32_ble_tracker::ESPBTDevice &devic return success; } -void XiaomiXMWSDJ04MMC::set_bindkey(const std::string &bindkey) { - memset(this->bindkey_, 0, 16); - if (bindkey.size() != 32) { - return; - } - char temp[3] = {0}; - for (int i = 0; i < 16; i++) { - strncpy(temp, &(bindkey.c_str()[i * 2]), 2); - this->bindkey_[i] = std::strtoul(temp, nullptr, 16); - } -} +void XiaomiXMWSDJ04MMC::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } } // namespace xiaomi_xmwsdj04mmc } // namespace esphome diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h index ed0458ce49..22cac63059 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.h @@ -13,7 +13,7 @@ namespace xiaomi_xmwsdj04mmc { class XiaomiXMWSDJ04MMC : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { this->address_ = address; } - void set_bindkey(const std::string &bindkey); + void set_bindkey(const char *bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; diff --git a/esphome/components/xl9535/xl9535.cpp b/esphome/components/xl9535/xl9535.cpp index 958fc5eede..dd6c8188eb 100644 --- a/esphome/components/xl9535/xl9535.cpp +++ b/esphome/components/xl9535/xl9535.cpp @@ -110,7 +110,9 @@ void XL9535Component::pin_mode(uint8_t pin, gpio::Flags mode) { void XL9535GPIOPin::setup() { this->pin_mode(this->flags_); } -std::string XL9535GPIOPin::dump_summary() const { return str_snprintf("%u via XL9535", 15, this->pin_); } +size_t XL9535GPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "%u via XL9535", this->pin_); +} void XL9535GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool XL9535GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } diff --git a/esphome/components/xl9535/xl9535.h b/esphome/components/xl9535/xl9535.h index 3b511fd9b3..be0e2fbd82 100644 --- a/esphome/components/xl9535/xl9535.h +++ b/esphome/components/xl9535/xl9535.h @@ -39,7 +39,7 @@ class XL9535GPIOPin : public GPIOPin { gpio::Flags get_flags() const override { return this->flags_; } void setup() override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; diff --git a/esphome/components/xpt2046/touchscreen/xpt2046.cpp b/esphome/components/xpt2046/touchscreen/xpt2046.cpp index fa99e3afa7..84d3daf823 100644 --- a/esphome/components/xpt2046/touchscreen/xpt2046.cpp +++ b/esphome/components/xpt2046/touchscreen/xpt2046.cpp @@ -59,10 +59,8 @@ void XPT2046Component::update_touches() { } void XPT2046Component::dump_config() { - ESP_LOGCONFIG(TAG, "XPT2046:"); - - LOG_PIN(" IRQ Pin: ", this->irq_pin_); ESP_LOGCONFIG(TAG, + "XPT2046:\n" " X min: %d\n" " X max: %d\n" " Y min: %d\n" @@ -73,7 +71,7 @@ void XPT2046Component::dump_config() { " threshold: %d", this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_, YESNO(this->swap_x_y_), YESNO(this->invert_x_), YESNO(this->invert_y_), this->threshold_); - + LOG_PIN(" IRQ Pin: ", this->irq_pin_); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/zephyr/__init__.py b/esphome/components/zephyr/__init__.py index 0381fbcba9..8e3ae86bbe 100644 --- a/esphome/components/zephyr/__init__.py +++ b/esphome/components/zephyr/__init__.py @@ -3,7 +3,8 @@ import textwrap from typing import TypedDict import esphome.codegen as cg -from esphome.const import CONF_BOARD +import esphome.config_validation as cv +from esphome.const import CONF_BOARD, KEY_CORE, KEY_FRAMEWORK_VERSION from esphome.core import CORE from esphome.helpers import copy_file_if_changed, write_file_if_changed @@ -21,7 +22,6 @@ from .const import ( ) CODEOWNERS = ["@tomaszduda23"] -AUTO_LOAD = ["preferences"] PrjConfValueType = bool | str | int @@ -111,32 +111,15 @@ def add_extra_script(stage: str, filename: str, path: Path) -> None: def zephyr_to_code(config): - cg.add(zephyr_ns.setup_preferences()) cg.add_build_flag("-DUSE_ZEPHYR") cg.set_cpp_standard("gnu++20") # build is done by west so bypass board checking in platformio cg.add_platformio_option("boards_dir", CORE.relative_build_path("boards")) - # c++ support zephyr_add_prj_conf("NEWLIB_LIBC", True) - zephyr_add_prj_conf("CONFIG_FPU", True) + zephyr_add_prj_conf("FPU", True) zephyr_add_prj_conf("NEWLIB_LIBC_FLOAT_PRINTF", True) - zephyr_add_prj_conf("CPLUSPLUS", True) - zephyr_add_prj_conf("CONFIG_STD_CPP20", True) - zephyr_add_prj_conf("LIB_CPLUSPLUS", True) - # preferences - zephyr_add_prj_conf("SETTINGS", True) - zephyr_add_prj_conf("NVS", True) - zephyr_add_prj_conf("FLASH_MAP", True) - zephyr_add_prj_conf("CONFIG_FLASH", True) - # watchdog - zephyr_add_prj_conf("WATCHDOG", True) - zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False) - # disable console - zephyr_add_prj_conf("UART_CONSOLE", False) - zephyr_add_prj_conf("CONSOLE", False, False) - # use NFC pins as GPIO - zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True) + zephyr_add_prj_conf("STD_CPP20", True) # os: ***** USAGE FAULT ***** # os: Illegal load of EXC_RETURN into PC @@ -149,6 +132,14 @@ def zephyr_to_code(config): ) +def zephyr_setup_preferences(): + cg.add(zephyr_ns.setup_preferences()) + zephyr_add_prj_conf("SETTINGS", True) + zephyr_add_prj_conf("NVS", True) + zephyr_add_prj_conf("FLASH_MAP", True) + zephyr_add_prj_conf("FLASH", True) + + def _format_prj_conf_val(value: PrjConfValueType) -> str: if isinstance(value, bool): return "y" if value else "n" @@ -160,6 +151,9 @@ def _format_prj_conf_val(value: PrjConfValueType) -> str: def zephyr_add_cdc_acm(config, id): + framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + if CORE.is_nrf52 and framework_ver >= cv.Version(3, 2, 0): + zephyr_add_prj_conf("CONFIG_USB_DEVICE_STACK_NEXT", False) zephyr_add_prj_conf("USB_DEVICE_STACK", True) zephyr_add_prj_conf("USB_CDC_ACM", True) # prevent device to go to susspend, without this communication stop working in python @@ -169,12 +163,12 @@ def zephyr_add_cdc_acm(config, id): zephyr_add_prj_conf("USB_CDC_ACM_LOG_LEVEL_WRN", True) zephyr_add_overlay( f""" -&zephyr_udc0 {{ - cdc_acm_uart{id}: cdc_acm_uart{id} {{ - compatible = "zephyr,cdc-acm-uart"; - }}; -}}; -""" + &zephyr_udc0 {{ + cdc_acm_uart{id}: cdc_acm_uart{id} {{ + compatible = "zephyr,cdc-acm-uart"; + }}; + }}; + """ ) @@ -194,11 +188,12 @@ def copy_files(): if user: zephyr_add_overlay( f""" -/ {{ - zephyr,user {{ - {[f"{key} = {', '.join(value)};" for key, value in user.items()][0]} -}}; -}};""" + / {{ + zephyr,user {{ + {[f"{key} = {', '.join(value)};" for key, value in user.items()][0]} + }}; + }}; + """ ) want_opts = zephyr_data()[KEY_PRJ_CONF] diff --git a/esphome/components/zephyr/core.cpp b/esphome/components/zephyr/core.cpp index d5427a0ebf..d7027b33f5 100644 --- a/esphome/components/zephyr/core.cpp +++ b/esphome/components/zephyr/core.cpp @@ -10,8 +10,10 @@ namespace esphome { +#ifdef CONFIG_WATCHDOG static int wdt_channel_id = -1; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static const device *const WDT = DEVICE_DT_GET(DT_ALIAS(watchdog0)); +#endif void yield() { ::k_yield(); } uint32_t millis() { return k_ticks_to_ms_floor32(k_uptime_ticks()); } @@ -20,10 +22,16 @@ void delayMicroseconds(uint32_t us) { ::k_usleep(us); } void delay(uint32_t ms) { ::k_msleep(ms); } void arch_init() { +#ifdef CONFIG_WATCHDOG if (device_is_ready(WDT)) { static wdt_timeout_cfg wdt_config{}; wdt_config.flags = WDT_FLAG_RESET_SOC; +#ifdef USE_ZIGBEE + // zboss thread use a lot of cpu cycles during start + wdt_config.window.max = 10000; +#else wdt_config.window.max = 2000; +#endif wdt_channel_id = wdt_install_timeout(WDT, &wdt_config); if (wdt_channel_id >= 0) { uint8_t options = 0; @@ -36,12 +44,15 @@ void arch_init() { wdt_setup(WDT, options); } } +#endif } void arch_feed_wdt() { +#ifdef CONFIG_WATCHDOG if (wdt_channel_id >= 0) { wdt_feed(WDT, wdt_channel_id); } +#endif } void arch_restart() { sys_reboot(SYS_REBOOT_COLD); } @@ -72,6 +83,7 @@ bool random_bytes(uint8_t *data, size_t len) { return true; } +#ifdef USE_NRF52 void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) mac[0] = ((NRF_FICR->DEVICEADDR[1] & 0xFFFF) >> 8) | 0xC0; mac[1] = NRF_FICR->DEVICEADDR[1] & 0xFFFF; @@ -80,7 +92,7 @@ void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parame mac[4] = NRF_FICR->DEVICEADDR[0] >> 8; mac[5] = NRF_FICR->DEVICEADDR[0]; } - +#endif } // namespace esphome void setup(); diff --git a/esphome/components/zephyr/gpio.cpp b/esphome/components/zephyr/gpio.cpp index 41b983535c..1d5b0f282b 100644 --- a/esphome/components/zephyr/gpio.cpp +++ b/esphome/components/zephyr/gpio.cpp @@ -50,25 +50,7 @@ void ZephyrGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Inte } void ZephyrGPIOPin::setup() { - const struct device *gpio = nullptr; - if (this->pin_ < 32) { -#define GPIO0 DT_NODELABEL(gpio0) -#if DT_NODE_HAS_STATUS(GPIO0, okay) - gpio = DEVICE_DT_GET(GPIO0); -#else -#error "gpio0 is disabled" -#endif - } else { -#define GPIO1 DT_NODELABEL(gpio1) -#if DT_NODE_HAS_STATUS(GPIO1, okay) - gpio = DEVICE_DT_GET(GPIO1); -#else -#error "gpio1 is disabled" -#endif - } - if (device_is_ready(gpio)) { - this->gpio_ = gpio; - } else { + if (!device_is_ready(this->gpio_)) { ESP_LOGE(TAG, "gpio %u is not ready.", this->pin_); return; } @@ -79,23 +61,22 @@ void ZephyrGPIOPin::pin_mode(gpio::Flags flags) { if (nullptr == this->gpio_) { return; } - auto ret = gpio_pin_configure(this->gpio_, this->pin_ % 32, flags_to_mode(flags, this->inverted_, this->value_)); + auto ret = gpio_pin_configure(this->gpio_, this->pin_ % this->gpio_size_, + flags_to_mode(flags, this->inverted_, this->value_)); if (ret != 0) { ESP_LOGE(TAG, "gpio %u cannot be configured %d.", this->pin_, ret); } } -std::string ZephyrGPIOPin::dump_summary() const { - char buffer[32]; - snprintf(buffer, sizeof(buffer), "GPIO%u, P%u.%u", this->pin_, this->pin_ / 32, this->pin_ % 32); - return buffer; +size_t ZephyrGPIOPin::dump_summary(char *buffer, size_t len) const { + return snprintf(buffer, len, "GPIO%u, %s%u", this->pin_, this->pin_name_prefix_, this->pin_ % this->gpio_size_); } bool ZephyrGPIOPin::digital_read() { if (nullptr == this->gpio_) { return false; } - return bool(gpio_pin_get(this->gpio_, this->pin_ % 32) != this->inverted_); + return bool(gpio_pin_get(this->gpio_, this->pin_ % this->gpio_size_) != this->inverted_); } void ZephyrGPIOPin::digital_write(bool value) { @@ -105,7 +86,7 @@ void ZephyrGPIOPin::digital_write(bool value) { if (nullptr == this->gpio_) { return; } - gpio_pin_set(this->gpio_, this->pin_ % 32, value != this->inverted_ ? 1 : 0); + gpio_pin_set(this->gpio_, this->pin_ % this->gpio_size_, value != this->inverted_ ? 1 : 0); } void ZephyrGPIOPin::detach_interrupt() const { // TODO diff --git a/esphome/components/zephyr/gpio.h b/esphome/components/zephyr/gpio.h index 6e8f81857a..c9540f4f01 100644 --- a/esphome/components/zephyr/gpio.h +++ b/esphome/components/zephyr/gpio.h @@ -8,6 +8,11 @@ namespace zephyr { class ZephyrGPIOPin : public InternalGPIOPin { public: + ZephyrGPIOPin(const device *gpio, int gpio_size, const char *pin_name_prefix) { + this->gpio_ = gpio; + this->gpio_size_ = gpio_size; + this->pin_name_prefix_ = pin_name_prefix; + } void set_pin(uint8_t pin) { this->pin_ = pin; } void set_inverted(bool inverted) { this->inverted_ = inverted; } void set_flags(gpio::Flags flags) { this->flags_ = flags; } @@ -16,7 +21,7 @@ class ZephyrGPIOPin : public InternalGPIOPin { void pin_mode(gpio::Flags flags) override; bool digital_read() override; void digital_write(bool value) override; - std::string dump_summary() const override; + size_t dump_summary(char *buffer, size_t len) const override; void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return this->pin_; } @@ -25,10 +30,12 @@ class ZephyrGPIOPin : public InternalGPIOPin { protected: void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; - uint8_t pin_; - bool inverted_{}; - gpio::Flags flags_{}; const device *gpio_{nullptr}; + const char *pin_name_prefix_{nullptr}; + gpio::Flags flags_{}; + uint8_t pin_; + uint8_t gpio_size_{}; + bool inverted_{}; bool value_{false}; }; diff --git a/esphome/components/zephyr/preferences.cpp b/esphome/components/zephyr/preferences.cpp index d702366044..08b361b8fb 100644 --- a/esphome/components/zephyr/preferences.cpp +++ b/esphome/components/zephyr/preferences.cpp @@ -1,4 +1,5 @@ #ifdef USE_ZEPHYR +#ifdef CONFIG_SETTINGS #include #include "esphome/core/preferences.h" @@ -154,3 +155,4 @@ ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const } // namespace esphome #endif +#endif diff --git a/esphome/components/zigbee/__init__.py b/esphome/components/zigbee/__init__.py new file mode 100644 index 0000000000..2281dd38a9 --- /dev/null +++ b/esphome/components/zigbee/__init__.py @@ -0,0 +1,173 @@ +import logging +from typing import Any + +from esphome import automation, core +import esphome.codegen as cg +from esphome.components.nrf52.boards import BOOTLOADER_CONFIG, Section +from esphome.components.zephyr import zephyr_add_pm_static, zephyr_data +from esphome.components.zephyr.const import KEY_BOOTLOADER +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_INTERNAL, CONF_NAME +from esphome.core import CORE +from esphome.types import ConfigType + +from .const_zephyr import ( + CONF_MAX_EP_NUMBER, + CONF_ON_JOIN, + CONF_POWER_SOURCE, + CONF_WIPE_ON_BOOT, + CONF_ZIGBEE_ID, + KEY_EP_NUMBER, + KEY_ZIGBEE, + POWER_SOURCE, + ZigbeeComponent, + zigbee_ns, +) +from .zigbee_zephyr import zephyr_binary_sensor, zephyr_sensor, zephyr_switch + +_LOGGER = logging.getLogger(__name__) + +CODEOWNERS = ["@tomaszduda23"] + + +def zigbee_set_core_data(config: ConfigType) -> ConfigType: + if zephyr_data()[KEY_BOOTLOADER] in BOOTLOADER_CONFIG: + zephyr_add_pm_static( + [Section("empty_after_zboss_offset", 0xF4000, 0xC000, "flash_primary")] + ) + + return config + + +BINARY_SENSOR_SCHEMA = cv.Schema({}).extend(zephyr_binary_sensor) +SENSOR_SCHEMA = cv.Schema({}).extend(zephyr_sensor) +SWITCH_SCHEMA = cv.Schema({}).extend(zephyr_switch) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(ZigbeeComponent), + cv.Optional(CONF_ON_JOIN): automation.validate_automation(single=True), + cv.Optional(CONF_WIPE_ON_BOOT, default=False): cv.All( + cv.Any( + cv.boolean, + cv.one_of(*["once"], lower=True), + ), + cv.requires_component("nrf52"), + ), + cv.Optional(CONF_POWER_SOURCE, default="DC_SOURCE"): cv.enum( + POWER_SOURCE, upper=True + ), + } + ).extend(cv.COMPONENT_SCHEMA), + zigbee_set_core_data, + cv.only_with_framework("zephyr"), +) + + +def validate_number_of_ep(config: ConfigType) -> None: + if KEY_ZIGBEE not in CORE.data: + raise cv.Invalid("At least one zigbee device need to be included") + count = len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER]) + if count == 1: + raise cv.Invalid( + "Single endpoint is not supported https://github.com/Koenkk/zigbee2mqtt/issues/29888" + ) + if count > CONF_MAX_EP_NUMBER and not CORE.testing_mode: + raise cv.Invalid(f"Maximum number of end points is {CONF_MAX_EP_NUMBER}") + + +FINAL_VALIDATE_SCHEMA = cv.All( + validate_number_of_ep, +) + + +async def to_code(config: ConfigType) -> None: + cg.add_define("USE_ZIGBEE") + if CORE.using_zephyr: + from .zigbee_zephyr import zephyr_to_code + + await zephyr_to_code(config) + + +async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: + if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + return + if CORE.using_zephyr: + from .zigbee_zephyr import zephyr_setup_binary_sensor + + await zephyr_setup_binary_sensor(entity, config) + + +async def setup_sensor(entity: cg.MockObj, config: ConfigType) -> None: + if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + return + if CORE.using_zephyr: + from .zigbee_zephyr import zephyr_setup_sensor + + await zephyr_setup_sensor(entity, config) + + +async def setup_switch(entity: cg.MockObj, config: ConfigType) -> None: + if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + return + if CORE.using_zephyr: + from .zigbee_zephyr import zephyr_setup_switch + + await zephyr_setup_switch(entity, config) + + +def consume_endpoint(config: ConfigType) -> ConfigType: + if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + return config + if " " in config[CONF_NAME]: + _LOGGER.warning( + "Spaces in '%s' work with ZHA but not Zigbee2MQTT. For Zigbee2MQTT use '%s'", + config[CONF_NAME], + config[CONF_NAME].replace(" ", "_"), + ) + data: dict[str, Any] = CORE.data.setdefault(KEY_ZIGBEE, {}) + slots: list[str] = data.setdefault(KEY_EP_NUMBER, []) + slots.extend([""]) + return config + + +def validate_binary_sensor(config: ConfigType) -> ConfigType: + return consume_endpoint(config) + + +def validate_sensor(config: ConfigType) -> ConfigType: + return consume_endpoint(config) + + +def validate_switch(config: ConfigType) -> ConfigType: + return consume_endpoint(config) + + +ZIGBEE_ACTION_SCHEMA = automation.maybe_simple_id( + cv.Schema( + { + cv.GenerateID(): cv.use_id(ZigbeeComponent), + } + ) +) + +FactoryResetAction = zigbee_ns.class_( + "FactoryResetAction", automation.Action, cg.Parented.template(ZigbeeComponent) +) + + +@automation.register_action( + "zigbee.factory_reset", + FactoryResetAction, + ZIGBEE_ACTION_SCHEMA, +) +async def reset_zigbee_to_code( + config: ConfigType, + action_id: core.ID, + template_arg: cg.TemplateArguments, + args: list[tuple], +) -> cg.Pvariable: + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/zigbee/automation.h b/esphome/components/zigbee/automation.h new file mode 100644 index 0000000000..1822e6a029 --- /dev/null +++ b/esphome/components/zigbee/automation.h @@ -0,0 +1,16 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ZIGBEE +#ifdef USE_NRF52 +#include "zigbee_zephyr.h" +#endif +namespace esphome::zigbee { + +template class FactoryResetAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->factory_reset(); } +}; + +} // namespace esphome::zigbee + +#endif diff --git a/esphome/components/zigbee/const_zephyr.py b/esphome/components/zigbee/const_zephyr.py new file mode 100644 index 0000000000..0372f22593 --- /dev/null +++ b/esphome/components/zigbee/const_zephyr.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg + +zigbee_ns = cg.esphome_ns.namespace("zigbee") +ZigbeeComponent = zigbee_ns.class_("ZigbeeComponent", cg.Component) +BinaryAttrs = zigbee_ns.struct("BinaryAttrs") +AnalogAttrs = zigbee_ns.struct("AnalogAttrs") + +CONF_MAX_EP_NUMBER = 8 +CONF_ZIGBEE_ID = "zigbee_id" +CONF_ON_JOIN = "on_join" +CONF_WIPE_ON_BOOT = "wipe_on_boot" +CONF_ZIGBEE_BINARY_SENSOR = "zigbee_binary_sensor" +CONF_ZIGBEE_SENSOR = "zigbee_sensor" +CONF_ZIGBEE_SWITCH = "zigbee_switch" +CONF_POWER_SOURCE = "power_source" +POWER_SOURCE = { + "UNKNOWN": "ZB_ZCL_BASIC_POWER_SOURCE_UNKNOWN", + "MAINS_SINGLE_PHASE": "ZB_ZCL_BASIC_POWER_SOURCE_MAINS_SINGLE_PHASE", + "MAINS_THREE_PHASE": "ZB_ZCL_BASIC_POWER_SOURCE_MAINS_THREE_PHASE", + "BATTERY": "ZB_ZCL_BASIC_POWER_SOURCE_BATTERY", + "DC_SOURCE": "ZB_ZCL_BASIC_POWER_SOURCE_DC_SOURCE", + "EMERGENCY_MAINS_CONST": "ZB_ZCL_BASIC_POWER_SOURCE_EMERGENCY_MAINS_CONST", + "EMERGENCY_MAINS_TRANSF": "ZB_ZCL_BASIC_POWER_SOURCE_EMERGENCY_MAINS_TRANSF", +} + +# Keys for CORE.data storage +KEY_ZIGBEE = "zigbee" +KEY_EP_NUMBER = "ep_number" + +# External ZBOSS SDK types (just strings for codegen) +ZB_ZCL_BASIC_ATTRS_EXT_T = "zb_zcl_basic_attrs_ext_t" +ZB_ZCL_IDENTIFY_ATTRS_T = "zb_zcl_identify_attrs_t" + +# Cluster IDs +ZB_ZCL_CLUSTER_ID_BASIC = "ZB_ZCL_CLUSTER_ID_BASIC" +ZB_ZCL_CLUSTER_ID_IDENTIFY = "ZB_ZCL_CLUSTER_ID_IDENTIFY" +ZB_ZCL_CLUSTER_ID_BINARY_INPUT = "ZB_ZCL_CLUSTER_ID_BINARY_INPUT" +ZB_ZCL_CLUSTER_ID_ANALOG_INPUT = "ZB_ZCL_CLUSTER_ID_ANALOG_INPUT" +ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT = "ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT" diff --git a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp new file mode 100644 index 0000000000..8b7aff70a8 --- /dev/null +++ b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp @@ -0,0 +1,37 @@ +#include "zigbee_binary_sensor_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_BINARY_SENSOR) +#include "esphome/core/log.h" +extern "C" { +#include +#include +#include +#include +#include +} +namespace esphome::zigbee { + +static const char *const TAG = "zigbee.binary_sensor"; + +ZigbeeBinarySensor::ZigbeeBinarySensor(binary_sensor::BinarySensor *binary_sensor) : binary_sensor_(binary_sensor) {} + +void ZigbeeBinarySensor::setup() { + this->binary_sensor_->add_on_state_callback([this](bool state) { + this->cluster_attributes_->present_value = state ? ZB_TRUE : ZB_FALSE; + ESP_LOGD(TAG, "Set attribute endpoint: %d, present_value %d", this->endpoint_, + this->cluster_attributes_->present_value); + ZB_ZCL_SET_ATTRIBUTE(this->endpoint_, ZB_ZCL_CLUSTER_ID_BINARY_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, &this->cluster_attributes_->present_value, + ZB_FALSE); + this->parent_->flush(); + }); +} + +void ZigbeeBinarySensor::dump_config() { + ESP_LOGCONFIG(TAG, + "Zigbee Binary Sensor\n" + " Endpoint: %d, present_value %u", + this->endpoint_, this->cluster_attributes_->present_value); +} + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.h b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.h new file mode 100644 index 0000000000..aae79fa289 --- /dev/null +++ b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.h @@ -0,0 +1,45 @@ +#pragma once +#include "esphome/core/defines.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_BINARY_SENSOR) +#include "esphome/components/zigbee/zigbee_zephyr.h" +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +extern "C" { +#include +#include +} + +// it should have been defined inside of sdk. It is missing though +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +// copy of ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST + description +#define ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \ + description) \ + ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_BINARY_INPUT) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_OUT_OF_SERVICE_ID, (out_of_service)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_PRESENT_VALUE_ID, (present_value)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_STATUS_FLAG_ID, (status_flag)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_INPUT_DESCRIPTION_ID, (description)) \ + ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST + +namespace esphome::zigbee { + +class ZigbeeBinarySensor : public ZigbeeEntity, public Component { + public: + explicit ZigbeeBinarySensor(binary_sensor::BinarySensor *binary_sensor); + void set_cluster_attributes(BinaryAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; } + + void setup() override; + void dump_config() override; + + protected: + BinaryAttrs *cluster_attributes_{nullptr}; + binary_sensor::BinarySensor *binary_sensor_; +}; + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_sensor_zephyr.cpp b/esphome/components/zigbee/zigbee_sensor_zephyr.cpp new file mode 100644 index 0000000000..74550d6487 --- /dev/null +++ b/esphome/components/zigbee/zigbee_sensor_zephyr.cpp @@ -0,0 +1,76 @@ +#include "zigbee_sensor_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SENSOR) +#include "esphome/core/log.h" +extern "C" { +#include +#include +#include +#include +#include +} +namespace esphome::zigbee { + +static const char *const TAG = "zigbee.sensor"; + +ZigbeeSensor::ZigbeeSensor(sensor::Sensor *sensor) : sensor_(sensor) {} + +void ZigbeeSensor::setup() { + this->sensor_->add_on_state_callback([this](float state) { + this->cluster_attributes_->present_value = state; + ESP_LOGD(TAG, "Set attribute endpoint: %d, present_value %f", this->endpoint_, state); + ZB_ZCL_SET_ATTRIBUTE(this->endpoint_, ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, + (zb_uint8_t *) &this->cluster_attributes_->present_value, ZB_FALSE); + this->parent_->flush(); + }); +} + +void ZigbeeSensor::dump_config() { + ESP_LOGCONFIG(TAG, + "Zigbee Sensor\n" + " Endpoint: %d, present_value %f", + this->endpoint_, this->cluster_attributes_->present_value); +} + +const zb_uint8_t ZB_ZCL_ANALOG_INPUT_STATUS_FLAG_MAX_VALUE = 0x0F; + +static zb_ret_t check_value_analog_server(zb_uint16_t attr_id, zb_uint8_t endpoint, + zb_uint8_t *value) { // NOLINT(readability-non-const-parameter) + zb_ret_t ret = RET_OK; + ZVUNUSED(endpoint); + + switch (attr_id) { + case ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID: + ret = ZB_ZCL_CHECK_BOOL_VALUE(*value) ? RET_OK : RET_ERROR; + break; + case ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID: + break; + + case ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID: + if (*value > ZB_ZCL_ANALOG_INPUT_STATUS_FLAG_MAX_VALUE) { + ret = RET_ERROR; + } + break; + + default: + break; + } + + return ret; +} + +} // namespace esphome::zigbee + +void zb_zcl_analog_input_init_server() { + zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + esphome::zigbee::check_value_analog_server, (zb_zcl_cluster_write_attr_hook_t) NULL, + (zb_zcl_cluster_handler_t) NULL); +} + +void zb_zcl_analog_input_init_client() { + zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, ZB_ZCL_CLUSTER_CLIENT_ROLE, + (zb_zcl_cluster_check_value_t) NULL, (zb_zcl_cluster_write_attr_hook_t) NULL, + (zb_zcl_cluster_handler_t) NULL); +} + +#endif diff --git a/esphome/components/zigbee/zigbee_sensor_zephyr.h b/esphome/components/zigbee/zigbee_sensor_zephyr.h new file mode 100644 index 0000000000..37406f21d0 --- /dev/null +++ b/esphome/components/zigbee/zigbee_sensor_zephyr.h @@ -0,0 +1,86 @@ +#pragma once + +#include "esphome/components/zigbee/zigbee_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SENSOR) +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +extern "C" { +#include +#include +} + +enum { + ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID = 0x001C, + ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID = 0x0051, + ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID = 0x0055, + ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID = 0x006F, + ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID = 0x0075, +}; + +#define ZB_ZCL_ANALOG_INPUT_CLUSTER_REVISION_DEFAULT ((zb_uint16_t) 0x0001u) + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID, ZB_ZCL_ATTR_TYPE_BOOL, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \ + (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, ZB_ZCL_ATTR_TYPE_SINGLE, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL | ZB_ZCL_ATTR_ACCESS_REPORTING, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID, ZB_ZCL_ATTR_TYPE_8BITMAP, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \ + (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID, ZB_ZCL_ATTR_TYPE_16BIT_ENUM, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +#define ESPHOME_ZB_ZCL_DECLARE_ANALOG_INPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \ + engineering_units, description) \ + ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_ANALOG_INPUT) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_OUT_OF_SERVICE_ID, (out_of_service)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID, (present_value)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_STATUS_FLAG_ID, (status_flag)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_ENGINEERING_UNITS_ID, (engineering_units)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_ANALOG_INPUT_DESCRIPTION_ID, (description)) \ + ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST + +void zb_zcl_analog_input_init_server(); +void zb_zcl_analog_input_init_client(); +#define ZB_ZCL_CLUSTER_ID_ANALOG_INPUT_SERVER_ROLE_INIT zb_zcl_analog_input_init_server +#define ZB_ZCL_CLUSTER_ID_ANALOG_INPUT_CLIENT_ROLE_INIT zb_zcl_analog_input_init_client + +namespace esphome::zigbee { + +class ZigbeeSensor : public ZigbeeEntity, public Component { + public: + explicit ZigbeeSensor(sensor::Sensor *sensor); + void set_cluster_attributes(AnalogAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; } + + void setup() override; + void dump_config() override; + + protected: + AnalogAttrs *cluster_attributes_{nullptr}; + sensor::Sensor *sensor_{nullptr}; +}; + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_switch_zephyr.cpp b/esphome/components/zigbee/zigbee_switch_zephyr.cpp new file mode 100644 index 0000000000..5454f262f9 --- /dev/null +++ b/esphome/components/zigbee/zigbee_switch_zephyr.cpp @@ -0,0 +1,111 @@ +#include "zigbee_switch_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SWITCH) +#include "esphome/core/log.h" +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +namespace esphome::zigbee { + +static const char *const TAG = "zigbee_on_off.switch"; + +void ZigbeeSwitch::dump_config() { + ESP_LOGCONFIG(TAG, + "Zigbee Switch\n" + " Endpoint: %d, present_value %u", + this->endpoint_, this->cluster_attributes_->present_value); +} + +void ZigbeeSwitch::setup() { + this->parent_->add_callback(this->endpoint_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); }); + this->switch_->add_on_state_callback([this](bool state) { + this->cluster_attributes_->present_value = state ? ZB_TRUE : ZB_FALSE; + ESP_LOGD(TAG, "Set attribute endpoint: %d, present_value %d", this->endpoint_, + this->cluster_attributes_->present_value); + ZB_ZCL_SET_ATTRIBUTE(this->endpoint_, ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID, &this->cluster_attributes_->present_value, + ZB_FALSE); + this->parent_->flush(); + }); +} + +void ZigbeeSwitch::zcl_device_cb_(zb_bufid_t bufid) { + zb_zcl_device_callback_param_t *p_device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); + zb_zcl_device_callback_id_t device_cb_id = p_device_cb_param->device_cb_id; + zb_uint16_t cluster_id = p_device_cb_param->cb_param.set_attr_value_param.cluster_id; + zb_uint16_t attr_id = p_device_cb_param->cb_param.set_attr_value_param.attr_id; + + p_device_cb_param->status = RET_OK; + + switch (device_cb_id) { + /* ZCL set attribute value */ + case ZB_ZCL_SET_ATTR_VALUE_CB_ID: + if (cluster_id == ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT) { + uint8_t value = p_device_cb_param->cb_param.set_attr_value_param.values.data8; + ESP_LOGI(TAG, "Binary output attribute setting to %hd", value); + if (attr_id == ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID) { + this->defer([this, value]() { + this->cluster_attributes_->present_value = value ? ZB_TRUE : ZB_FALSE; + this->switch_->publish_state(value); + }); + } + } else { + /* other clusters attribute handled here */ + ESP_LOGI(TAG, "Unhandled cluster attribute id: %d", cluster_id); + } + break; + default: + p_device_cb_param->status = RET_ERROR; + break; + } + + ESP_LOGD(TAG, "%s status: %hd", __func__, p_device_cb_param->status); +} + +const zb_uint8_t ZB_ZCL_BINARY_OUTPUT_STATUS_FLAG_MAX_VALUE = 0x0F; + +static zb_ret_t check_value_binary_output_server(zb_uint16_t attr_id, zb_uint8_t endpoint, + zb_uint8_t *value) { // NOLINT(readability-non-const-parameter) + zb_ret_t ret = RET_OK; + ZVUNUSED(endpoint); + + switch (attr_id) { + case ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID: + case ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID: + ret = ZB_ZCL_CHECK_BOOL_VALUE(*value) ? RET_OK : RET_ERROR; + break; + + case ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID: + if (*value > ZB_ZCL_BINARY_OUTPUT_STATUS_FLAG_MAX_VALUE) { + ret = RET_ERROR; + } + break; + + default: + break; + } + + return ret; +} + +} // namespace esphome::zigbee + +void zb_zcl_binary_output_init_server() { + zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ZB_ZCL_CLUSTER_SERVER_ROLE, + esphome::zigbee::check_value_binary_output_server, + (zb_zcl_cluster_write_attr_hook_t) NULL, (zb_zcl_cluster_handler_t) NULL); +} + +void zb_zcl_binary_output_init_client() { + zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, ZB_ZCL_CLUSTER_CLIENT_ROLE, + (zb_zcl_cluster_check_value_t) NULL, (zb_zcl_cluster_write_attr_hook_t) NULL, + (zb_zcl_cluster_handler_t) NULL); +} + +#endif diff --git a/esphome/components/zigbee/zigbee_switch_zephyr.h b/esphome/components/zigbee/zigbee_switch_zephyr.h new file mode 100644 index 0000000000..b774c23b3c --- /dev/null +++ b/esphome/components/zigbee/zigbee_switch_zephyr.h @@ -0,0 +1,83 @@ +#pragma once + +#include "esphome/components/zigbee/zigbee_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_SWITCH) +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +extern "C" { +#include +#include +} + +#define ZB_ZCL_BINARY_OUTPUT_CLUSTER_REVISION_DEFAULT ((zb_uint16_t) 0x0001u) + +// NOLINTNEXTLINE(readability-identifier-naming) +enum zb_zcl_binary_output_attr_e { + ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID = 0x001C, + ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID = 0x0051, + ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID = 0x0055, + ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID = 0x006F, +}; + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID, ZB_ZCL_ATTR_TYPE_BOOL, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_WRITE_OPTIONAL, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \ + (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID, ZB_ZCL_ATTR_TYPE_BOOL, \ + ZB_ZCL_ATTR_ACCESS_READ_WRITE | ZB_ZCL_ATTR_ACCESS_REPORTING, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \ + (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID, ZB_ZCL_ATTR_TYPE_8BITMAP, \ + ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING, (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), \ + (void *) (data_ptr) \ + } + +#define ZB_SET_ATTR_DESCR_WITH_ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID(data_ptr) \ + { \ + ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID, ZB_ZCL_ATTR_TYPE_CHAR_STRING, ZB_ZCL_ATTR_ACCESS_READ_ONLY, \ + (ZB_ZCL_NON_MANUFACTURER_SPECIFIC), (void *) (data_ptr) \ + } + +#define ESPHOME_ZB_ZCL_DECLARE_BINARY_OUTPUT_ATTRIB_LIST(attr_list, out_of_service, present_value, status_flag, \ + description) \ + ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, ZB_ZCL_BINARY_OUTPUT) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_OUT_OF_SERVICE_ID, (out_of_service)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_PRESENT_VALUE_ID, (present_value)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_STATUS_FLAG_ID, (status_flag)) \ + ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_BINARY_OUTPUT_DESCRIPTION_ID, (description)) \ + ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST + +void zb_zcl_binary_output_init_server(); +void zb_zcl_binary_output_init_client(); + +#define ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT_SERVER_ROLE_INIT zb_zcl_binary_output_init_server +#define ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT_CLIENT_ROLE_INIT zb_zcl_binary_output_init_client + +namespace esphome::zigbee { + +class ZigbeeSwitch : public ZigbeeEntity, public Component { + public: + ZigbeeSwitch(switch_::Switch *s) : switch_(s) {} + void set_cluster_attributes(BinaryAttrs &cluster_attributes) { this->cluster_attributes_ = &cluster_attributes; } + + void setup() override; + + void dump_config() override; + + protected: + void zcl_device_cb_(zb_bufid_t bufid); + + BinaryAttrs *cluster_attributes_{nullptr}; + switch_::Switch *switch_; +}; + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_zephyr.cpp b/esphome/components/zigbee/zigbee_zephyr.cpp new file mode 100644 index 0000000000..e43ab8f84d --- /dev/null +++ b/esphome/components/zigbee/zigbee_zephyr.cpp @@ -0,0 +1,252 @@ +#include "zigbee_zephyr.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) +#include "esphome/core/log.h" +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +namespace esphome::zigbee { + +static const char *const TAG = "zigbee"; + +ZigbeeComponent *global_zigbee = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +const uint8_t IEEE_ADDR_BUF_SIZE = 17; + +void ZigbeeComponent::zboss_signal_handler_esphome(zb_bufid_t bufid) { + zb_zdo_app_signal_hdr_t *sig_hndler = nullptr; + zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, &sig_hndler); + zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid); + + switch (sig) { + case ZB_ZDO_SIGNAL_SKIP_STARTUP: + ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_SKIP_STARTUP, status: %d", status); + break; + case ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY: + ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY, status: %d", status); + break; + case ZB_ZDO_SIGNAL_LEAVE: + ESP_LOGD(TAG, "ZB_ZDO_SIGNAL_LEAVE, status: %d", status); + break; + case ZB_BDB_SIGNAL_DEVICE_REBOOT: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_REBOOT, status: %d", status); + if (status == RET_OK) { + on_join_(); + } + break; + case ZB_BDB_SIGNAL_STEERING: + break; + case ZB_COMMON_SIGNAL_CAN_SLEEP: + ESP_LOGV(TAG, "ZB_COMMON_SIGNAL_CAN_SLEEP, status: %d", status); + break; + case ZB_BDB_SIGNAL_DEVICE_FIRST_START: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_DEVICE_FIRST_START, status: %d", status); + break; + case ZB_NLME_STATUS_INDICATION: + ESP_LOGD(TAG, "ZB_NLME_STATUS_INDICATION, status: %d", status); + break; + case ZB_BDB_SIGNAL_TC_REJOIN_DONE: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_TC_REJOIN_DONE, status: %d", status); + break; + default: + ESP_LOGD(TAG, "zboss_signal_handler sig: %d, status: %d", sig, status); + break; + } + + auto err = zigbee_default_signal_handler(bufid); + if (err != RET_OK) { + ESP_LOGE(TAG, "Zigbee_default_signal_handler ERROR %u [%s]", err, zb_error_to_string_get(err)); + } + + switch (sig) { + case ZB_BDB_SIGNAL_STEERING: + ESP_LOGD(TAG, "ZB_BDB_SIGNAL_STEERING, status: %d", status); + if (status == RET_OK) { + zb_ext_pan_id_t extended_pan_id; + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0}; + int addr_len; + + zb_get_extended_pan_id(extended_pan_id); + addr_len = ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), extended_pan_id); + + for (int i = 0; i < addr_len; ++i) { + if (ieee_addr_buf[i] != '0') { + on_join_(); + break; + } + } + } + break; + } + + /* All callbacks should either reuse or free passed buffers. + * If bufid == 0, the buffer is invalid (not passed). + */ + if (bufid) { + zb_buf_free(bufid); + } +} + +void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) { + zb_zcl_device_callback_param_t *p_device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t); + zb_zcl_device_callback_id_t device_cb_id = p_device_cb_param->device_cb_id; + zb_uint16_t cluster_id = p_device_cb_param->cb_param.set_attr_value_param.cluster_id; + zb_uint16_t attr_id = p_device_cb_param->cb_param.set_attr_value_param.attr_id; + auto endpoint = p_device_cb_param->endpoint; + + ESP_LOGI(TAG, "Zcl_device_cb %s id %hd, cluster_id %d, attr_id %d, endpoint: %d", __func__, device_cb_id, cluster_id, + attr_id, endpoint); + + /* Set default response value. */ + p_device_cb_param->status = RET_OK; + + // endpoints are enumerated from 1 + if (global_zigbee->callbacks_.size() >= endpoint) { + const auto &cb = global_zigbee->callbacks_[endpoint - 1]; + if (cb) { + cb(bufid); + } + return; + } + p_device_cb_param->status = RET_ERROR; +} + +void ZigbeeComponent::on_join_() { + this->defer([this]() { + ESP_LOGD(TAG, "Joined the network"); + this->join_trigger_.trigger(); + this->join_cb_.call(); + }); +} + +#ifdef USE_ZIGBEE_WIPE_ON_BOOT +void ZigbeeComponent::erase_flash_(int area) { + const struct flash_area *fap; + flash_area_open(area, &fap); + flash_area_erase(fap, 0, fap->fa_size); + flash_area_close(fap); +} +#endif + +void ZigbeeComponent::setup() { + global_zigbee = this; + auto err = settings_subsys_init(); + if (err) { + ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); + return; + } + +#ifdef USE_ZIGBEE_WIPE_ON_BOOT + bool wipe = true; +#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC + // unique hash to store preferences for this component + uint32_t hash = 88498616UL; + uint32_t wipe_value = 0; + auto wipe_pref = global_preferences->make_preference(hash, true); + if (wipe_pref.load(&wipe_value)) { + wipe = wipe_value != USE_ZIGBEE_WIPE_ON_BOOT_MAGIC; + ESP_LOGD(TAG, "Wipe value in preferences %u, in firmware %u", wipe_value, USE_ZIGBEE_WIPE_ON_BOOT_MAGIC); + } +#endif + if (wipe) { + erase_flash_(FIXED_PARTITION_ID(ZBOSS_NVRAM)); + erase_flash_(FIXED_PARTITION_ID(ZBOSS_PRODUCT_CONFIG)); + erase_flash_(FIXED_PARTITION_ID(SETTINGS_STORAGE)); +#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC + wipe_value = USE_ZIGBEE_WIPE_ON_BOOT_MAGIC; + wipe_pref.save(&wipe_value); +#endif + } +#endif + + ZB_ZCL_REGISTER_DEVICE_CB(zcl_device_cb); + err = settings_load(); + if (err) { + ESP_LOGE(TAG, "Cannot load settings, err: %d", err); + return; + } + zigbee_enable(); +} + +static const char *role() { + switch (zb_get_network_role()) { + case ZB_NWK_DEVICE_TYPE_COORDINATOR: + return "coordinator"; + case ZB_NWK_DEVICE_TYPE_ROUTER: + return "router"; + case ZB_NWK_DEVICE_TYPE_ED: + return "end device"; + } + return "unknown"; +} + +static const char *get_wipe_on_boot() { +#ifdef USE_ZIGBEE_WIPE_ON_BOOT +#ifdef USE_ZIGBEE_WIPE_ON_BOOT_MAGIC + return "ONCE"; +#else + return "YES"; +#endif +#else + return "NO"; +#endif +} + +void ZigbeeComponent::dump_config() { + char ieee_addr_buf[IEEE_ADDR_BUF_SIZE] = {0}; + zb_ieee_addr_t addr; + zb_get_long_address(addr); + ieee_addr_to_str(ieee_addr_buf, sizeof(ieee_addr_buf), addr); + zb_ext_pan_id_t extended_pan_id; + char extended_pan_id_buf[IEEE_ADDR_BUF_SIZE] = {0}; + zb_get_extended_pan_id(extended_pan_id); + ieee_addr_to_str(extended_pan_id_buf, sizeof(extended_pan_id_buf), extended_pan_id); + ESP_LOGCONFIG(TAG, + "Zigbee\n" + " Wipe on boot: %s\n" + " Device is joined to the network: %s\n" + " Current channel: %d\n" + " Current page: %d\n" + " Sleep threshold: %ums\n" + " Role: %s\n" + " Long addr: 0x%s\n" + " Short addr: 0x%04X\n" + " Long pan id: 0x%s\n" + " Short pan id: 0x%04X", + get_wipe_on_boot(), YESNO(zb_zdo_joined()), zb_get_current_channel(), zb_get_current_page(), + zb_get_sleep_threshold(), role(), ieee_addr_buf, zb_get_short_address(), extended_pan_id_buf, + zb_get_pan_id()); +} + +static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) { + ESP_LOGD(TAG, "Force zboss scheduler to wake and send attribute report"); + zb_buf_free(bufid); +} + +void ZigbeeComponent::flush() { this->need_flush_ = true; } + +void ZigbeeComponent::loop() { + if (this->need_flush_) { + this->need_flush_ = false; + zb_buf_get_out_delayed_ext(send_attribute_report, 0, 0); + } +} + +void ZigbeeComponent::factory_reset() { + ESP_LOGD(TAG, "Factory reset"); + ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0); +} + +} // namespace esphome::zigbee + +extern "C" void zboss_signal_handler(zb_uint8_t param) { + esphome::zigbee::global_zigbee->zboss_signal_handler_esphome(param); +} +#endif diff --git a/esphome/components/zigbee/zigbee_zephyr.h b/esphome/components/zigbee/zigbee_zephyr.h new file mode 100644 index 0000000000..d5f1257f9c --- /dev/null +++ b/esphome/components/zigbee/zigbee_zephyr.h @@ -0,0 +1,101 @@ +#pragma once +#include "esphome/core/defines.h" +#if defined(USE_ZIGBEE) && defined(USE_NRF52) +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +extern "C" { +#include +#include +} + +// copy of ZB_DECLARE_SIMPLE_DESC. Due to https://github.com/nrfconnect/sdk-nrfxlib/pull/666 +#define ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clusters_count, out_clusters_count) \ + typedef ZB_PACKED_PRE struct zb_af_simple_desc_##ep_name##_##in_clusters_count##_##out_clusters_count##_s { \ + zb_uint8_t endpoint; /* Endpoint */ \ + zb_uint16_t app_profile_id; /* Application profile identifier */ \ + zb_uint16_t app_device_id; /* Application device identifier */ \ + zb_bitfield_t app_device_version : 4; /* Application device version */ \ + zb_bitfield_t reserved : 4; /* Reserved */ \ + zb_uint8_t app_input_cluster_count; /* Application input cluster count */ \ + zb_uint8_t app_output_cluster_count; /* Application output cluster count */ \ + /* Application input and output cluster list */ \ + zb_uint16_t app_cluster_list[(in_clusters_count) + (out_clusters_count)]; \ + } ZB_PACKED_STRUCT zb_af_simple_desc_##ep_name##_##in_clusters_count##_##out_clusters_count##_t + +#define ESPHOME_CAT7(a, b, c, d, e, f, g) a##b##c##d##e##f##g +// needed to use ESPHOME_ZB_DECLARE_SIMPLE_DESC +#define ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_num, out_num) \ + ESPHOME_CAT7(zb_af_simple_desc_, ep_name, _, in_num, _, out_num, _t) + +// needed to use ESPHOME_ZB_DECLARE_SIMPLE_DESC +#define ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num, app_device_id, ...) \ + ESPHOME_ZB_DECLARE_SIMPLE_DESC(ep_name, in_clust_num, out_clust_num); \ + ESPHOME_ZB_AF_SIMPLE_DESC_TYPE(ep_name, in_clust_num, out_clust_num) \ + simple_desc_##ep_name = {ep_id, ZB_AF_HA_PROFILE_ID, app_device_id, 0, 0, in_clust_num, out_clust_num, {__VA_ARGS__}} + +// needed to use ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC +#define ESPHOME_ZB_HA_DECLARE_EP(ep_name, ep_id, cluster_list, in_cluster_num, out_cluster_num, report_attr_count, \ + app_device_id, ...) \ + ESPHOME_ZB_ZCL_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_cluster_num, out_cluster_num, app_device_id, __VA_ARGS__); \ + ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_info##ep_name, report_attr_count); \ + ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, 0, NULL, \ + ZB_ZCL_ARRAY_SIZE(cluster_list, zb_zcl_cluster_desc_t), cluster_list, \ + (zb_af_simple_desc_1_1_t *) &simple_desc_##ep_name, report_attr_count, \ + reporting_info##ep_name, 0, NULL) + +namespace esphome::zigbee { + +struct BinaryAttrs { + zb_bool_t out_of_service; + zb_bool_t present_value; + zb_uint8_t status_flags; + zb_uchar_t description[ZB_ZCL_MAX_STRING_SIZE]; +}; + +struct AnalogAttrs { + zb_bool_t out_of_service; + float present_value; + zb_uint8_t status_flags; + zb_uint16_t engineering_units; + zb_uchar_t description[ZB_ZCL_MAX_STRING_SIZE]; +}; + +class ZigbeeComponent : public Component { + public: + void setup() override; + void dump_config() override; + void add_callback(zb_uint8_t endpoint, std::function &&cb) { + // endpoints are enumerated from 1 + this->callbacks_[endpoint - 1] = std::move(cb); + } + void add_join_callback(std::function &&cb) { this->join_cb_.add(std::move(cb)); } + void zboss_signal_handler_esphome(zb_bufid_t bufid); + void factory_reset(); + Trigger<> *get_join_trigger() { return &this->join_trigger_; }; + void flush(); + void loop() override; + + protected: + static void zcl_device_cb(zb_bufid_t bufid); + void on_join_(); +#ifdef USE_ZIGBEE_WIPE_ON_BOOT + void erase_flash_(int area); +#endif + std::array, ZIGBEE_ENDPOINTS_COUNT> callbacks_{}; + CallbackManager join_cb_; + Trigger<> join_trigger_; + bool need_flush_{false}; +}; + +class ZigbeeEntity { + public: + void set_parent(ZigbeeComponent *parent) { this->parent_ = parent; } + void set_endpoint(zb_uint8_t endpoint) { this->endpoint_ = endpoint; } + + protected: + zb_uint8_t endpoint_{0}; + ZigbeeComponent *parent_{nullptr}; +}; + +} // namespace esphome::zigbee +#endif diff --git a/esphome/components/zigbee/zigbee_zephyr.py b/esphome/components/zigbee/zigbee_zephyr.py new file mode 100644 index 0000000000..7f1f7dc57f --- /dev/null +++ b/esphome/components/zigbee/zigbee_zephyr.py @@ -0,0 +1,445 @@ +from datetime import datetime +import random + +from esphome import automation +import esphome.codegen as cg +from esphome.components.zephyr import zephyr_add_prj_conf +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_CENTIMETER, + UNIT_DECIBEL, + UNIT_HECTOPASCAL, + UNIT_HERTZ, + UNIT_HOUR, + UNIT_KELVIN, + UNIT_KILOMETER, + UNIT_KILOWATT, + UNIT_KILOWATT_HOURS, + UNIT_LUX, + UNIT_METER, + UNIT_MICROGRAMS_PER_CUBIC_METER, + UNIT_MILLIAMP, + UNIT_MILLIGRAMS_PER_CUBIC_METER, + UNIT_MILLIMETER, + UNIT_MILLISECOND, + UNIT_MILLIVOLT, + UNIT_MINUTE, + UNIT_OHM, + UNIT_PARTS_PER_BILLION, + UNIT_PARTS_PER_MILLION, + UNIT_PASCAL, + UNIT_PERCENT, + UNIT_SECOND, + UNIT_VOLT, + UNIT_WATT, + UNIT_WATT_HOURS, + __version__, +) +from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.cpp_generator import ( + AssignmentExpression, + MockObj, + VariableDeclarationExpression, +) +from esphome.types import ConfigType + +from .const_zephyr import ( + CONF_ON_JOIN, + CONF_POWER_SOURCE, + CONF_WIPE_ON_BOOT, + CONF_ZIGBEE_BINARY_SENSOR, + CONF_ZIGBEE_ID, + CONF_ZIGBEE_SENSOR, + CONF_ZIGBEE_SWITCH, + KEY_EP_NUMBER, + KEY_ZIGBEE, + POWER_SOURCE, + ZB_ZCL_BASIC_ATTRS_EXT_T, + ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, + ZB_ZCL_CLUSTER_ID_BASIC, + ZB_ZCL_CLUSTER_ID_BINARY_INPUT, + ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, + ZB_ZCL_CLUSTER_ID_IDENTIFY, + ZB_ZCL_IDENTIFY_ATTRS_T, + AnalogAttrs, + BinaryAttrs, + ZigbeeComponent, + zigbee_ns, +) + +ZigbeeBinarySensor = zigbee_ns.class_("ZigbeeBinarySensor", cg.Component) +ZigbeeSensor = zigbee_ns.class_("ZigbeeSensor", cg.Component) +ZigbeeSwitch = zigbee_ns.class_("ZigbeeSwitch", cg.Component) + +# BACnet engineering units mapping (ZCL uses BACnet unit codes) +# See: https://github.com/zigpy/zha/blob/dev/zha/application/platforms/number/bacnet.py +BACNET_UNITS = { + UNIT_CELSIUS: 62, + UNIT_KELVIN: 63, + UNIT_VOLT: 5, + UNIT_MILLIVOLT: 124, + UNIT_AMPERE: 3, + UNIT_MILLIAMP: 2, + UNIT_OHM: 4, + UNIT_WATT: 47, + UNIT_KILOWATT: 48, + UNIT_WATT_HOURS: 18, + UNIT_KILOWATT_HOURS: 19, + UNIT_PASCAL: 53, + UNIT_HECTOPASCAL: 133, + UNIT_HERTZ: 27, + UNIT_MILLIMETER: 30, + UNIT_CENTIMETER: 118, + UNIT_METER: 31, + UNIT_KILOMETER: 193, + UNIT_MILLISECOND: 159, + UNIT_SECOND: 73, + UNIT_MINUTE: 72, + UNIT_HOUR: 71, + UNIT_PARTS_PER_MILLION: 96, + UNIT_PARTS_PER_BILLION: 97, + UNIT_MICROGRAMS_PER_CUBIC_METER: 219, + UNIT_MILLIGRAMS_PER_CUBIC_METER: 218, + UNIT_LUX: 37, + UNIT_DECIBEL: 199, + UNIT_PERCENT: 98, +} +BACNET_UNIT_NO_UNITS = 95 + +zephyr_binary_sensor = cv.Schema( + { + cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent), + cv.OnlyWith(CONF_ZIGBEE_BINARY_SENSOR, ["nrf52", "zigbee"]): cv.declare_id( + ZigbeeBinarySensor + ), + } +) + +zephyr_sensor = cv.Schema( + { + cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent), + cv.OnlyWith(CONF_ZIGBEE_SENSOR, ["nrf52", "zigbee"]): cv.declare_id( + ZigbeeSensor + ), + } +) + +zephyr_switch = cv.Schema( + { + cv.OnlyWith(CONF_ZIGBEE_ID, ["nrf52", "zigbee"]): cv.use_id(ZigbeeComponent), + cv.OnlyWith(CONF_ZIGBEE_SWITCH, ["nrf52", "zigbee"]): cv.declare_id( + ZigbeeSwitch + ), + } +) + + +async def zephyr_to_code(config: ConfigType) -> None: + zephyr_add_prj_conf("ZIGBEE", True) + zephyr_add_prj_conf("ZIGBEE_APP_UTILS", True) + zephyr_add_prj_conf("ZIGBEE_ROLE_END_DEVICE", True) + + zephyr_add_prj_conf("ZIGBEE_CHANNEL_SELECTION_MODE_MULTI", True) + + zephyr_add_prj_conf("CRYPTO", True) + + zephyr_add_prj_conf("NET_IPV6", False) + zephyr_add_prj_conf("NET_IP_ADDR_CHECK", False) + zephyr_add_prj_conf("NET_UDP", False) + + if config[CONF_WIPE_ON_BOOT]: + if config[CONF_WIPE_ON_BOOT] == "once": + cg.add_define( + "USE_ZIGBEE_WIPE_ON_BOOT_MAGIC", random.randint(0x000001, 0xFFFFFF) + ) + cg.add_define("USE_ZIGBEE_WIPE_ON_BOOT") + var = cg.new_Pvariable(config[CONF_ID]) + + if on_join_config := config.get(CONF_ON_JOIN): + await automation.build_automation(var.get_join_trigger(), [], on_join_config) + + await cg.register_component(var, config) + + await _attr_to_code(config) + CORE.add_job(_ctx_to_code, config) + + +async def _attr_to_code(config: ConfigType) -> None: + # Create the basic attributes structure and attribute list + basic_attrs = zigbee_new_variable("zigbee_basic_attrs", ZB_ZCL_BASIC_ATTRS_EXT_T) + zigbee_new_attr_list( + "zigbee_basic_attrib_list", + "ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT", + zigbee_assign(basic_attrs.zcl_version, cg.RawExpression("ZB_ZCL_VERSION")), + zigbee_assign(basic_attrs.app_version, 0), + zigbee_assign(basic_attrs.stack_version, 0), + zigbee_assign(basic_attrs.hw_version, 0), + zigbee_set_string(basic_attrs.mf_name, "esphome"), + zigbee_set_string(basic_attrs.model_id, CORE.name), + zigbee_set_string( + basic_attrs.date_code, datetime.now().strftime("%d/%m/%y %H:%M") + ), + zigbee_assign( + basic_attrs.power_source, + cg.RawExpression(POWER_SOURCE[config[CONF_POWER_SOURCE]]), + ), + zigbee_set_string(basic_attrs.location_id, ""), + zigbee_assign( + basic_attrs.ph_env, cg.RawExpression("ZB_ZCL_BASIC_ENV_UNSPECIFIED") + ), + zigbee_set_string(basic_attrs.sw_ver, __version__), + ) + + # Create the identify attributes structure and attribute list + identify_attrs = zigbee_new_variable( + "zigbee_identify_attrs", ZB_ZCL_IDENTIFY_ATTRS_T + ) + zigbee_new_attr_list( + "zigbee_identify_attrib_list", + "ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST", + zigbee_assign( + identify_attrs.identify_time, + cg.RawExpression("ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE"), + ), + ) + + +def zigbee_new_variable(name: str, type_: str) -> cg.MockObj: + """Create a global variable with the given name and type.""" + decl = VariableDeclarationExpression(type_, "", name) + CORE.add_global(decl) + return MockObj(name, ".") + + +def zigbee_assign(target: cg.MockObj, expression: cg.RawExpression | int) -> str: + """Assign an expression to a target and return a reference to it.""" + cg.add(AssignmentExpression("", "", target, expression)) + return f"&{target}" + + +def zigbee_set_string(target: cg.MockObj, value: str) -> str: + """Set a ZCL string value and return the target name (arrays decay to pointers).""" + # Zigbee supports only ASCII + value = value.encode("ascii", "ignore").decode() + cg.add( + cg.RawExpression( + f"ZB_ZCL_SET_STRING_VAL({target}, {cg.safe_exp(value)}, ZB_ZCL_STRING_CONST_SIZE({cg.safe_exp(value)}))" + ) + ) + return str(target) + + +def zigbee_new_attr_list(name: str, macro: str, *args: str) -> str: + """Create an attribute list using a ZBOSS macro and return the name.""" + obj = cg.RawExpression(f"{macro}({name}, {', '.join(args)})") + CORE.add_global(obj) + return name + + +class ZigbeeClusterDesc: + """Represents a Zigbee cluster descriptor for code generation.""" + + def __init__(self, cluster_id: str, attr_list_name: str | None = None) -> None: + self._cluster_id = cluster_id + self._attr_list_name = attr_list_name + + @property + def cluster_id(self) -> str: + return self._cluster_id + + @property + def has_attrs(self) -> bool: + return self._attr_list_name is not None + + def __str__(self) -> str: + role = ( + "ZB_ZCL_CLUSTER_SERVER_ROLE" + if self._attr_list_name + else "ZB_ZCL_CLUSTER_CLIENT_ROLE" + ) + if self._attr_list_name: + attr_count = f"ZB_ZCL_ARRAY_SIZE({self._attr_list_name}, zb_zcl_attr_t)" + return f"ZB_ZCL_CLUSTER_DESC({self._cluster_id}, {attr_count}, {self._attr_list_name}, {role}, ZB_ZCL_MANUF_CODE_INVALID)" + return f"ZB_ZCL_CLUSTER_DESC({self._cluster_id}, 0, NULL, {role}, ZB_ZCL_MANUF_CODE_INVALID)" + + +def zigbee_new_cluster_list( + name: str, clusters: list[ZigbeeClusterDesc] +) -> tuple[str, list[ZigbeeClusterDesc]]: + """Create a cluster list array and return its name and the clusters.""" + # Always include basic and identify clusters first + all_clusters = [ + ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_BASIC, "zigbee_basic_attrib_list"), + ZigbeeClusterDesc(ZB_ZCL_CLUSTER_ID_IDENTIFY, "zigbee_identify_attrib_list"), + ] + all_clusters.extend(clusters) + + cluster_strs = [str(c) for c in all_clusters] + CORE.add_global( + cg.RawExpression( + f"zb_zcl_cluster_desc_t {name}[] = {{{', '.join(cluster_strs)}}}" + ) + ) + return (name, all_clusters) + + +def zigbee_register_ep( + ep_name: str, + cluster_list_name: str, + report_attr_count: int, + clusters: list[ZigbeeClusterDesc], + slot_index: int, + app_device_id: str, +) -> None: + """Register a Zigbee endpoint.""" + in_cluster_num = sum(1 for c in clusters if c.has_attrs) + out_cluster_num = len(clusters) - in_cluster_num + cluster_ids = [c.cluster_id for c in clusters] + + # Store endpoint name for device context generation + CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER][slot_index] = ep_name + + # Generate the endpoint declaration + ep_id = slot_index + 1 # Endpoints are 1-indexed + obj = cg.RawExpression( + f"ESPHOME_ZB_HA_DECLARE_EP({ep_name}, {ep_id}, {cluster_list_name}, " + f"{in_cluster_num}, {out_cluster_num}, {report_attr_count}, {app_device_id}, {', '.join(cluster_ids)})" + ) + CORE.add_global(obj) + + +@coroutine_with_priority(CoroPriority.LATE) +async def _ctx_to_code(config: ConfigType) -> None: + cg.add_define("ZIGBEE_ENDPOINTS_COUNT", len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])) + cg.add_global( + cg.RawExpression( + f"ZBOSS_DECLARE_DEVICE_CTX_EP_VA(zb_device_ctx, &{', &'.join(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])})" + ) + ) + cg.add(cg.RawExpression("ZB_AF_REGISTER_DEVICE_CTX(&zb_device_ctx)")) + + +async def zephyr_setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: + CORE.add_job(_add_binary_sensor, entity, config) + + +async def zephyr_setup_sensor(entity: cg.MockObj, config: ConfigType) -> None: + CORE.add_job(_add_sensor, entity, config) + + +async def zephyr_setup_switch(entity: cg.MockObj, config: ConfigType) -> None: + CORE.add_job(_add_switch, entity, config) + + +def _slot_index() -> int: + """Find the next available endpoint slot""" + slot = next( + (i for i, v in enumerate(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER]) if v == ""), None + ) + if slot is None: + raise cv.Invalid( + f"Not found empty slot, size ({len(CORE.data[KEY_ZIGBEE][KEY_EP_NUMBER])})" + ) + return slot + + +async def _add_zigbee_ep( + entity: cg.MockObj, + config: ConfigType, + component_key, + attrs_type, + zcl_macro: str, + cluster_id: str, + app_device_id: str, + extra_field_values: dict[str, int] | None = None, +) -> None: + slot_index = _slot_index() + + prefix = f"zigbee_ep{slot_index + 1}" + attrs_name = f"{prefix}_attrs" + attr_list_name = f"{prefix}_attrib_list" + cluster_list_name = f"{prefix}_cluster_list" + ep_name = f"{prefix}_ep" + + # Create attribute struct + attrs = zigbee_new_variable(attrs_name, attrs_type) + + # Build attribute list args + attr_args = [ + zigbee_assign(attrs.out_of_service, 0), + zigbee_assign(attrs.present_value, 0), + zigbee_assign(attrs.status_flags, 0), + ] + # Add extra field assignments (e.g., engineering_units for sensors) + if extra_field_values: + for field_name, value in extra_field_values.items(): + attr_args.append(zigbee_assign(getattr(attrs, field_name), value)) + attr_args.append(zigbee_set_string(attrs.description, config[CONF_NAME])) + + # Create attribute list + attr_list = zigbee_new_attr_list(attr_list_name, zcl_macro, *attr_args) + + # Create cluster list and register endpoint + cluster_list_name, clusters = zigbee_new_cluster_list( + cluster_list_name, + [ZigbeeClusterDesc(cluster_id, attr_list)], + ) + zigbee_register_ep( + ep_name, cluster_list_name, 2, clusters, slot_index, app_device_id + ) + + # Create ESPHome component + var = cg.new_Pvariable(config[component_key], entity) + await cg.register_component(var, {}) + + cg.add(var.set_endpoint(slot_index + 1)) + cg.add(var.set_cluster_attributes(attrs)) + + hub = await cg.get_variable(config[CONF_ZIGBEE_ID]) + cg.add(var.set_parent(hub)) + + +async def _add_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: + await _add_zigbee_ep( + entity, + config, + CONF_ZIGBEE_BINARY_SENSOR, + BinaryAttrs, + "ESPHOME_ZB_ZCL_DECLARE_BINARY_INPUT_ATTRIB_LIST", + ZB_ZCL_CLUSTER_ID_BINARY_INPUT, + "ZB_HA_SIMPLE_SENSOR_DEVICE_ID", + ) + + +async def _add_sensor(entity: cg.MockObj, config: ConfigType) -> None: + # Get BACnet engineering unit from unit_of_measurement + unit = config.get(CONF_UNIT_OF_MEASUREMENT, "") + bacnet_unit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS) + + await _add_zigbee_ep( + entity, + config, + CONF_ZIGBEE_SENSOR, + AnalogAttrs, + "ESPHOME_ZB_ZCL_DECLARE_ANALOG_INPUT_ATTRIB_LIST", + ZB_ZCL_CLUSTER_ID_ANALOG_INPUT, + "ZB_HA_CUSTOM_ATTR_DEVICE_ID", + extra_field_values={"engineering_units": bacnet_unit}, + ) + + +async def _add_switch(entity: cg.MockObj, config: ConfigType) -> None: + await _add_zigbee_ep( + entity, + config, + CONF_ZIGBEE_SWITCH, + BinaryAttrs, + "ESPHOME_ZB_ZCL_DECLARE_BINARY_OUTPUT_ATTRIB_LIST", + ZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, + "ZB_HA_CUSTOM_ATTR_DEVICE_ID", + ) diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index e0ca5529b8..8506b19e7f 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -1,4 +1,7 @@ #include "zwave_proxy.h" + +#ifdef USE_API + #include "esphome/components/api/api_server.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" @@ -9,6 +12,9 @@ namespace esphome::zwave_proxy { static const char *const TAG = "zwave_proxy"; +// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512) +static constexpr size_t ZWAVE_MAX_LOG_BYTES = 168; + static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20; // GET_NETWORK_IDS response: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...] static constexpr uint8_t ZWAVE_COMMAND_TYPE_RESPONSE = 0x01; // Response type field value @@ -99,7 +105,7 @@ void ZWaveProxy::process_uart_() { this->buffer_[1] >= ZWAVE_MIN_GET_NETWORK_IDS_LENGTH && this->buffer_[0] == ZWAVE_FRAME_TYPE_START) { // Store the 4-byte Home ID, which starts at offset 4, and notify connected clients if it changed // The frame parser has already validated the checksum and ensured all bytes are present - if (this->set_home_id(&this->buffer_[4])) { + if (this->set_home_id_(&this->buffer_[4])) { this->send_homeid_changed_msg_(); } } @@ -120,10 +126,11 @@ void ZWaveProxy::process_uart_() { } void ZWaveProxy::dump_config() { + char hex_buf[format_hex_pretty_size(ZWAVE_HOME_ID_SIZE)]; ESP_LOGCONFIG(TAG, "Z-Wave Proxy:\n" " Home ID: %s", - format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str()); + format_hex_pretty_to(hex_buf, this->home_id_.data(), this->home_id_.size())); } void ZWaveProxy::api_connection_authenticated(api::APIConnection *conn) { @@ -158,23 +165,40 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en } } -bool ZWaveProxy::set_home_id(const uint8_t *new_home_id) { +bool ZWaveProxy::set_home_id_(const uint8_t *new_home_id) { if (std::memcmp(this->home_id_.data(), new_home_id, this->home_id_.size()) == 0) { ESP_LOGV(TAG, "Home ID unchanged"); return false; // No change } std::memcpy(this->home_id_.data(), new_home_id, this->home_id_.size()); - ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str()); + char hex_buf[format_hex_pretty_size(ZWAVE_HOME_ID_SIZE)]; + ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty_to(hex_buf, this->home_id_.data(), this->home_id_.size())); this->home_id_ready_ = true; return true; // Home ID was changed } void ZWaveProxy::send_frame(const uint8_t *data, size_t length) { - if (length == 1 && data[0] == this->last_response_) { - ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]); + // Safety: validate pointer before any access + if (data == nullptr) { + ESP_LOGE(TAG, "Null data pointer"); return; } - ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty(data, length).c_str()); + if (length == 0) { + ESP_LOGE(TAG, "Length 0"); + return; + } + + // Skip duplicate single-byte responses (ACK/NAK/CAN) + if (length == 1 && data[0] == this->last_response_) { + ESP_LOGV(TAG, "Response already sent: 0x%02X", data[0]); + return; + } + +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty_to(hex_buf, data, length)); + this->write_array(data, length); } @@ -247,7 +271,10 @@ bool ZWaveProxy::parse_byte_(uint8_t byte) { this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK; } else { this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_ACK; - ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_.data(), this->buffer_index_).c_str()); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)]; +#endif + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_index_)); frame_completed = true; } this->response_handler_(); @@ -344,3 +371,5 @@ bool ZWaveProxy::response_handler_() { ZWaveProxy *global_zwave_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome::zwave_proxy + +#endif // USE_API diff --git a/esphome/components/zwave_proxy/zwave_proxy.h b/esphome/components/zwave_proxy/zwave_proxy.h index e23e202bea..eb26316f49 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.h +++ b/esphome/components/zwave_proxy/zwave_proxy.h @@ -1,5 +1,8 @@ #pragma once +#include "esphome/core/defines.h" +#ifdef USE_API + #include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_pb2.h" #include "esphome/core/component.h" @@ -11,6 +14,7 @@ namespace esphome::zwave_proxy { static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size +static constexpr size_t ZWAVE_HOME_ID_SIZE = 4; // Z-Wave Home ID size in bytes enum ZWaveResponseTypes : uint8_t { ZWAVE_FRAME_TYPE_ACK = 0x06, @@ -56,11 +60,11 @@ class ZWaveProxy : public uart::UARTDevice, public Component { uint32_t get_home_id() { return encode_uint32(this->home_id_[0], this->home_id_[1], this->home_id_[2], this->home_id_[3]); } - bool set_home_id(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed. void send_frame(const uint8_t *data, size_t length); protected: + bool set_home_id_(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed. void send_homeid_changed_msg_(api::APIConnection *conn = nullptr); void send_simple_command_(uint8_t command_id); bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer) @@ -70,8 +74,8 @@ class ZWaveProxy : public uart::UARTDevice, public Component { // Pre-allocated message - always ready to send api::ZWaveProxyFrame outgoing_proto_msg_; - std::array buffer_; // Fixed buffer for incoming data - std::array home_id_{0, 0, 0, 0}; // Fixed buffer for home ID + std::array buffer_; // Fixed buffer for incoming data + std::array home_id_{}; // Fixed buffer for home ID // Pointers and 32-bit values (aligned together) api::APIConnection *api_connection_{nullptr}; // Current subscribed client @@ -89,3 +93,5 @@ class ZWaveProxy : public uart::UARTDevice, public Component { extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome::zwave_proxy + +#endif // USE_API diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 08fffa6cec..8e2fadbea8 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -697,7 +697,16 @@ only_on_esp32 = only_on(PLATFORM_ESP32) only_on_esp8266 = only_on(PLATFORM_ESP8266) only_on_rp2040 = only_on(PLATFORM_RP2040) only_with_arduino = only_with_framework(Framework.ARDUINO) -only_with_esp_idf = only_with_framework(Framework.ESP_IDF) + + +def only_with_esp_idf(obj): + """Deprecated: use only_on_esp32 instead.""" + _LOGGER.warning( + "cv.only_with_esp_idf was deprecated in 2026.1, will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + "Use cv.only_on_esp32 and/or cv.only_with_arduino instead." + ) + return only_with_framework(Framework.ESP_IDF)(obj) # Adapted from: @@ -1957,7 +1966,9 @@ MQTT_COMPONENT_SCHEMA = Schema( Optional(CONF_RETAIN): All(requires_component("mqtt"), boolean), Optional(CONF_DISCOVERY): All(requires_component("mqtt"), boolean), Optional(CONF_SUBSCRIBE_QOS): All(requires_component("mqtt"), mqtt_qos), - Optional(CONF_STATE_TOPIC): All(requires_component("mqtt"), publish_topic), + Optional(CONF_STATE_TOPIC): All( + requires_component("mqtt"), templatable(publish_topic) + ), Optional(CONF_AVAILABILITY): All( requires_component("mqtt"), Any(None, MQTT_COMPONENT_AVAILABILITY_SCHEMA) ), @@ -1966,12 +1977,49 @@ MQTT_COMPONENT_SCHEMA = Schema( MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( { - Optional(CONF_COMMAND_TOPIC): All(requires_component("mqtt"), subscribe_topic), + Optional(CONF_COMMAND_TOPIC): All( + requires_component("mqtt"), templatable(subscribe_topic) + ), Optional(CONF_COMMAND_RETAIN): All(requires_component("mqtt"), boolean), } ) +# Unicode FRACTION SLASH (U+2044) - visually similar to '/' but URL-safe +FRACTION_SLASH = "\u2044" + + +def _validate_no_slash(value): + """Validate that a name does not contain '/' characters. + + The '/' character is used as a path separator in web server URLs, + so it cannot be used in entity or device names. + + During the deprecation period, '/' is automatically replaced with + the visually similar Unicode FRACTION SLASH (U+2044) character. + """ + if "/" in value: + # Remove before 2026.7.0 + new_value = value.replace("/", FRACTION_SLASH) + _LOGGER.warning( + "'%s' contains '/' which is reserved as a URL path separator. " + "Automatically replacing with '%s' (Unicode FRACTION SLASH). " + "Please update your configuration. " + "This will become an error in ESPHome 2026.7.0.", + value, + new_value, + ) + return new_value + return value + + +# Maximum length for entity, device, and area names +# This ensures web server URL IDs fit in a 280-byte buffer: +# domain(20) + "/" + device(120) + "/" + name(120) + null = 263 bytes +# Note: Must be < 255 because web_server UrlMatch uses uint8_t for length fields +NAME_MAX_LENGTH = 120 + + def _validate_entity_name(value): value = string(value) try: @@ -1982,9 +2030,28 @@ def _validate_entity_name(value): requires_friendly_name( "Name cannot be None when esphome->friendly_name is not set!" )(value) + if value is not None: + # Validate length for web server URL compatibility + if len(value) > NAME_MAX_LENGTH: + raise Invalid( + f"Name is too long ({len(value)} chars). " + f"Maximum length is {NAME_MAX_LENGTH} characters." + ) + # Validate no '/' in name for web server URL compatibility + value = _validate_no_slash(value) return value +def string_no_slash(value): + """Validate a string that cannot contain '/' characters. + + Used for device and area names where '/' is reserved as a URL path separator. + Use with cv.Length() to also enforce maximum length. + """ + value = string(value) + return _validate_no_slash(value) + + ENTITY_BASE_SCHEMA = Schema( { Optional(CONF_NAME): _validate_entity_name, diff --git a/esphome/const.py b/esphome/const.py index 6771b9f265..95ccfb9dee 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.12.6" +__version__ = "2026.1.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( @@ -123,6 +123,7 @@ CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" CONF_ADVANCED = "advanced" CONF_AFTER = "after" +CONF_ALGORITHM_TUNING = "algorithm_tuning" CONF_ALL = "all" CONF_ALLOW_OTHER_USES = "allow_other_uses" CONF_ALPHA = "alpha" @@ -246,6 +247,7 @@ CONF_COMPENSATION = "compensation" CONF_COMPILE_PROCESS_LIMIT = "compile_process_limit" CONF_COMPONENT_ID = "component_id" CONF_COMPONENTS = "components" +CONF_COMPRESSION = "compression" CONF_CONDITION = "condition" CONF_CONDITION_ID = "condition_id" CONF_CONDUCTIVITY = "conductivity" @@ -435,6 +437,7 @@ CONF_GAIN_FACTOR = "gain_factor" CONF_GAMMA_CORRECT = "gamma_correct" CONF_GAS_RESISTANCE = "gas_resistance" CONF_GATEWAY = "gateway" +CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" CONF_GLASS_ATTENUATION_FACTOR = "glass_attenuation_factor" CONF_GLYPHS = "glyphs" CONF_GPIO = "gpio" @@ -497,6 +500,7 @@ CONF_INCLUDE_INTERNAL = "include_internal" CONF_INCLUDES = "includes" CONF_INCLUDES_C = "includes_c" CONF_INDEX = "index" +CONF_INDEX_OFFSET = "index_offset" CONF_INDOOR = "indoor" CONF_INFRARED = "infrared" CONF_INIT_SEQUENCE = "init_sequence" @@ -534,6 +538,8 @@ CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" CONF_LATITUDE = "latitude" +CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" +CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" CONF_LED = "led" CONF_LEGEND = "legend" CONF_LENGTH = "length" @@ -645,7 +651,9 @@ CONF_NEVER = "never" CONF_NEW_PASSWORD = "new_password" CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide" CONF_NOISE_LEVEL = "noise_level" +CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" CONF_NOTIFY = "notify" +CONF_NOX = "nox" CONF_NUM_ATTEMPTS = "num_attempts" CONF_NUM_CHANNELS = "num_channels" CONF_NUM_CHIPS = "num_chips" @@ -672,6 +680,7 @@ CONF_ON_CLIENT_CONNECTED = "on_client_connected" CONF_ON_CLIENT_DISCONNECTED = "on_client_disconnected" CONF_ON_CONNECT = "on_connect" CONF_ON_CONTROL = "on_control" +CONF_ON_DATA = "on_data" CONF_ON_DIRECTION_SET = "on_direction_set" CONF_ON_DISCONNECT = "on_disconnect" CONF_ON_DOUBLE_CLICK = "on_double_click" @@ -702,6 +711,7 @@ CONF_ON_RELEASE = "on_release" CONF_ON_RESPONSE = "on_response" CONF_ON_SHUTDOWN = "on_shutdown" CONF_ON_SPEED_SET = "on_speed_set" +CONF_ON_START = "on_start" CONF_ON_STATE = "on_state" CONF_ON_SUCCESS = "on_success" CONF_ON_TAG = "on_tag" @@ -939,6 +949,7 @@ CONF_STATE_TOPIC = "state_topic" CONF_STATIC_IP = "static_ip" CONF_STATUS = "status" CONF_STB_PIN = "stb_pin" +CONF_STD_INITIAL = "std_initial" CONF_STEP = "step" CONF_STEP_DELAY = "step_delay" CONF_STEP_MODE = "step_mode" @@ -1006,6 +1017,7 @@ CONF_TILT_COMMAND_TOPIC = "tilt_command_topic" CONF_TILT_LAMBDA = "tilt_lambda" CONF_TILT_STATE_TOPIC = "tilt_state_topic" CONF_TIME = "time" +CONF_TIME_CONSTANT = "time_constant" CONF_TIME_ID = "time_id" CONF_TIMEOUT = "timeout" CONF_TIMES = "times" @@ -1060,6 +1072,8 @@ CONF_VERSION = "version" CONF_VIBRATIONS = "vibrations" CONF_VISIBLE = "visible" CONF_VISUAL = "visual" +CONF_VOC = "voc" +CONF_VOC_BASELINE = "voc_baseline" CONF_VOLTAGE = "voltage" CONF_VOLTAGE_ATTENUATION = "voltage_attenuation" CONF_VOLTAGE_DIVIDER = "voltage_divider" @@ -1074,6 +1088,7 @@ CONF_WARM_WHITE = "warm_white" CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature" CONF_WATCHDOG_THRESHOLD = "watchdog_threshold" CONF_WATCHDOG_TIMEOUT = "watchdog_timeout" +CONF_WATER_HEATER = "water_heater" CONF_WEB_SERVER = "web_server" CONF_WEB_SERVER_ID = "web_server_id" CONF_WEIGHT = "weight" @@ -1167,6 +1182,7 @@ ICON_TIMELAPSE = "mdi:timelapse" ICON_TIMER = "mdi:timer-outline" ICON_VIBRATE = "mdi:vibrate" ICON_WATER = "mdi:water" +ICON_WATER_HEATER = "mdi:water-boiler" ICON_WATER_PERCENT = "mdi:water-percent" ICON_WEATHER_SUNSET = "mdi:weather-sunset" ICON_WEATHER_SUNSET_DOWN = "mdi:weather-sunset-down" diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 0e09d97fed..70593d8153 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -608,6 +608,8 @@ class EsphomeCore: self.current_component: str | None = None # Address cache for DNS and mDNS lookups from command line arguments self.address_cache: AddressCache | None = None + # Cached config hash (computed lazily) + self._config_hash: int | None = None def reset(self): from esphome.pins import PIN_SCHEMA_REGISTRY @@ -636,6 +638,7 @@ class EsphomeCore: self.unique_ids = {} self.current_component = None self.address_cache = None + self._config_hash = None PIN_SCHEMA_REGISTRY.reset() @contextmanager @@ -685,6 +688,21 @@ class EsphomeCore: return None + @property + def config_hash(self) -> int: + """Get the FNV-1a 32-bit hash of the config. + + The hash is computed lazily and cached for performance. + Uses sort_keys=True to ensure deterministic ordering. + """ + if self._config_hash is None: + from esphome import yaml_util + from esphome.helpers import fnv1a_32bit_hash + + config_str = yaml_util.dump(self.config, show_secrets=True, sort_keys=True) + self._config_hash = fnv1a_32bit_hash(config_str) + return self._config_hash + @property def config_dir(self) -> Path: if self.config_path.is_dir(): @@ -799,6 +817,11 @@ class EsphomeCore: @property def using_esp_idf(self): + _LOGGER.warning( + "CORE.using_esp_idf was deprecated in 2026.1, will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + "Use CORE.is_esp32 and/or CORE.using_arduino instead." + ) return self.target_framework == "esp-idf" @property diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index a85d671a07..55eb25ce09 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -1,5 +1,15 @@ #include "esphome/core/application.h" +#include "esphome/core/build_info_data.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" +#include + +#ifdef USE_ESP8266 +#include +#endif +#ifdef USE_ESP32 +#include +#endif #include "esphome/core/version.h" #include "esphome/core/hal.h" #include @@ -191,9 +201,29 @@ void Application::loop() { if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { - ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_); + char build_time_str[Application::BUILD_TIME_STR_SIZE]; + this->get_build_time_string(build_time_str); + ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str); #ifdef ESPHOME_PROJECT_NAME ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION); +#endif +#ifdef USE_ESP32 + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + ESP_LOGI(TAG, "ESP32 Chip: %s r%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100, + chip_info.revision % 100, chip_info.cores); +#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET) + // Suggest optimization for chips that don't need the PSRAM cache workaround + if (chip_info.revision >= 300) { +#ifdef USE_PSRAM + ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to save ~10KB IRAM", chip_info.revision / 100, + chip_info.revision % 100); +#else + ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100, + chip_info.revision % 100); +#endif + } +#endif #endif } @@ -711,4 +741,9 @@ void Application::wake_loop_threadsafe() { } #endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) +void Application::get_build_time_string(std::span buffer) { + ESPHOME_strncpy_P(buffer.data(), ESPHOME_BUILD_TIME_STR, buffer.size()); + buffer[buffer.size() - 1] = '\0'; +} + } // namespace esphome diff --git a/esphome/core/application.h b/esphome/core/application.h index 8e2035b7c5..592bf809f1 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -1,16 +1,21 @@ #pragma once #include +#include #include +#include #include #include +#include "esphome/core/build_info_data.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" +#include "esphome/core/progmem.h" #include "esphome/core/scheduler.h" #include "esphome/core/string_ref.h" +#include "esphome/core/version.h" #ifdef USE_DEVICES #include "esphome/core/device.h" @@ -83,6 +88,12 @@ #ifdef USE_ALARM_CONTROL_PANEL #include "esphome/components/alarm_control_panel/alarm_control_panel.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif +#ifdef USE_INFRARED +#include "esphome/components/infrared/infrared.h" +#endif #ifdef USE_EVENT #include "esphome/components/event/event.h" #endif @@ -100,8 +111,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick class Application { public: - void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment, - const char *compilation_time, bool name_add_mac_suffix) { + void pre_setup(const std::string &name, const std::string &friendly_name, bool name_add_mac_suffix) { arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { @@ -120,8 +130,6 @@ class Application { this->name_ = name; this->friendly_name_ = friendly_name; } - this->comment_ = comment; - this->compilation_time_ = compilation_time; } #ifdef USE_DEVICES @@ -214,6 +222,14 @@ class Application { } #endif +#ifdef USE_WATER_HEATER + void register_water_heater(water_heater::WaterHeater *water_heater) { this->water_heaters_.push_back(water_heater); } +#endif + +#ifdef USE_INFRARED + void register_infrared(infrared::Infrared *infrared) { this->infrareds_.push_back(infrared); } +#endif + #ifdef USE_EVENT void register_event(event::Event *event) { this->events_.push_back(event); } #endif @@ -254,16 +270,46 @@ class Application { return ""; } - /// Get the comment of this Application set by pre_setup(). - std::string get_comment() const { return this->comment_; } - /// Get the comment as StringRef (avoids allocation) - StringRef get_comment_ref() const { return StringRef(this->comment_); } + /// Copy the comment string into the provided buffer + /// Buffer must be ESPHOME_COMMENT_SIZE bytes (compile-time enforced) + void get_comment_string(std::span buffer) { + ESPHOME_strncpy_P(buffer.data(), ESPHOME_COMMENT_STR, buffer.size()); + buffer[buffer.size() - 1] = '\0'; + } + + /// Get the comment of this Application as a string + std::string get_comment() { + char buffer[ESPHOME_COMMENT_SIZE]; + this->get_comment_string(buffer); + return std::string(buffer); + } bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } - std::string get_compilation_time() const { return this->compilation_time_; } - /// Get the compilation time as StringRef (for API usage) - StringRef get_compilation_time_ref() const { return StringRef(this->compilation_time_); } + /// Size of buffer required for build time string (including null terminator) + static constexpr size_t BUILD_TIME_STR_SIZE = 26; + + /// Get the config hash as a 32-bit integer + constexpr uint32_t get_config_hash() { return ESPHOME_CONFIG_HASH; } + + /// Get the config hash extended with ESPHome version + constexpr uint32_t get_config_version_hash() { return fnv1a_hash_extend(ESPHOME_CONFIG_HASH, ESPHOME_VERSION); } + + /// Get the build time as a Unix timestamp + constexpr time_t get_build_time() { return ESPHOME_BUILD_TIME; } + + /// Copy the build time string into the provided buffer + /// Buffer must be BUILD_TIME_STR_SIZE bytes (compile-time enforced) + void get_build_time_string(std::span buffer); + + /// Get the build time as a string (deprecated, use get_build_time_string() instead) + // Remove before 2026.7.0 + ESPDEPRECATED("Use get_build_time_string() instead. Removed in 2026.7.0", "2026.1.0") + std::string get_compilation_time() { + char buf[BUILD_TIME_STR_SIZE]; + this->get_build_time_string(buf); + return std::string(buf); + } /// Get the cached time in milliseconds from when the current component started its loop execution inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; } @@ -413,6 +459,16 @@ class Application { GET_ENTITY_METHOD(alarm_control_panel::AlarmControlPanel, alarm_control_panel, alarm_control_panels) #endif +#ifdef USE_WATER_HEATER + auto &get_water_heaters() const { return this->water_heaters_; } + GET_ENTITY_METHOD(water_heater::WaterHeater, water_heater, water_heaters) +#endif + +#ifdef USE_INFRARED + auto &get_infrareds() const { return this->infrareds_; } + GET_ENTITY_METHOD(infrared::Infrared, infrared, infrareds) +#endif + #ifdef USE_EVENT auto &get_events() const { return this->events_; } GET_ENTITY_METHOD(event::Event, event, events) @@ -477,8 +533,6 @@ class Application { // Pointer-sized members first Component *current_component_{nullptr}; - const char *comment_{nullptr}; - const char *compilation_time_{nullptr}; // std::vector (3 pointers each: begin, end, capacity) // Partitioned vector design for looping components @@ -611,6 +665,12 @@ class Application { StaticVector alarm_control_panels_{}; #endif +#ifdef USE_WATER_HEATER + StaticVector water_heaters_{}; +#endif +#ifdef USE_INFRARED + StaticVector infrareds_{}; +#endif #ifdef USE_UPDATE StaticVector updates_{}; #endif diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 61d2944acf..eac469d0fc 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -42,6 +42,10 @@ template struct gens<0, S...> { using type = seq; }; #define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name) template class TemplatableValue { + // For std::string, store pointer to heap-allocated string to keep union pointer-sized. + // For other types, store value inline. + static constexpr bool USE_HEAP_STORAGE = std::same_as; + public: TemplatableValue() : type_(NONE) {} @@ -52,7 +56,11 @@ template class TemplatableValue { } template TemplatableValue(F value) requires(!std::invocable) : type_(VALUE) { - new (&this->value_) T(std::move(value)); + if constexpr (USE_HEAP_STORAGE) { + this->value_ = new T(std::move(value)); + } else { + new (&this->value_) T(std::move(value)); + } } // For stateless lambdas (convertible to function pointer): use function pointer @@ -71,7 +79,11 @@ template class TemplatableValue { // Copy constructor TemplatableValue(const TemplatableValue &other) : type_(other.type_) { if (this->type_ == VALUE) { - new (&this->value_) T(other.value_); + if constexpr (USE_HEAP_STORAGE) { + this->value_ = new T(*other.value_); + } else { + new (&this->value_) T(other.value_); + } } else if (this->type_ == LAMBDA) { this->f_ = new std::function(*other.f_); } else if (this->type_ == STATELESS_LAMBDA) { @@ -84,7 +96,12 @@ template class TemplatableValue { // Move constructor TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) { if (this->type_ == VALUE) { - new (&this->value_) T(std::move(other.value_)); + if constexpr (USE_HEAP_STORAGE) { + this->value_ = other.value_; + other.value_ = nullptr; + } else { + new (&this->value_) T(std::move(other.value_)); + } } else if (this->type_ == LAMBDA) { this->f_ = other.f_; other.f_ = nullptr; @@ -115,23 +132,31 @@ template class TemplatableValue { ~TemplatableValue() { if (this->type_ == VALUE) { - this->value_.~T(); + if constexpr (USE_HEAP_STORAGE) { + delete this->value_; + } else { + this->value_.~T(); + } } else if (this->type_ == LAMBDA) { delete this->f_; } // STATELESS_LAMBDA/STATIC_STRING/NONE: no cleanup needed (pointers, not heap-allocated) } - bool has_value() { return this->type_ != NONE; } + bool has_value() const { return this->type_ != NONE; } - T value(X... x) { + T value(X... x) const { switch (this->type_) { case STATELESS_LAMBDA: return this->stateless_f_(x...); // Direct function pointer call case LAMBDA: return (*this->f_)(x...); // std::function call case VALUE: - return this->value_; + if constexpr (USE_HEAP_STORAGE) { + return *this->value_; + } else { + return this->value_; + } case STATIC_STRING: // if constexpr required: code must compile for all T, but STATIC_STRING // can only be set when T is std::string (enforced by constructor constraint) @@ -159,6 +184,12 @@ template class TemplatableValue { return this->value(x...); } + /// Check if this holds a static string (const char* stored without allocation) + bool is_static_string() const { return this->type_ == STATIC_STRING; } + + /// Get the static string pointer (only valid if is_static_string() returns true) + const char *get_static_string() const { return this->static_str_; } + protected: enum : uint8_t { NONE, @@ -168,8 +199,11 @@ template class TemplatableValue { STATIC_STRING, // For const char* when T is std::string - avoids heap allocation } type_; + // For std::string, use heap pointer to minimize union size (4 bytes vs 12+). + // For other types, store value inline as before. + using ValueStorage = std::conditional_t; union { - T value_; + ValueStorage value_; // T for inline storage, T* for heap storage std::function *f_; T (*stateless_f_)(X...); const char *static_str_; // For STATIC_STRING type diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index e8878ac251..19d0ccf972 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -191,15 +191,15 @@ template class DelayAction : public Action, public Compon // instead of std::bind to avoid bind overhead (~16 bytes heap + faster execution) if constexpr (sizeof...(Ts) == 0) { App.scheduler.set_timer_common_( - this, Scheduler::SchedulerItem::TIMEOUT, - /* is_static_string= */ true, "delay", this->delay_.value(), [this]() { this->play_next_(); }, + this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::STATIC_STRING, "delay", 0, this->delay_.value(), + [this]() { this->play_next_(); }, /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); } else { // For delays with arguments, use std::bind to preserve argument values // Arguments must be copied because original references may be invalid after delay auto f = std::bind(&DelayAction::play_next_, this, x...); - App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, - /* is_static_string= */ true, "delay", this->delay_.value(x...), std::move(f), + App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::STATIC_STRING, + "delay", 0, this->delay_.value(x...), std::move(f), /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); } } diff --git a/esphome/core/build_info_data.h b/esphome/core/build_info_data.h new file mode 100644 index 0000000000..02bb465e44 --- /dev/null +++ b/esphome/core/build_info_data.h @@ -0,0 +1,12 @@ +#pragma once + +// This file is not used by the runtime, instead, a version is generated during +// compilation with the actual build info values. +// +// This file is only used by static analyzers and IDEs. + +#define ESPHOME_CONFIG_HASH 0x12345678U // NOLINT +#define ESPHOME_BUILD_TIME 1700000000 // NOLINT +#define ESPHOME_COMMENT_SIZE 1 // NOLINT +static const char ESPHOME_BUILD_TIME_STR[] = "2024-01-01 00:00:00 +0000"; +static const char ESPHOME_COMMENT_STR[] = ""; diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 97ab2edb5a..2f61f7d195 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -118,7 +118,10 @@ void Component::setup() {} void Component::loop() {} void Component::set_interval(const std::string &name, uint32_t interval, std::function &&f) { // NOLINT +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" App.scheduler.set_interval(this, name, interval, std::move(f)); +#pragma GCC diagnostic pop } void Component::set_interval(const char *name, uint32_t interval, std::function &&f) { // NOLINT @@ -126,7 +129,10 @@ void Component::set_interval(const char *name, uint32_t interval, std::function< } bool Component::cancel_interval(const std::string &name) { // NOLINT +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return App.scheduler.cancel_interval(this, name); +#pragma GCC diagnostic pop } bool Component::cancel_interval(const char *name) { // NOLINT @@ -135,7 +141,10 @@ bool Component::cancel_interval(const char *name) { // NOLINT void Component::set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, float backoff_increase_factor) { // NOLINT +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); +#pragma GCC diagnostic pop } void Component::set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, @@ -144,7 +153,10 @@ void Component::set_retry(const char *name, uint32_t initial_wait_time, uint8_t } bool Component::cancel_retry(const std::string &name) { // NOLINT +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return App.scheduler.cancel_retry(this, name); +#pragma GCC diagnostic pop } bool Component::cancel_retry(const char *name) { // NOLINT @@ -152,7 +164,10 @@ bool Component::cancel_retry(const char *name) { // NOLINT } void Component::set_timeout(const std::string &name, uint32_t timeout, std::function &&f) { // NOLINT +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" App.scheduler.set_timeout(this, name, timeout, std::move(f)); +#pragma GCC diagnostic pop } void Component::set_timeout(const char *name, uint32_t timeout, std::function &&f) { // NOLINT @@ -160,13 +175,36 @@ void Component::set_timeout(const char *name, uint32_t timeout, std::function &&f) { // NOLINT + App.scheduler.set_timeout(this, id, timeout, std::move(f)); +} + +bool Component::cancel_timeout(uint32_t id) { return App.scheduler.cancel_timeout(this, id); } + +void Component::set_interval(uint32_t id, uint32_t interval, std::function &&f) { // NOLINT + App.scheduler.set_interval(this, id, interval, std::move(f)); +} + +bool Component::cancel_interval(uint32_t id) { return App.scheduler.cancel_interval(this, id); } + +void Component::set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, + std::function &&f, float backoff_increase_factor) { // NOLINT + App.scheduler.set_retry(this, id, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); +} + +bool Component::cancel_retry(uint32_t id) { return App.scheduler.cancel_retry(this, id); } + void Component::call_loop() { this->loop(); } void Component::call_setup() { this->setup(); } void Component::call_dump_config() { @@ -205,7 +243,13 @@ void Component::call() { this->call_setup(); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG uint32_t setup_time = millis() - start_time; - ESP_LOGCONFIG(TAG, "Setup %s took %ums", LOG_STR_ARG(this->get_component_log_str()), (unsigned) setup_time); + // Only log at CONFIG level if setup took longer than the blocking threshold + // to avoid spamming the log and blocking the event loop + if (setup_time >= WARN_IF_BLOCKING_OVER_MS) { + ESP_LOGCONFIG(TAG, "Setup %s took %ums", LOG_STR_ARG(this->get_component_log_str()), (unsigned) setup_time); + } else { + ESP_LOGV(TAG, "Setup %s took %ums", LOG_STR_ARG(this->get_component_log_str()), (unsigned) setup_time); + } #endif break; } @@ -295,10 +339,19 @@ void Component::defer(std::function &&f) { // NOLINT App.scheduler.set_timeout(this, static_cast(nullptr), 0, std::move(f)); } bool Component::cancel_defer(const std::string &name) { // NOLINT +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + return App.scheduler.cancel_timeout(this, name); +#pragma GCC diagnostic pop +} +bool Component::cancel_defer(const char *name) { // NOLINT return App.scheduler.cancel_timeout(this, name); } void Component::defer(const std::string &name, std::function &&f) { // NOLINT +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" App.scheduler.set_timeout(this, name, 0, std::move(f)); +#pragma GCC diagnostic pop } void Component::defer(const char *name, std::function &&f) { // NOLINT App.scheduler.set_timeout(this, name, 0, std::move(f)); diff --git a/esphome/core/component.h b/esphome/core/component.h index 32f594d6f8..49349d4199 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -306,6 +306,8 @@ class Component { * * @see cancel_interval() */ + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_interval(const std::string &name, uint32_t interval, std::function &&f); // NOLINT /** Set an interval function with a const char* name. @@ -324,6 +326,14 @@ class Component { */ void set_interval(const char *name, uint32_t interval, std::function &&f); // NOLINT + /** Set an interval function with a numeric ID (zero heap allocation). + * + * @param id The numeric identifier for this interval function + * @param interval The interval in ms + * @param f The function to call + */ + void set_interval(uint32_t id, uint32_t interval, std::function &&f); // NOLINT + void set_interval(uint32_t interval, std::function &&f); // NOLINT /** Cancel an interval function. @@ -331,8 +341,11 @@ class Component { * @param name The identifier for this interval function. * @return Whether an interval functions was deleted. */ + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_interval(const std::string &name); // NOLINT bool cancel_interval(const char *name); // NOLINT + bool cancel_interval(uint32_t id); // NOLINT /** Set an retry function with a unique name. Empty name means no cancelling possible. * @@ -364,12 +377,25 @@ class Component { * @param backoff_increase_factor time between retries is multiplied by this factor on every retry after the first * @see cancel_retry() */ + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT void set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT + /** Set a retry function with a numeric ID (zero heap allocation). + * + * @param id The numeric identifier for this retry function + * @param initial_wait_time The wait time after the first execution + * @param max_attempts The max number of attempts + * @param f The function to call + * @param backoff_increase_factor The factor to increase the retry interval by + */ + void set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT + std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT + void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, // NOLINT float backoff_increase_factor = 1.0f); // NOLINT @@ -378,8 +404,11 @@ class Component { * @param name The identifier for this retry function. * @return Whether a retry function was deleted. */ + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_retry(const std::string &name); // NOLINT bool cancel_retry(const char *name); // NOLINT + bool cancel_retry(uint32_t id); // NOLINT /** Set a timeout function with a unique name. * @@ -395,6 +424,8 @@ class Component { * * @see cancel_timeout() */ + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std::string &name, uint32_t timeout, std::function &&f); // NOLINT /** Set a timeout function with a const char* name. @@ -413,6 +444,14 @@ class Component { */ void set_timeout(const char *name, uint32_t timeout, std::function &&f); // NOLINT + /** Set a timeout function with a numeric ID (zero heap allocation). + * + * @param id The numeric identifier for this timeout function + * @param timeout The timeout in ms + * @param f The function to call + */ + void set_timeout(uint32_t id, uint32_t timeout, std::function &&f); // NOLINT + void set_timeout(uint32_t timeout, std::function &&f); // NOLINT /** Cancel a timeout function. @@ -420,8 +459,11 @@ class Component { * @param name The identifier for this timeout function. * @return Whether a timeout functions was deleted. */ + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std::string &name); // NOLINT bool cancel_timeout(const char *name); // NOLINT + bool cancel_timeout(uint32_t id); // NOLINT /** Defer a callback to the next loop() call. * @@ -430,6 +472,8 @@ class Component { * @param name The name of the defer function. * @param f The callback. */ + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std::string &name, std::function &&f); // NOLINT /** Defer a callback to the next loop() call with a const char* name. @@ -451,7 +495,10 @@ class Component { void defer(std::function &&f); // NOLINT /// Cancel a defer callback using the specified name, name must not be empty. + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_defer(const std::string &name); // NOLINT + bool cancel_defer(const char *name); // NOLINT // Ordered for optimal packing on 32-bit systems const LogString *component_source_{nullptr}; diff --git a/esphome/core/component_iterator.cpp b/esphome/core/component_iterator.cpp index 8c6a7b95b5..ff76b2b81b 100644 --- a/esphome/core/component_iterator.cpp +++ b/esphome/core/component_iterator.cpp @@ -163,6 +163,18 @@ void ComponentIterator::advance() { break; #endif +#ifdef USE_WATER_HEATER + case IteratorState::WATER_HEATER: + this->process_platform_item_(App.get_water_heaters(), &ComponentIterator::on_water_heater); + break; +#endif + +#ifdef USE_INFRARED + case IteratorState::INFRARED: + this->process_platform_item_(App.get_infrareds(), &ComponentIterator::on_infrared); + break; +#endif + #ifdef USE_EVENT case IteratorState::EVENT: this->process_platform_item_(App.get_events(), &ComponentIterator::on_event); diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index 1b1bd80ac5..e13d81a8e4 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -16,6 +16,12 @@ class UserServiceDescriptor; } // namespace api #endif +#ifdef USE_INFRARED +namespace infrared { +class Infrared; +} // namespace infrared +#endif + class ComponentIterator { public: void begin(bool include_internal = false); @@ -84,6 +90,12 @@ class ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0; #endif +#ifdef USE_WATER_HEATER + virtual bool on_water_heater(water_heater::WaterHeater *water_heater) = 0; +#endif +#ifdef USE_INFRARED + virtual bool on_infrared(infrared::Infrared *infrared) = 0; +#endif #ifdef USE_EVENT virtual bool on_event(event::Event *event) = 0; #endif @@ -161,6 +173,12 @@ class ComponentIterator { #ifdef USE_ALARM_CONTROL_PANEL ALARM_CONTROL_PANEL, #endif +#ifdef USE_WATER_HEATER + WATER_HEATER, +#endif +#ifdef USE_INFRARED + INFRARED, +#endif #ifdef USE_EVENT EVENT, #endif diff --git a/esphome/core/config.py b/esphome/core/config.py index 3adaf7eb9e..21ed8ced1a 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -76,6 +76,7 @@ VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} def validate_hostname(config): + # Keep in sync with ESPHOME_DEVICE_NAME_MAX_LEN in esphome/core/entity_base.h max_length = 31 if config[CONF_NAME_ADD_MAC_SUFFIX]: max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used @@ -183,17 +184,24 @@ if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ: else: _compile_process_limit_default = cv.UNDEFINED +# Keep in sync with ESPHOME_FRIENDLY_NAME_MAX_LEN in esphome/core/entity_base.h +FRIENDLY_NAME_MAX_LEN = 120 + AREA_SCHEMA = cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(Area), - cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_NAME): cv.All( + cv.string_no_slash, cv.Length(max=FRIENDLY_NAME_MAX_LEN) + ), } ) DEVICE_SCHEMA = cv.Schema( { cv.GenerateID(CONF_ID): cv.declare_id(Device), - cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_NAME): cv.All( + cv.string_no_slash, cv.Length(max=FRIENDLY_NAME_MAX_LEN) + ), cv.Optional(CONF_AREA_ID): cv.use_id(Area), } ) @@ -207,9 +215,12 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, - cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All(cv.string, cv.Length(max=120)), + # Keep max=120 in sync with OBJECT_ID_MAX_LEN in esphome/core/entity_base.h + cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All( + cv.string_no_slash, cv.Length(max=FRIENDLY_NAME_MAX_LEN) + ), cv.Optional(CONF_AREA): validate_area_config, - cv.Optional(CONF_COMMENT): cv.string, + cv.Optional(CONF_COMMENT): cv.All(cv.string, cv.Length(max=255)), cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( { @@ -382,10 +393,15 @@ def include_file(path: Path, basename: Path, is_c_header: bool = False): ARDUINO_GLUE_CODE = """\ +#undef yield #define yield() esphome::yield() +#undef millis #define millis() esphome::millis() +#undef micros #define micros() esphome::micros() +#undef delay #define delay(x) esphome::delay(x) +#undef delayMicroseconds #define delayMicroseconds(x) esphome::delayMicroseconds(x) """ @@ -500,8 +516,6 @@ async def to_code(config: ConfigType) -> None: cg.App.pre_setup( config[CONF_NAME], config[CONF_FRIENDLY_NAME], - config.get(CONF_COMMENT, ""), - cg.RawExpression('__DATE__ ", " __TIME__'), config[CONF_NAME_ADD_MAC_SUFFIX], ) ) @@ -537,7 +551,7 @@ async def to_code(config: ConfigType) -> None: if config[CONF_DEBUG_SCHEDULER]: cg.add_define("ESPHOME_DEBUG_SCHEDULER") - if CORE.using_arduino and not CORE.is_bk72xx: + if CORE.using_arduino: CORE.add_job(add_arduino_global_workaround) if config[CONF_INCLUDES]: diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 697017217d..632b46c893 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -58,6 +58,9 @@ #ifdef USE_ALARM_CONTROL_PANEL #include "esphome/components/alarm_control_panel/alarm_control_panel.h" #endif +#ifdef USE_WATER_HEATER +#include "esphome/components/water_heater/water_heater.h" +#endif #ifdef USE_EVENT #include "esphome/components/event/event.h" #endif @@ -123,6 +126,9 @@ class Controller { #ifdef USE_ALARM_CONTROL_PANEL virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){}; #endif +#ifdef USE_WATER_HEATER + virtual void on_water_heater_update(water_heater::WaterHeater *obj){}; +#endif #ifdef USE_EVENT virtual void on_event(event::Event *obj){}; #endif diff --git a/esphome/core/controller_registry.cpp b/esphome/core/controller_registry.cpp index 0a84bb0d0d..13b505e8e9 100644 --- a/esphome/core/controller_registry.cpp +++ b/esphome/core/controller_registry.cpp @@ -98,6 +98,10 @@ CONTROLLER_REGISTRY_NOTIFY(media_player::MediaPlayer, media_player) CONTROLLER_REGISTRY_NOTIFY(alarm_control_panel::AlarmControlPanel, alarm_control_panel) #endif +#ifdef USE_WATER_HEATER +CONTROLLER_REGISTRY_NOTIFY(water_heater::WaterHeater, water_heater) +#endif + #ifdef USE_EVENT CONTROLLER_REGISTRY_NOTIFY_NO_UPDATE_SUFFIX(event::Event, event) #endif diff --git a/esphome/core/controller_registry.h b/esphome/core/controller_registry.h index 640a276a0a..d6452d8827 100644 --- a/esphome/core/controller_registry.h +++ b/esphome/core/controller_registry.h @@ -119,6 +119,12 @@ class AlarmControlPanel; } #endif +#ifdef USE_WATER_HEATER +namespace water_heater { +class WaterHeater; +} +#endif + #ifdef USE_EVENT namespace event { class Event; @@ -228,6 +234,10 @@ class ControllerRegistry { static void notify_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj); #endif +#ifdef USE_WATER_HEATER + static void notify_water_heater_update(water_heater::WaterHeater *obj); +#endif + #ifdef USE_EVENT static void notify_event(event::Event *obj); #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index a5170d73ff..c229d1df7d 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -20,6 +20,8 @@ // logger #define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_VERY_VERBOSE +#define USE_LOG_LISTENERS +#define ESPHOME_LOG_MAX_LISTENERS 8 // Feature flags #define USE_ALARM_CONTROL_PANEL @@ -28,6 +30,7 @@ #define USE_BUTTON #define USE_CAMERA #define USE_CLIMATE +#define USE_CLIMATE_VISUAL_OVERRIDES #define USE_CONTROLLER_REGISTRY #define USE_COVER #define USE_DATETIME @@ -48,6 +51,8 @@ #define USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT 8000 // NOLINT #define USE_IMAGE #define USE_IMPROV_SERIAL_NEXT_URL +#define USE_INFRARED +#define USE_IR_RF #define USE_JSON #define USE_LIGHT #define USE_LOCK @@ -99,6 +104,7 @@ #define USE_OUTPUT #define USE_POWER_SUPPLY #define USE_QR_CODE +#define USE_SAFE_MODE_CALLBACK #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED @@ -112,6 +118,8 @@ #define USE_UART_WAKE_LOOP_ON_RX #define USE_UPDATE #define USE_VALVE +#define USE_WATER_HEATER +#define USE_WATER_HEATER_VISUAL_OVERRIDES #define USE_ZWAVE_PROXY // Feature flags which do not work for zephyr @@ -141,11 +149,8 @@ #define USE_ONLINE_IMAGE_PNG_SUPPORT #define USE_ONLINE_IMAGE_JPEG_SUPPORT #define USE_OTA -#define USE_OTA_MD5 #define USE_OTA_PASSWORD -#define USE_OTA_SHA256 -#define ALLOW_OTA_DOWNGRADE_MD5 -#define USE_OTA_STATE_CALLBACK +#define USE_OTA_STATE_LISTENER #define USE_OTA_VERSION 2 #define USE_TIME_TIMEZONE #define USE_WIFI @@ -161,15 +166,12 @@ #define USE_I2S_LEGACY #endif -// IDF-specific feature flags -#ifdef USE_ESP_IDF -#define USE_MQTT_IDF_ENQUEUE -#define ESPHOME_LOOP_TASK_STACK_SIZE 8192 -#endif - // ESP32-specific feature flags #ifdef USE_ESP32 +#define USE_MQTT_IDF_ENQUEUE #define USE_ESPHOME_TASK_LOG_BUFFER +#define USE_OTA_ROLLBACK +#define USE_ESP32_MIN_CHIP_REVISION_SET #define USE_BLUETOOTH_PROXY #define BLUETOOTH_PROXY_MAX_CONNECTIONS 3 @@ -196,6 +198,7 @@ #define ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT 1 #define ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT 1 #define ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT 2 +#define ESPHOME_LOOP_TASK_STACK_SIZE 8192 #define USE_ESP32_CAMERA_JPEG_ENCODER #define USE_HTTP_REQUEST_RESPONSE #define USE_I2C @@ -213,21 +216,33 @@ #define USE_WEBSERVER_AUTH #define USE_WEBSERVER_OTA #define USE_WEBSERVER_PORT 80 // NOLINT +#define USE_WEBSERVER_GZIP #define USE_WEBSERVER_SORTING +#define WEB_SERVER_DEFAULT_HEADERS_COUNT 1 +#define USE_CAPTIVE_PORTAL_GZIP #define USE_WIFI_11KV_SUPPORT #define USE_WIFI_FAST_CONNECT -#define USE_WIFI_LISTENERS +#define USE_WIFI_IP_STATE_LISTENERS +#define USE_WIFI_SCAN_RESULTS_LISTENERS +#define USE_WIFI_CONNECT_STATE_LISTENERS +#define USE_WIFI_POWER_SAVE_LISTENERS +#define ESPHOME_WIFI_IP_STATE_LISTENERS 2 +#define ESPHOME_WIFI_SCAN_RESULTS_LISTENERS 2 +#define ESPHOME_WIFI_CONNECT_STATE_LISTENERS 2 +#define ESPHOME_WIFI_POWER_SAVE_LISTENERS 2 #define USE_WIFI_RUNTIME_POWER_SAVE #define USB_HOST_MAX_REQUESTS 16 #ifdef USE_ARDUINO -#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 2) +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 5) #define USE_ETHERNET #define USE_ETHERNET_KSZ8081 #define USE_ETHERNET_MANUAL_IP +#define USE_ETHERNET_IP_STATE_LISTENERS +#define ESPHOME_ETHERNET_IP_STATE_LISTENERS 2 #endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #define USE_MICRO_WAKE_WORD #define USE_MICRO_WAKE_WORD_VAD #if defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) @@ -250,7 +265,11 @@ #define USE_ADC_SENSOR_VCC #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2) #define USE_CAPTIVE_PORTAL +#define USE_ESP8266_LOGGER_SERIAL +#define USE_ESP8266_LOGGER_SERIAL1 #define USE_ESP8266_PREFERENCES_FLASH +#define USE_ESP8266_UART_SERIAL +#define USE_ESP8266_UART_SERIAL1 #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_HTTP_REQUEST_RESPONSE #define USE_I2C @@ -301,6 +320,10 @@ #define USE_NRF52_UICR_ERASE #define USE_SOFTDEVICE_ID 7 #define USE_SOFTDEVICE_VERSION 1 +#define USE_ZIGBEE +#define USE_ZIGBEE_WIPE_ON_BOOT +#define USE_ZIGBEE_WIPE_ON_BOOT_MAGIC 1 +#define ZIGBEE_ENDPOINTS_COUNT 8 #endif // Disabled feature flags @@ -311,9 +334,9 @@ // Default counts for static analysis #define CONTROLLER_REGISTRY_MAX 2 +#define ESPHOME_AREA_COUNT 10 #define ESPHOME_COMPONENT_COUNT 50 #define ESPHOME_DEVICE_COUNT 10 -#define ESPHOME_AREA_COUNT 10 #define ESPHOME_ENTITY_ALARM_CONTROL_PANEL_COUNT 1 #define ESPHOME_ENTITY_BINARY_SENSOR_COUNT 1 #define ESPHOME_ENTITY_BUTTON_COUNT 1 @@ -323,6 +346,7 @@ #define ESPHOME_ENTITY_DATETIME_COUNT 1 #define ESPHOME_ENTITY_EVENT_COUNT 1 #define ESPHOME_ENTITY_FAN_COUNT 1 +#define ESPHOME_ENTITY_INFRARED_COUNT 1 #define ESPHOME_ENTITY_LIGHT_COUNT 1 #define ESPHOME_ENTITY_LOCK_COUNT 1 #define ESPHOME_ENTITY_MEDIA_PLAYER_COUNT 1 @@ -335,3 +359,5 @@ #define ESPHOME_ENTITY_TIME_COUNT 1 #define ESPHOME_ENTITY_UPDATE_COUNT 1 #define ESPHOME_ENTITY_VALVE_COUNT 1 +#define ESPHOME_ENTITY_WATER_HEATER_COUNT 1 +#define ESPHOME_MAX_USB_CDC_INSTANCES 1 diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 046f99d8cc..8508b93411 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -9,7 +9,8 @@ static const char *const TAG = "entity_base"; // Entity Name const StringRef &EntityBase::get_name() const { return this->name_; } -void EntityBase::set_name(const char *name) { +void EntityBase::set_name(const char *name) { this->set_name(name, 0); } +void EntityBase::set_name(const char *name, uint32_t object_id_hash) { this->name_ = StringRef(name); if (this->name_.empty()) { #ifdef USE_DEVICES @@ -18,11 +19,29 @@ void EntityBase::set_name(const char *name) { } else #endif { - this->name_ = StringRef(App.get_friendly_name()); + // Bug-for-bug compatibility with OLD behavior: + // - With MAC suffix: OLD code used App.get_friendly_name() directly (no fallback) + // - Without MAC suffix: OLD code used pre-computed object_id with fallback to device name + const std::string &friendly = App.get_friendly_name(); + if (App.is_name_add_mac_suffix_enabled()) { + // MAC suffix enabled - use friendly_name directly (even if empty) for compatibility + this->name_ = StringRef(friendly); + } else { + // No MAC suffix - fallback to device name if friendly_name is empty + this->name_ = StringRef(!friendly.empty() ? friendly : App.get_name()); + } } this->flags_.has_own_name = false; + // Dynamic name - must calculate hash at runtime + this->calc_object_id_(); } else { this->flags_.has_own_name = true; + // Static name - use pre-computed hash if provided + if (object_id_hash != 0) { + this->object_id_hash_ = object_id_hash; + } else { + this->calc_object_id_(); + } } } @@ -45,45 +64,30 @@ void EntityBase::set_icon(const char *icon) { #endif } -// Check if the object_id is dynamic (changes with MAC suffix) -bool EntityBase::is_object_id_dynamic_() const { - return !this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled(); -} - -// Entity Object ID +// Entity Object ID - computed on-demand from name std::string EntityBase::get_object_id() const { - // Check if `App.get_friendly_name()` is constant or dynamic. - if (this->is_object_id_dynamic_()) { - // `App.get_friendly_name()` is dynamic. - return str_sanitize(str_snake_case(App.get_friendly_name())); - } - // `App.get_friendly_name()` is constant. - return this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_; -} -StringRef EntityBase::get_object_id_ref_for_api_() const { - static constexpr auto EMPTY_STRING = StringRef::from_lit(""); - // Return empty for dynamic case (MAC suffix) - if (this->is_object_id_dynamic_()) { - return EMPTY_STRING; - } - // For static case, return the string or empty if null - return this->object_id_c_str_ == nullptr ? EMPTY_STRING : StringRef(this->object_id_c_str_); -} -void EntityBase::set_object_id(const char *object_id) { - this->object_id_c_str_ = object_id; - this->calc_object_id_(); + char buf[OBJECT_ID_MAX_LEN]; + size_t len = this->write_object_id_to(buf, sizeof(buf)); + return std::string(buf, len); } -void EntityBase::set_name_and_object_id(const char *name, const char *object_id) { - this->set_name(name); - this->object_id_c_str_ = object_id; - this->calc_object_id_(); -} - -// Calculate Object ID Hash from Entity Name +// Calculate Object ID Hash directly from name using snake_case + sanitize void EntityBase::calc_object_id_() { - this->object_id_hash_ = - fnv1_hash(this->is_object_id_dynamic_() ? this->get_object_id().c_str() : this->object_id_c_str_); + this->object_id_hash_ = fnv1_hash_object_id(this->name_.c_str(), this->name_.size()); +} + +size_t EntityBase::write_object_id_to(char *buf, size_t buf_size) const { + size_t len = std::min(this->name_.size(), buf_size - 1); + for (size_t i = 0; i < len; i++) { + buf[i] = to_sanitized_char(to_snake_case_char(this->name_[i])); + } + buf[len] = '\0'; + return len; +} + +StringRef EntityBase::get_object_id_to(std::span buf) const { + size_t len = this->write_object_id_to(buf.data(), buf.size()); + return StringRef(buf.data(), len); } uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index fdf3f6300a..f91bd9b20c 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include #include "string_ref.h" #include "helpers.h" #include "log.h" @@ -12,14 +13,21 @@ namespace esphome { -// Forward declaration for friend access -namespace api { -class APIConnection; -} // namespace api +// Maximum device name length - keep in sync with validate_hostname() in esphome/core/config.py +static constexpr size_t ESPHOME_DEVICE_NAME_MAX_LEN = 31; -namespace web_server { -struct UrlMatch; -} // namespace web_server +// Maximum friendly name length for entities and sub-devices - keep in sync with FRIENDLY_NAME_MAX_LEN in +// esphome/core/config.py +static constexpr size_t ESPHOME_FRIENDLY_NAME_MAX_LEN = 120; + +// Maximum domain length (longest: "alarm_control_panel" = 19) +static constexpr size_t ESPHOME_DOMAIN_MAX_LEN = 20; + +// Maximum size for object_id buffer (friendly_name + null + margin) +static constexpr size_t OBJECT_ID_MAX_LEN = 128; + +// Maximum state length that Home Assistant will accept without raising ValueError +static constexpr size_t MAX_STATE_LEN = 255; enum EntityCategory : uint8_t { ENTITY_CATEGORY_NONE = 0, @@ -33,20 +41,37 @@ class EntityBase { // Get/set the name of this Entity const StringRef &get_name() const; void set_name(const char *name); + /// Set name with pre-computed object_id hash (avoids runtime hash calculation) + /// Use hash=0 for dynamic names that need runtime calculation + void set_name(const char *name, uint32_t object_id_hash); // Get whether this Entity has its own name or it should use the device friendly_name. bool has_own_name() const { return this->flags_.has_own_name; } // Get the sanitized name of this Entity as an ID. + // Deprecated: object_id mangles names and all object_id methods are planned for removal. + // See https://github.com/esphome/backlog/issues/76 + // Now is the time to stop using object_id entirely. If you still need it temporarily, + // use get_object_id_to() which will remain available longer but will also eventually be removed. + ESPDEPRECATED("object_id mangles names and all object_id methods are planned for removal " + "(see https://github.com/esphome/backlog/issues/76). " + "Now is the time to stop using object_id. If still needed, use get_object_id_to() " + "which will remain available longer. get_object_id() will be removed in 2026.7.0", + "2025.12.0") std::string get_object_id() const; - void set_object_id(const char *object_id); - - // Set both name and object_id in one call (reduces generated code size) - void set_name_and_object_id(const char *name, const char *object_id); // Get the unique Object ID of this Entity uint32_t get_object_id_hash(); + /// Get object_id with zero heap allocation + /// For static case: returns StringRef to internal storage (buffer unused) + /// For dynamic case: formats into buffer and returns StringRef to buffer + StringRef get_object_id_to(std::span buf) const; + + /// Write object_id directly to buffer, returns length written (excluding null) + /// Useful for building compound strings without intermediate buffer + size_t write_object_id_to(char *buf, size_t buf_size) const; + // Get/set whether this Entity should be hidden outside ESPHome bool is_internal() const { return this->flags_.internal; } void set_internal(bool internal) { this->flags_.internal = internal; } @@ -87,6 +112,8 @@ class EntityBase { return this->device_->get_device_id(); } void set_device(Device *device) { this->device_ = device; } + // Get the device this entity belongs to (nullptr if main device) + Device *get_device() const { return this->device_; } #endif // Check if this entity has state @@ -125,20 +152,9 @@ class EntityBase { } protected: - friend class api::APIConnection; - friend struct web_server::UrlMatch; - - // Get object_id as StringRef when it's static (for API usage) - // Returns empty StringRef if object_id is dynamic (needs allocation) - StringRef get_object_id_ref_for_api_() const; - void calc_object_id_(); - /// Check if the object_id is dynamic (changes with MAC suffix) - bool is_object_id_dynamic_() const; - StringRef name_; - const char *object_id_c_str_{nullptr}; #ifdef USE_ENTITY_ICON const char *icon_c_str_{nullptr}; #endif diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index f360b4d809..c1801c0bda 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -15,7 +15,7 @@ from esphome.const import ( from esphome.core import CORE, ID from esphome.cpp_generator import MockObj, add, get_variable import esphome.final_validate as fv -from esphome.helpers import sanitize, snake_case +from esphome.helpers import fnv1_hash_object_id, sanitize, snake_case from esphome.types import ConfigType, EntityMetadata _LOGGER = logging.getLogger(__name__) @@ -75,34 +75,18 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: config: Configuration dictionary containing entity settings platform: The platform name (e.g., "sensor", "binary_sensor") """ - # Get device info - device_name: str | None = None - device_id_obj: ID | None + # Get device info if configured if device_id_obj := config.get(CONF_DEVICE_ID): device: MockObj = await get_variable(device_id_obj) add(var.set_device(device)) - # Get device name for object ID calculation - device_name = device_id_obj.id - # Calculate base object_id using the same logic as C++ - # This must match the C++ behavior in esphome/core/entity_base.cpp - base_object_id = get_base_entity_object_id( - config[CONF_NAME], CORE.friendly_name, device_name - ) - - if not config[CONF_NAME]: - _LOGGER.debug( - "Entity has empty name, using '%s' as object_id base", base_object_id - ) - - # Set both name and object_id in one call to reduce generated code size - add(var.set_name_and_object_id(config[CONF_NAME], base_object_id)) - _LOGGER.debug( - "Setting object_id '%s' for entity '%s' on platform '%s'", - base_object_id, - config[CONF_NAME], - platform, - ) + # Set the entity name with pre-computed object_id hash + # For named entities: pre-compute hash from entity name + # For empty-name entities: pass 0, C++ calculates hash at runtime from + # device name, friendly_name, or app name (bug-for-bug compatibility) + entity_name = config[CONF_NAME] + object_id_hash = fnv1_hash_object_id(entity_name) if entity_name else 0 + add(var.set_name(entity_name, object_id_hash)) # Only set disabled_by_default if True (default is False) if config[CONF_DISABLED_BY_DEFAULT]: add(var.set_disabled_by_default(True)) diff --git a/esphome/core/gpio.cpp b/esphome/core/gpio.cpp new file mode 100644 index 0000000000..21e88b5b6d --- /dev/null +++ b/esphome/core/gpio.cpp @@ -0,0 +1,24 @@ +#include "esphome/core/gpio.h" +#include "esphome/core/log.h" + +namespace esphome { + +#ifdef USE_ESP8266 +void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin) { + if (pin == nullptr) + return; + static constexpr size_t LOG_PIN_PREFIX_MAX_LEN = 32; + char prefix_buf[LOG_PIN_PREFIX_MAX_LEN]; + strncpy_P(prefix_buf, reinterpret_cast(prefix), sizeof(prefix_buf) - 1); + prefix_buf[sizeof(prefix_buf) - 1] = '\0'; + log_pin_with_prefix(tag, prefix_buf, pin); +} +#else +void log_pin(const char *tag, const char *prefix, GPIOPin *pin) { + if (pin == nullptr) + return; + log_pin_with_prefix(tag, prefix, pin); +} +#endif + +} // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index dd6f14fef9..f2f85e18bc 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -1,13 +1,22 @@ #pragma once +#include #include +#include #include +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + namespace esphome { -#define LOG_PIN(prefix, pin) \ - if ((pin) != nullptr) { \ - ESP_LOGCONFIG(TAG, prefix "%s", (pin)->dump_summary().c_str()); \ - } +/// Maximum buffer size for dump_summary output +inline constexpr size_t GPIO_SUMMARY_MAX_LEN = 48; + +#ifdef USE_ESP8266 +#define LOG_PIN(prefix, pin) log_pin(TAG, F(prefix), pin) +#else +#define LOG_PIN(prefix, pin) log_pin(TAG, prefix, pin) +#endif // put GPIO flags in a namespace to not pollute esphome namespace namespace gpio { @@ -64,7 +73,17 @@ class GPIOPin { virtual void digital_write(bool value) = 0; - virtual std::string dump_summary() const = 0; + /// Write a summary of this pin to the provided buffer. + /// @param buffer The buffer to write to + /// @param len The size of the buffer (must be > 0) + /// @return The number of characters that would be written (excluding null terminator), + /// which may exceed len-1 if truncation occurred (snprintf semantics) + virtual size_t dump_summary(char *buffer, size_t len) const; + + /// Get a summary of this pin as a string. + /// @deprecated Use dump_summary(char*, size_t) instead. Will be removed in 2026.7.0. + ESPDEPRECATED("Override dump_summary(char*, size_t) instead. Will be removed in 2026.7.0.", "2026.1.0") + virtual std::string dump_summary() const; virtual bool is_internal() { return false; } }; @@ -103,4 +122,41 @@ class InternalGPIOPin : public GPIOPin { virtual void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; }; +// Inline default implementations for GPIOPin virtual methods. +// These provide bridge functionality for backwards compatibility with external components. + +// Default implementation bridges to old std::string method for backwards compatibility. +inline size_t GPIOPin::dump_summary(char *buffer, size_t len) const { + if (len == 0) + return 0; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + std::string s = this->dump_summary(); +#pragma GCC diagnostic pop + size_t copy_len = std::min(s.size(), len - 1); + memcpy(buffer, s.c_str(), copy_len); + buffer[copy_len] = '\0'; + return s.size(); // Return would-be length (snprintf semantics) +} + +// Default implementation returns empty string. +// External components should override this if they haven't migrated to buffer-based version. +// Remove before 2026.7.0 +inline std::string GPIOPin::dump_summary() const { return {}; } + +// Inline helper for log_pin - allows compiler to inline into log_pin in gpio.cpp +inline void log_pin_with_prefix(const char *tag, const char *prefix, GPIOPin *pin) { + char buffer[GPIO_SUMMARY_MAX_LEN]; + size_t len = pin->dump_summary(buffer, sizeof(buffer)); + len = std::min(len, sizeof(buffer) - 1); + esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, "%s%.*s", prefix, (int) len, buffer); +} + +// log_pin function declarations - implementation in gpio.cpp +#ifdef USE_ESP8266 +void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin); +#else +void log_pin(const char *tag, const char *prefix, GPIOPin *pin); +#endif + } // namespace esphome diff --git a/esphome/core/hal.h b/esphome/core/hal.h index 0ccf21ad83..1a4230e421 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -3,20 +3,12 @@ #include #include "gpio.h" -#if defined(USE_ESP32_FRAMEWORK_ESP_IDF) +#if defined(USE_ESP32) #include #ifndef PROGMEM #define PROGMEM #endif -#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) - -#include - -#ifndef PROGMEM -#define PROGMEM -#endif - #elif defined(USE_ESP8266) #include diff --git a/esphome/core/hash_base.h b/esphome/core/hash_base.h index c45c4df70b..0c1c2dce33 100644 --- a/esphome/core/hash_base.h +++ b/esphome/core/hash_base.h @@ -25,14 +25,8 @@ class HashBase { /// Retrieve the hash as bytes void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); } - /// Retrieve the hash as hex characters - void get_hex(char *output) { - for (size_t i = 0; i < this->get_size(); i++) { - uint8_t byte = this->digest_[i]; - output[i * 2] = format_hex_char(byte >> 4); - output[i * 2 + 1] = format_hex_char(byte & 0x0F); - } - } + /// Retrieve the hash as hex characters. Output buffer must hold get_size() * 2 + 1 bytes. + void get_hex(char *output) { format_hex_to(output, this->get_size() * 2 + 1, this->digest_, this->get_size()); } /// Compare the hash against a provided byte-encoded hash bool equals_bytes(const uint8_t *expected) { return memcmp(this->digest_, expected, this->get_size()) == 0; } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 732e8b6f8b..309407fbec 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -143,11 +143,12 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, return refout ? (crc ^ 0xffff) : crc; } +// FNV-1 hash - deprecated, use fnv1a_hash() for new code uint32_t fnv1_hash(const char *str) { - uint32_t hash = 2166136261UL; + uint32_t hash = FNV1_OFFSET_BASIS; if (str) { while (*str) { - hash *= 16777619UL; + hash *= FNV1_PRIME; hash ^= *str++; } } @@ -161,6 +162,9 @@ float random_float() { return static_cast(random_uint32()) / static_cast< bool str_equals_case_insensitive(const std::string &a, const std::string &b) { return strcasecmp(a.c_str(), b.c_str()) == 0; } +bool str_equals_case_insensitive(StringRef a, StringRef b) { + return a.size() == b.size() && strncasecmp(a.c_str(), b.c_str(), a.size()) == 0; +} #if __cplusplus >= 202002L bool str_startswith(const std::string &str, const std::string &start) { return str.starts_with(start); } bool str_endswith(const std::string &str, const std::string &end) { return str.ends_with(end); } @@ -189,21 +193,18 @@ template std::string str_ctype_transform(const std::string &str) std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } std::string str_snake_case(const std::string &str) { - std::string result; - result.resize(str.length()); - std::transform(str.begin(), str.end(), result.begin(), ::tolower); - std::replace(result.begin(), result.end(), ' ', '_'); + std::string result = str; + for (char &c : result) { + c = to_snake_case_char(c); + } return result; } std::string str_sanitize(const std::string &str) { - std::string out = str; - std::replace_if( - out.begin(), out.end(), - [](const char &c) { - return c != '-' && c != '_' && (c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z'); - }, - '_'); - return out; + std::string result = str; + for (char &c : result) { + c = to_sanitized_char(c); + } + return result; } std::string str_snprintf(const char *fmt, size_t len, ...) { std::string str; @@ -238,17 +239,16 @@ std::string str_sprintf(const char *fmt, ...) { // Maximum size for name with suffix: 120 (max friendly name) + 1 (separator) + 6 (MAC suffix) + 1 (null term) static constexpr size_t MAX_NAME_WITH_SUFFIX_SIZE = 128; -std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, - size_t suffix_len) { - char buffer[MAX_NAME_WITH_SUFFIX_SIZE]; +size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, + const char *suffix_ptr, size_t suffix_len) { size_t total_len = name_len + 1 + suffix_len; // Silently truncate if needed: prioritize keeping the full suffix - if (total_len >= MAX_NAME_WITH_SUFFIX_SIZE) { - // NOTE: This calculation could underflow if suffix_len >= MAX_NAME_WITH_SUFFIX_SIZE - 2, + if (total_len >= buffer_size) { + // NOTE: This calculation could underflow if suffix_len >= buffer_size - 2, // but this is safe because this helper is only called with small suffixes: // MAC suffixes (6-12 bytes), ".local" (5 bytes), etc. - name_len = MAX_NAME_WITH_SUFFIX_SIZE - suffix_len - 2; // -2 for separator and null terminator + name_len = buffer_size - suffix_len - 2; // -2 for separator and null terminator total_len = name_len + 1 + suffix_len; } @@ -256,7 +256,14 @@ std::string make_name_with_suffix(const char *name, size_t name_len, char sep, c buffer[name_len] = sep; memcpy(buffer + name_len + 1, suffix_ptr, suffix_len); buffer[total_len] = '\0'; - return std::string(buffer, total_len); + return total_len; +} + +std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, + size_t suffix_len) { + char buffer[MAX_NAME_WITH_SUFFIX_SIZE]; + size_t len = make_name_with_suffix_to(buffer, sizeof(buffer), name, name_len, sep, suffix_ptr, suffix_len); + return std::string(buffer, len); } std::string make_name_with_suffix(const std::string &name, char sep, const char *suffix_ptr, size_t suffix_len) { @@ -266,19 +273,12 @@ std::string make_name_with_suffix(const std::string &name, char sep, const char // Parsing & formatting size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { - uint8_t val; size_t chars = std::min(length, 2 * count); for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) { - if (*str >= '0' && *str <= '9') { - val = *str - '0'; - } else if (*str >= 'A' && *str <= 'F') { - val = 10 + (*str - 'A'); - } else if (*str >= 'a' && *str <= 'f') { - val = 10 + (*str - 'a'); - } else { + uint8_t val = parse_hex_char(*str); + if (val > 15) return 0; - } - data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; + data[i >> 1] = (i & 1) ? data[i >> 1] | val : val << 4; } return chars; } @@ -289,30 +289,91 @@ std::string format_mac_address_pretty(const uint8_t *mac) { return std::string(buf); } +// Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase +static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator, + char base) { + if (length == 0) { + buffer[0] = '\0'; + return buffer; + } + // With separator: total length is 3*length (2*length hex chars, (length-1) separators, 1 null terminator) + // Without separator: total length is 2*length + 1 (2*length hex chars, 1 null terminator) + uint8_t stride = separator ? 3 : 2; + size_t max_bytes = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride); + if (max_bytes == 0) { + buffer[0] = '\0'; + return buffer; + } + if (length > max_bytes) { + length = max_bytes; + } + for (size_t i = 0; i < length; i++) { + size_t pos = i * stride; + buffer[pos] = format_hex_char(data[i] >> 4, base); + buffer[pos + 1] = format_hex_char(data[i] & 0x0F, base); + if (separator && i < length - 1) { + buffer[pos + 2] = separator; + } + } + buffer[length * stride - (separator ? 1 : 0)] = '\0'; + return buffer; +} + +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { + return format_hex_internal(buffer, buffer_size, data, length, 0, 'a'); +} + std::string format_hex(const uint8_t *data, size_t length) { std::string ret; ret.resize(length * 2); - for (size_t i = 0; i < length; i++) { - ret[2 * i] = format_hex_char(data[i] >> 4); - ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); - } + format_hex_to(&ret[0], length * 2 + 1, data, length); return ret; } std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) { + return format_hex_internal(buffer, buffer_size, data, length, separator, 'A'); +} + +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint16_t *data, size_t length, char separator) { + if (length == 0 || buffer_size == 0) { + if (buffer_size > 0) + buffer[0] = '\0'; + return buffer; + } + // With separator: each uint16_t needs 5 chars (4 hex + 1 sep), except last has no separator + // Without separator: each uint16_t needs 4 chars, plus null terminator + uint8_t stride = separator ? 5 : 4; + size_t max_values = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride); + if (max_values == 0) { + buffer[0] = '\0'; + return buffer; + } + if (length > max_values) { + length = max_values; + } + for (size_t i = 0; i < length; i++) { + size_t pos = i * stride; + buffer[pos] = format_hex_pretty_char((data[i] & 0xF000) >> 12); + buffer[pos + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); + buffer[pos + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); + buffer[pos + 3] = format_hex_pretty_char(data[i] & 0x000F); + if (separator && i < length - 1) { + buffer[pos + 4] = separator; + } + } + buffer[length * stride - (separator ? 1 : 0)] = '\0'; + return buffer; +} + // Shared implementation for uint8_t and string hex formatting static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) { if (data == nullptr || length == 0) return ""; std::string ret; - uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise - ret.resize(multiple * length - (separator ? 1 : 0)); - for (size_t i = 0; i < length; i++) { - ret[multiple * i] = format_hex_pretty_char(data[i] >> 4); - ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F); - if (separator && i != length - 1) - ret[multiple * i + 2] = separator; - } + size_t hex_len = separator ? (length * 3 - 1) : (length * 2); + ret.resize(hex_len); + format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator); if (show_length && length > 4) return ret + " (" + std::to_string(length) + ")"; return ret; @@ -329,16 +390,9 @@ std::string format_hex_pretty(const uint16_t *data, size_t length, char separato if (data == nullptr || length == 0) return ""; std::string ret; - uint8_t multiple = separator ? 5 : 4; // 5 if separator is not \0, 4 otherwise - ret.resize(multiple * length - (separator ? 1 : 0)); - for (size_t i = 0; i < length; i++) { - ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12); - ret[multiple * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); - ret[multiple * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); - ret[multiple * i + 3] = format_hex_pretty_char(data[i] & 0x000F); - if (separator && i != length - 1) - ret[multiple * i + 4] = separator; - } + size_t hex_len = separator ? (length * 5 - 1) : (length * 4); + ret.resize(hex_len); + format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator); if (show_length && length > 4) return ret + " (" + std::to_string(length) + ")"; return ret; @@ -386,23 +440,33 @@ static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_de } std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - normalize_accuracy_decimals(value, accuracy_decimals); - char tmp[32]; // should be enough, but we should maybe improve this at some point. - snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); - return std::string(tmp); + char buf[VALUE_ACCURACY_MAX_LEN]; + value_accuracy_to_buf(buf, value, accuracy_decimals); + return std::string(buf); } -std::string value_accuracy_with_uom_to_string(float value, int8_t accuracy_decimals, StringRef unit_of_measurement) { +size_t value_accuracy_to_buf(std::span buf, float value, int8_t accuracy_decimals) { normalize_accuracy_decimals(value, accuracy_decimals); - // Buffer sized for float (up to ~15 chars) + space + typical UOM (usually <20 chars like "μS/cm") - // snprintf truncates safely if exceeded, though ESPHome UOMs are typically short - char tmp[64]; + // snprintf returns chars that would be written (excluding null), or negative on error + int len = snprintf(buf.data(), buf.size(), "%.*f", accuracy_decimals, value); + if (len < 0) + return 0; // encoding error + // On truncation, snprintf returns would-be length; actual written is buf.size() - 1 + return static_cast(len) >= buf.size() ? buf.size() - 1 : static_cast(len); +} + +size_t value_accuracy_with_uom_to_buf(std::span buf, float value, + int8_t accuracy_decimals, StringRef unit_of_measurement) { if (unit_of_measurement.empty()) { - snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); - } else { - snprintf(tmp, sizeof(tmp), "%.*f %s", accuracy_decimals, value, unit_of_measurement.c_str()); + return value_accuracy_to_buf(buf, value, accuracy_decimals); } - return std::string(tmp); + normalize_accuracy_decimals(value, accuracy_decimals); + // snprintf returns chars that would be written (excluding null), or negative on error + int len = snprintf(buf.data(), buf.size(), "%.*f %s", accuracy_decimals, value, unit_of_measurement.c_str()); + if (len < 0) + return 0; // encoding error + // On truncation, snprintf returns would-be length; actual written is buf.size() - 1 + return static_cast(len) >= buf.size() ? buf.size() - 1 : static_cast(len); } int8_t step_to_accuracy_decimals(float step) { @@ -480,10 +544,14 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) { } size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) { - int in_len = encoded_string.size(); + return base64_decode(reinterpret_cast(encoded_string.data()), encoded_string.size(), buf, buf_len); +} + +size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len) { + size_t in_len = encoded_len; int i = 0; int j = 0; - int in = 0; + size_t in = 0; size_t out = 0; uint8_t char_array_4[4], char_array_3[3]; bool truncated = false; @@ -491,8 +559,8 @@ size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf // SAFETY: The loop condition checks is_base64() before processing each character. // This ensures base64_find_char() is only called on valid base64 characters, // preventing the edge case where invalid chars would return 0 (same as 'A'). - while (in_len-- && (encoded_string[in] != '=') && is_base64(encoded_string[in])) { - char_array_4[i++] = encoded_string[in]; + while (in_len-- && (encoded_data[in] != '=') && is_base64(encoded_data[in])) { + char_array_4[i++] = encoded_data[in]; in++; if (i == 4) { for (i = 0; i < 4; i++) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6054f03353..2e9c0e6b13 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -162,6 +162,10 @@ template class StaticVector { size_t size() const { return count_; } bool empty() const { return count_ == 0; } + // Direct access to underlying data + T *data() { return data_.data(); } + const T *data() const { return data_.data(); } + T &operator[](size_t i) { return data_[i]; } const T &operator[](size_t i) const { return data_[i]; } @@ -289,8 +293,8 @@ template class FixedVector { size_ = 0; } - // Shrink capacity to fit current size (frees all memory) - void shrink_to_fit() { + // Release all memory (destroys elements and frees memory) + void release() { cleanup_(); reset_(); } @@ -378,9 +382,42 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc = 0, uint16_t p bool refout = false); /// Calculate a FNV-1 hash of \p str. +/// Note: FNV-1a (fnv1a_hash) is preferred for new code due to better avalanche characteristics. uint32_t fnv1_hash(const char *str); inline uint32_t fnv1_hash(const std::string &str) { return fnv1_hash(str.c_str()); } +/// FNV-1 32-bit offset basis +constexpr uint32_t FNV1_OFFSET_BASIS = 2166136261UL; +/// FNV-1 32-bit prime +constexpr uint32_t FNV1_PRIME = 16777619UL; + +/// Extend a FNV-1a hash with additional string data. +constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str) { + if (str) { + while (*str) { + hash ^= *str++; + hash *= FNV1_PRIME; + } + } + return hash; +} +inline uint32_t fnv1a_hash_extend(uint32_t hash, const std::string &str) { + return fnv1a_hash_extend(hash, str.c_str()); +} +/// Extend a FNV-1a hash with an integer (hashes each byte). +template constexpr uint32_t fnv1a_hash_extend(uint32_t hash, T value) { + using UnsignedT = std::make_unsigned_t; + UnsignedT uvalue = static_cast(value); + for (size_t i = 0; i < sizeof(T); i++) { + hash ^= (uvalue >> (i * 8)) & 0xFF; + hash *= FNV1_PRIME; + } + return hash; +} +/// Calculate a FNV-1a hash of \p str. +constexpr uint32_t fnv1a_hash(const char *str) { return fnv1a_hash_extend(FNV1_OFFSET_BASIS, str); } +inline uint32_t fnv1a_hash(const std::string &str) { return fnv1a_hash(str.c_str()); } + /// Return a random 32-bit unsigned integer. uint32_t random_uint32(); /// Return a random float between 0 and 1. @@ -474,6 +511,8 @@ template constexpr T convert_little_endian(T val) { /// Compare strings for equality in case-insensitive manner. bool str_equals_case_insensitive(const std::string &a, const std::string &b); +/// Compare StringRefs for equality in case-insensitive manner. +bool str_equals_case_insensitive(StringRef a, StringRef b); /// Check whether a string starts with a value. bool str_startswith(const std::string &str, const std::string &start); @@ -481,6 +520,7 @@ bool str_startswith(const std::string &str, const std::string &start); bool str_endswith(const std::string &str, const std::string &end); /// Truncate a string to a specific length. +/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices. std::string str_truncate(const std::string &str, size_t length); /// Extract the part of the string until either the first occurrence of the specified character, or the end @@ -492,13 +532,36 @@ std::string str_until(const std::string &str, char ch); /// Convert the string to lower case. std::string str_lower_case(const std::string &str); /// Convert the string to upper case. +/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices. std::string str_upper_case(const std::string &str); + +/// Convert a single char to snake_case: lowercase and space to underscore. +constexpr char to_snake_case_char(char c) { return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; } /// Convert the string to snake case (lowercase with underscores). +/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices. std::string str_snake_case(const std::string &str); +/// Sanitize a single char: keep alphanumerics, dashes, underscores; replace others with underscore. +constexpr char to_sanitized_char(char c) { + return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_'; +} /// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. std::string str_sanitize(const std::string &str); +/// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations. +/// This computes object_id hashes directly from names without creating an intermediate buffer. +/// IMPORTANT: Must match Python fnv1_hash_object_id() in esphome/helpers.py. +/// If you modify this function, update the Python version and tests in both places. +inline uint32_t fnv1_hash_object_id(const char *str, size_t len) { + uint32_t hash = FNV1_OFFSET_BASIS; + for (size_t i = 0; i < len; i++) { + hash *= FNV1_PRIME; + // Apply snake_case (space->underscore, uppercase->lowercase) then sanitize + hash ^= static_cast(to_sanitized_char(to_snake_case_char(str[i]))); + } + return hash; +} + /// snprintf-like function returning std::string of maximum length \p len (excluding null terminator). std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...); @@ -526,6 +589,18 @@ std::string make_name_with_suffix(const std::string &name, char sep, const char std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, size_t suffix_len); +/// Zero-allocation version: format name + separator + suffix directly into buffer. +/// @param buffer Output buffer (must have space for result + null terminator) +/// @param buffer_size Size of the output buffer +/// @param name The base name string +/// @param name_len Length of the name +/// @param sep Single character separator +/// @param suffix_ptr Pointer to the suffix characters +/// @param suffix_len Length of the suffix +/// @return Length written (excluding null terminator) +size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, + const char *suffix_ptr, size_t suffix_len); + ///@} /// @name Parsing & formatting @@ -624,46 +699,203 @@ template::value, int> = 0> optional< return parse_hex(str.c_str(), str.length()); } +/// Parse a hex character to its nibble value (0-15), returns 255 on invalid input +constexpr uint8_t parse_hex_char(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 255; +} + +/// Convert a nibble (0-15) to hex char with specified base ('a' for lowercase, 'A' for uppercase) +inline char format_hex_char(uint8_t v, char base) { return v >= 10 ? base + (v - 10) : '0' + v; } + /// Convert a nibble (0-15) to lowercase hex char -inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } +inline char format_hex_char(uint8_t v) { return format_hex_char(v, 'a'); } /// Convert a nibble (0-15) to uppercase hex char (used for pretty printing) -/// This always uses uppercase (A-F) for pretty/human-readable output -inline char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +inline char format_hex_pretty_char(uint8_t v) { return format_hex_char(v, 'A'); } -/// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase) -inline void format_mac_addr_upper(const uint8_t *mac, char *output) { - for (size_t i = 0; i < 6; i++) { - uint8_t byte = mac[i]; - output[i * 3] = format_hex_pretty_char(byte >> 4); - output[i * 3 + 1] = format_hex_pretty_char(byte & 0x0F); - if (i < 5) - output[i * 3 + 2] = ':'; +/// Write int8 value to buffer without modulo operations. +/// Buffer must have at least 4 bytes free. Returns pointer past last char written. +inline char *int8_to_str(char *buf, int8_t val) { + int32_t v = val; + if (v < 0) { + *buf++ = '-'; + v = -v; } - output[17] = '\0'; + if (v >= 100) { + *buf++ = '1'; // int8 max is 128, so hundreds digit is always 1 + v -= 100; + // Must write tens digit (even if 0) after hundreds + int32_t tens = v / 10; + *buf++ = '0' + tens; + v -= tens * 10; + } else if (v >= 10) { + int32_t tens = v / 10; + *buf++ = '0' + tens; + v -= tens * 10; + } + *buf++ = '0' + v; + return buf; +} + +/// Format byte array as lowercase hex to buffer (base implementation). +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length); + +/// Format byte array as lowercase hex to buffer. Automatically deduces buffer size. +/// Truncates output if data exceeds buffer capacity. Returns pointer to buffer. +template inline char *format_hex_to(char (&buffer)[N], const uint8_t *data, size_t length) { + static_assert(N >= 3, "Buffer must hold at least one hex byte (3 chars)"); + return format_hex_to(buffer, N, data, length); +} + +/// Format an unsigned integer in lowercased hex to buffer, starting with the most significant byte. +template::value, int> = 0> +inline char *format_hex_to(char (&buffer)[N], T val) { + static_assert(N >= sizeof(T) * 2 + 1, "Buffer too small for type"); + val = convert_big_endian(val); + return format_hex_to(buffer, reinterpret_cast(&val), sizeof(T)); +} + +/// Format std::vector as lowercase hex to buffer. +template inline char *format_hex_to(char (&buffer)[N], const std::vector &data) { + return format_hex_to(buffer, data.data(), data.size()); +} + +/// Format std::array as lowercase hex to buffer. +template inline char *format_hex_to(char (&buffer)[N], const std::array &data) { + return format_hex_to(buffer, data.data(), data.size()); +} + +/// Calculate buffer size needed for format_hex_to: "XXXXXXXX...\0" = bytes * 2 + 1 +constexpr size_t format_hex_size(size_t byte_count) { return byte_count * 2 + 1; } + +/// Calculate buffer size needed for format_hex_prefixed_to: "0xXXXXXXXX...\0" = bytes * 2 + 3 +constexpr size_t format_hex_prefixed_size(size_t byte_count) { return byte_count * 2 + 3; } + +/// Format an unsigned integer as "0x" prefixed lowercase hex to buffer. +template::value, int> = 0> +inline char *format_hex_prefixed_to(char (&buffer)[N], T val) { + static_assert(N >= sizeof(T) * 2 + 3, "Buffer too small for prefixed hex"); + buffer[0] = '0'; + buffer[1] = 'x'; + val = convert_big_endian(val); + format_hex_to(buffer + 2, N - 2, reinterpret_cast(&val), sizeof(T)); + return buffer; +} + +/// Format byte array as "0x" prefixed lowercase hex to buffer. +template inline char *format_hex_prefixed_to(char (&buffer)[N], const uint8_t *data, size_t length) { + static_assert(N >= 5, "Buffer must hold at least '0x' + one hex byte + null"); + buffer[0] = '0'; + buffer[1] = 'x'; + format_hex_to(buffer + 2, N - 2, data, length); + return buffer; +} + +/// Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0" +constexpr size_t format_hex_pretty_size(size_t byte_count) { return byte_count * 3; } + +/** Format byte array as uppercase hex to buffer (base implementation). + * + * @param buffer Output buffer to write to. + * @param buffer_size Size of the output buffer. + * @param data Pointer to the byte array to format. + * @param length Number of bytes in the array. + * @param separator Character to use between hex bytes, or '\0' for no separator. + * @return Pointer to buffer. + * + * Buffer size needed: length * 3 with separator (for "XX:XX:XX\0"), length * 2 + 1 without. + */ +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator = ':'); + +/// Format byte array as uppercase hex with separator to buffer. Automatically deduces buffer size. +template +inline char *format_hex_pretty_to(char (&buffer)[N], const uint8_t *data, size_t length, char separator = ':') { + static_assert(N >= 3, "Buffer must hold at least one hex byte"); + return format_hex_pretty_to(buffer, N, data, length, separator); +} + +/// Format std::vector as uppercase hex with separator to buffer. +template +inline char *format_hex_pretty_to(char (&buffer)[N], const std::vector &data, char separator = ':') { + return format_hex_pretty_to(buffer, data.data(), data.size(), separator); +} + +/// Format std::array as uppercase hex with separator to buffer. +template +inline char *format_hex_pretty_to(char (&buffer)[N], const std::array &data, char separator = ':') { + return format_hex_pretty_to(buffer, data.data(), data.size(), separator); +} + +/// Calculate buffer size needed for format_hex_pretty_to with uint16_t data: "XXXX:XXXX:...:XXXX\0" +constexpr size_t format_hex_pretty_uint16_size(size_t count) { return count * 5; } + +/** + * Format uint16_t array as uppercase hex with separator to pre-allocated buffer. + * Each uint16_t is formatted as 4 hex chars in big-endian order. + * + * @param buffer Output buffer to write to. + * @param buffer_size Size of the output buffer. + * @param data Pointer to uint16_t array. + * @param length Number of uint16_t values. + * @param separator Character to use between values, or '\0' for no separator. + * @return Pointer to buffer. + * + * Buffer size needed: length * 5 with separator (for "XXXX:XXXX\0"), length * 4 + 1 without. + */ +char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint16_t *data, size_t length, char separator = ':'); + +/// Format uint16_t array as uppercase hex with separator to buffer. Automatically deduces buffer size. +template +inline char *format_hex_pretty_to(char (&buffer)[N], const uint16_t *data, size_t length, char separator = ':') { + static_assert(N >= 5, "Buffer must hold at least one hex uint16_t"); + return format_hex_pretty_to(buffer, N, data, length, separator); +} + +/// MAC address size in bytes +static constexpr size_t MAC_ADDRESS_SIZE = 6; +/// Buffer size for MAC address with separators: "XX:XX:XX:XX:XX:XX\0" +static constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = format_hex_pretty_size(MAC_ADDRESS_SIZE); +/// Buffer size for MAC address without separators: "XXXXXXXXXXXX\0" +static constexpr size_t MAC_ADDRESS_BUFFER_SIZE = MAC_ADDRESS_SIZE * 2 + 1; + +/// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators) +inline char *format_mac_addr_upper(const uint8_t *mac, char *output) { + return format_hex_pretty_to(output, MAC_ADDRESS_PRETTY_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE, ':'); } /// Format MAC address as xxxxxxxxxxxxxx (lowercase, no separators) inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { - for (size_t i = 0; i < 6; i++) { - uint8_t byte = mac[i]; - output[i * 2] = format_hex_char(byte >> 4); - output[i * 2 + 1] = format_hex_char(byte & 0x0F); - } - output[12] = '\0'; + format_hex_to(output, MAC_ADDRESS_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE); } /// Format the six-byte array \p mac into a MAC address. +/// @warning Allocates heap memory. Use format_mac_addr_upper() with a stack buffer instead. +/// Causes heap fragmentation on long-running devices. std::string format_mac_address_pretty(const uint8_t mac[6]); /// Format the byte array \p data of length \p len in lowercased hex. +/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. +/// Causes heap fragmentation on long-running devices. std::string format_hex(const uint8_t *data, size_t length); /// Format the vector \p data in lowercased hex. +/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. +/// Causes heap fragmentation on long-running devices. std::string format_hex(const std::vector &data); /// Format an unsigned integer in lowercased hex, starting with the most significant byte. +/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. +/// Causes heap fragmentation on long-running devices. template::value, int> = 0> std::string format_hex(T val) { val = convert_big_endian(val); return format_hex(reinterpret_cast(&val), sizeof(T)); } +/// Format the std::array \p data in lowercased hex. +/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. +/// Causes heap fragmentation on long-running devices. template std::string format_hex(const std::array &data) { return format_hex(data.data(), data.size()); } @@ -831,10 +1063,18 @@ enum ParseOnOffState : uint8_t { /// Parse a string that contains either on, off or toggle. ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); -/// Create a string from a value and an accuracy in decimals. +/// @deprecated Allocates heap memory. Use value_accuracy_to_buf() instead. Removed in 2026.7.0. +ESPDEPRECATED("Allocates heap memory. Use value_accuracy_to_buf() instead. Removed in 2026.7.0.", "2026.1.0") std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); -/// Create a string from a value, an accuracy in decimals, and a unit of measurement. -std::string value_accuracy_with_uom_to_string(float value, int8_t accuracy_decimals, StringRef unit_of_measurement); + +/// Maximum buffer size for value_accuracy formatting (float ~15 chars + space + UOM ~40 chars + null) +static constexpr size_t VALUE_ACCURACY_MAX_LEN = 64; + +/// Format value with accuracy to buffer, returns chars written (excluding null) +size_t value_accuracy_to_buf(std::span buf, float value, int8_t accuracy_decimals); +/// Format value with accuracy and UOM to buffer, returns chars written (excluding null) +size_t value_accuracy_with_uom_to_buf(std::span buf, float value, + int8_t accuracy_decimals, StringRef unit_of_measurement); /// Derive accuracy in decimals from an increment step. int8_t step_to_accuracy_decimals(float step); @@ -844,6 +1084,7 @@ std::string base64_encode(const std::vector &buf); std::vector base64_decode(const std::string &encoded_string); size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len); +size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len); ///@} @@ -900,6 +1141,50 @@ template class CallbackManager { std::vector> callbacks_; }; +template class LazyCallbackManager; + +/** Lazy-allocating callback manager that only allocates memory when callbacks are registered. + * + * This is a drop-in replacement for CallbackManager that saves memory when no callbacks + * are registered (common case after the Controller Registry eliminated per-entity callbacks + * from API and web_server components). + * + * Memory overhead comparison (32-bit systems): + * - CallbackManager: 12 bytes (empty std::vector) + * - LazyCallbackManager: 4 bytes (nullptr unique_ptr) + * + * @tparam Ts The arguments for the callbacks, wrapped in void(). + */ +template class LazyCallbackManager { + public: + /// Add a callback to the list. Allocates the underlying CallbackManager on first use. + void add(std::function &&callback) { + if (!this->callbacks_) { + this->callbacks_ = make_unique>(); + } + this->callbacks_->add(std::move(callback)); + } + + /// Call all callbacks in this manager. No-op if no callbacks registered. + void call(Ts... args) { + if (this->callbacks_) { + this->callbacks_->call(args...); + } + } + + /// Return the number of registered callbacks. + size_t size() const { return this->callbacks_ ? this->callbacks_->size() : 0; } + + /// Check if any callbacks are registered. + bool empty() const { return !this->callbacks_ || this->callbacks_->size() == 0; } + + /// Call all callbacks in this manager. + void operator()(Ts... args) { this->call(args...); } + + protected: + std::unique_ptr> callbacks_; +}; + /// Helper class to deduplicate items in a series of values. template class Deduplicator { public: @@ -1056,16 +1341,14 @@ class HighFrequencyLoopRequester { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); // NOLINT(readability-non-const-parameter) -/// Buffer size for MAC address in lowercase hex notation (12 hex chars + null terminator) -constexpr size_t MAC_ADDRESS_BUFFER_SIZE = 13; - -/// Buffer size for MAC address in colon-separated uppercase hex notation (17 chars + null terminator) -constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = 18; - /// Get the device MAC address as a string, in lowercase hex notation. +/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices. +/// Use get_mac_address_into_buffer() instead. std::string get_mac_address(); /// Get the device MAC address as a string, in colon-separated uppercase hex notation. +/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices. +/// Use get_mac_address_pretty_into_buffer() instead. std::string get_mac_address_pretty(); /// Get the device MAC address into the given buffer, in lowercase hex notation. diff --git a/esphome/core/lock_free_queue.h b/esphome/core/lock_free_queue.h index 68e2825d09..e96b739b58 100644 --- a/esphome/core/lock_free_queue.h +++ b/esphome/core/lock_free_queue.h @@ -1,12 +1,12 @@ #pragma once -#if defined(USE_ESP32) - #include #include +#ifdef USE_ESP32 #include #include +#endif /* * Lock-free queue for single-producer single-consumer scenarios. @@ -95,7 +95,7 @@ template class LockFreeQueue { } protected: - T *buffer_[SIZE]; + T *buffer_[SIZE]{}; // Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset) std::atomic dropped_count_; // 65535 max - more than enough for drop tracking // Atomic: written by consumer (pop), read by producer (push) to check if full @@ -106,6 +106,7 @@ template class LockFreeQueue { std::atomic tail_; }; +#ifdef USE_ESP32 // Extended queue with task notification support template class NotifyingLockFreeQueue : public LockFreeQueue { public: @@ -140,7 +141,6 @@ template class NotifyingLockFreeQueue : public LockFreeQu private: TaskHandle_t task_to_notify_; }; +#endif } // namespace esphome - -#endif // defined(USE_ESP32) diff --git a/esphome/core/log.cpp b/esphome/core/log.cpp index 909319dd28..8338efbb33 100644 --- a/esphome/core/log.cpp +++ b/esphome/core/log.cpp @@ -46,7 +46,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr } #endif -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#ifdef USE_ESP32 int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT #ifdef USE_LOGGER auto *log = logger::global_logger; diff --git a/esphome/core/log.h b/esphome/core/log.h index cade6a74c1..a2c4b35c6e 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -14,13 +14,10 @@ #endif // Include ESP-IDF/Arduino based logging methods here so they don't undefine ours later -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ESP32) #include #include #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include -#endif #ifdef USE_LIBRETINY #include #endif @@ -66,7 +63,7 @@ void esp_log_vprintf_(int level, const char *tag, int line, const char *format, #ifdef USE_STORE_LOG_STR_IN_FLASH void esp_log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); #endif -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ESP32) int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #endif diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index f9508945e8..fe9c9b5a75 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -8,9 +8,15 @@ // ESP8266 uses Arduino macros #define ESPHOME_F(string_literal) F(string_literal) #define ESPHOME_PGM_P PGM_P +#define ESPHOME_PSTR(s) PSTR(s) #define ESPHOME_strncpy_P strncpy_P +#define ESPHOME_strncat_P strncat_P +#define ESPHOME_snprintf_P snprintf_P #else #define ESPHOME_F(string_literal) (string_literal) #define ESPHOME_PGM_P const char * +#define ESPHOME_PSTR(s) (s) #define ESPHOME_strncpy_P strncpy +#define ESPHOME_strncat_P strncat +#define ESPHOME_snprintf_P snprintf #endif diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 8b713523b6..047bf4ef17 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -5,6 +5,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include #include #include @@ -32,6 +33,34 @@ static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits::max() // max delay to start an interval sequence static constexpr uint32_t MAX_INTERVAL_DELAY = 5000; +#if defined(ESPHOME_LOG_HAS_VERBOSE) || defined(ESPHOME_DEBUG_SCHEDULER) +// Helper struct for formatting scheduler item names consistently in logs +// Uses a stack buffer to avoid heap allocation +// Uses ESPHOME_snprintf_P/ESPHOME_PSTR for ESP8266 to keep format strings in flash +struct SchedulerNameLog { + char buffer[20]; // Enough for "id:4294967295" or "hash:0xFFFFFFFF" or "(null)" + + // Format a scheduler item name for logging + // Returns pointer to formatted string (either static_name or internal buffer) + const char *format(Scheduler::NameType name_type, const char *static_name, uint32_t hash_or_id) { + using NameType = Scheduler::NameType; + if (name_type == NameType::STATIC_STRING) { + if (static_name) + return static_name; + // Copy "(null)" to buffer to keep it in flash on ESP8266 + ESPHOME_strncpy_P(buffer, ESPHOME_PSTR("(null)"), sizeof(buffer)); + return buffer; + } else if (name_type == NameType::HASHED_STRING) { + ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("hash:0x%08" PRIX32), hash_or_id); + return buffer; + } else { // NUMERIC_ID + ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("id:%" PRIu32), hash_or_id); + return buffer; + } + } +}; +#endif + // Uncomment to debug scheduler // #define ESPHOME_DEBUG_SCHEDULER @@ -76,17 +105,15 @@ static void validate_static_string(const char *name) { // avoid the main thread modifying the list while it is being accessed. // Common implementation for both timeout and interval -void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, - const void *name_ptr, uint32_t delay, std::function func, bool is_retry, - bool skip_cancel) { - // Get the name as const char* - const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); - +// name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id +void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type, + const char *static_name, uint32_t hash_or_id, uint32_t delay, + std::function func, bool is_retry, bool skip_cancel) { if (delay == SCHEDULER_DONT_RUN) { - // Still need to cancel existing timer if name is not empty + // Still need to cancel existing timer if we have a name/id if (!skip_cancel) { LockGuard guard{this->lock_}; - this->cancel_item_locked_(component, name_cstr, type); + this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type); } return; } @@ -98,23 +125,19 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type LockGuard guard{this->lock_}; // Create and populate the scheduler item - std::unique_ptr item; - if (!this->scheduler_item_pool_.empty()) { - // Reuse from pool - item = std::move(this->scheduler_item_pool_.back()); - this->scheduler_item_pool_.pop_back(); -#ifdef ESPHOME_DEBUG_SCHEDULER - ESP_LOGD(TAG, "Reused item from pool (pool size now: %zu)", this->scheduler_item_pool_.size()); -#endif - } else { - // Allocate new if pool is empty - item = make_unique(); -#ifdef ESPHOME_DEBUG_SCHEDULER - ESP_LOGD(TAG, "Allocated new item (pool empty)"); -#endif - } + auto item = this->get_item_from_pool_locked_(); item->component = component; - item->set_name(name_cstr, !is_static_string); + switch (name_type) { + case NameType::STATIC_STRING: + item->set_static_name(static_name); + break; + case NameType::HASHED_STRING: + item->set_hashed_name(hash_or_id); + break; + case NameType::NUMERIC_ID: + item->set_numeric_id(hash_or_id); + break; + } item->type = type; item->callback = std::move(func); // Reset remove flag - recycled items may have been cancelled (remove=true) in previous use @@ -127,7 +150,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type if (delay == 0 && type == SchedulerItem::TIMEOUT) { // Put in defer queue for guaranteed FIFO execution if (!skip_cancel) { - this->cancel_item_locked_(component, name_cstr, type); + this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type); } this->defer_queue_.push_back(std::move(item)); return; @@ -141,24 +164,32 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type // Calculate random offset (0 to min(interval/2, 5s)) uint32_t offset = (uint32_t) (std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float()); item->set_next_execution(now + offset); - ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", name_cstr ? name_cstr : "", delay, - offset); +#ifdef ESPHOME_LOG_HAS_VERBOSE + SchedulerNameLog name_log; + ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", + name_log.format(name_type, static_name, hash_or_id), delay, offset); +#endif } else { item->interval = 0; item->set_next_execution(now + delay); } #ifdef ESPHOME_DEBUG_SCHEDULER - this->debug_log_timer_(item.get(), is_static_string, name_cstr, type, delay, now); + this->debug_log_timer_(item.get(), name_type, static_name, hash_or_id, type, delay, now); #endif /* ESPHOME_DEBUG_SCHEDULER */ // For retries, check if there's a cancelled timeout first - if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT && - (has_cancelled_timeout_in_container_locked_(this->items_, component, name_cstr, /* match_retry= */ true) || - has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_cstr, /* match_retry= */ true))) { + // Skip check for anonymous retries (STATIC_STRING with nullptr) - they can't be cancelled by name + if (is_retry && (name_type != NameType::STATIC_STRING || static_name != nullptr) && type == SchedulerItem::TIMEOUT && + (has_cancelled_timeout_in_container_locked_(this->items_, component, name_type, static_name, hash_or_id, + /* match_retry= */ true) || + has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_type, static_name, hash_or_id, + /* match_retry= */ true))) { // Skip scheduling - the retry was cancelled #ifdef ESPHOME_DEBUG_SCHEDULER - ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr); + SchedulerNameLog skip_name_log; + ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", + skip_name_log.format(name_type, static_name, hash_or_id)); #endif return; } @@ -166,7 +197,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type // If name is provided, do atomic cancel-and-add (unless skip_cancel is true) // Cancel existing items if (!skip_cancel) { - this->cancel_item_locked_(component, name_cstr, type); + this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type); } // Add new item directly to to_add_ // since we have the lock held @@ -174,33 +205,51 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type } void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function func) { - this->set_timer_common_(component, SchedulerItem::TIMEOUT, true, name, timeout, std::move(func)); + this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::STATIC_STRING, name, 0, timeout, + std::move(func)); } void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function func) { - this->set_timer_common_(component, SchedulerItem::TIMEOUT, false, &name, timeout, std::move(func)); + this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::HASHED_STRING, nullptr, fnv1a_hash(name), + timeout, std::move(func)); +} +void HOT Scheduler::set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function func) { + this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID, nullptr, id, timeout, + std::move(func)); } bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name) { - return this->cancel_item_(component, false, &name, SchedulerItem::TIMEOUT); + return this->cancel_item_(component, NameType::HASHED_STRING, nullptr, fnv1a_hash(name), SchedulerItem::TIMEOUT); } bool HOT Scheduler::cancel_timeout(Component *component, const char *name) { - return this->cancel_item_(component, true, name, SchedulerItem::TIMEOUT); + return this->cancel_item_(component, NameType::STATIC_STRING, name, 0, SchedulerItem::TIMEOUT); +} +bool HOT Scheduler::cancel_timeout(Component *component, uint32_t id) { + return this->cancel_item_(component, NameType::NUMERIC_ID, nullptr, id, SchedulerItem::TIMEOUT); } void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, std::function func) { - this->set_timer_common_(component, SchedulerItem::INTERVAL, false, &name, interval, std::move(func)); + this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::HASHED_STRING, nullptr, fnv1a_hash(name), + interval, std::move(func)); } void HOT Scheduler::set_interval(Component *component, const char *name, uint32_t interval, std::function func) { - this->set_timer_common_(component, SchedulerItem::INTERVAL, true, name, interval, std::move(func)); + this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::STATIC_STRING, name, 0, interval, + std::move(func)); +} +void HOT Scheduler::set_interval(Component *component, uint32_t id, uint32_t interval, std::function func) { + this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID, nullptr, id, interval, + std::move(func)); } bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) { - return this->cancel_item_(component, false, &name, SchedulerItem::INTERVAL); + return this->cancel_item_(component, NameType::HASHED_STRING, nullptr, fnv1a_hash(name), SchedulerItem::INTERVAL); } bool HOT Scheduler::cancel_interval(Component *component, const char *name) { - return this->cancel_item_(component, true, name, SchedulerItem::INTERVAL); + return this->cancel_item_(component, NameType::STATIC_STRING, name, 0, SchedulerItem::INTERVAL); +} +bool HOT Scheduler::cancel_interval(Component *component, uint32_t id) { + return this->cancel_item_(component, NameType::NUMERIC_ID, nullptr, id, SchedulerItem::INTERVAL); } struct RetryArgs { @@ -208,17 +257,15 @@ struct RetryArgs { std::function func; Component *component; Scheduler *scheduler; - const char *name; // Points to static string or owned copy + // Union for name storage - only one is used based on name_type + union { + const char *static_name; // For STATIC_STRING + uint32_t hash_or_id; // For HASHED_STRING or NUMERIC_ID + } name_; uint32_t current_interval; float backoff_increase_factor; + Scheduler::NameType name_type; // Discriminator for name_ union uint8_t retry_countdown; - bool name_is_dynamic; // True if name needs delete[] - - ~RetryArgs() { - if (this->name_is_dynamic && this->name) { - delete[] this->name; - } - } }; void retry_handler(const std::shared_ptr &args) { @@ -226,31 +273,38 @@ void retry_handler(const std::shared_ptr &args) { if (retry_result == RetryResult::DONE || args->retry_countdown <= 0) return; // second execution of `func` happens after `initial_wait_time` - // Pass is_static_string=true because args->name is owned by the shared_ptr + // args->name_ is owned by the shared_ptr // which is captured in the lambda and outlives the SchedulerItem + const char *static_name = (args->name_type == Scheduler::NameType::STATIC_STRING) ? args->name_.static_name : nullptr; + uint32_t hash_or_id = (args->name_type != Scheduler::NameType::STATIC_STRING) ? args->name_.hash_or_id : 0; args->scheduler->set_timer_common_( - args->component, Scheduler::SchedulerItem::TIMEOUT, true, args->name, args->current_interval, - [args]() { retry_handler(args); }, /* is_retry= */ true); + args->component, Scheduler::SchedulerItem::TIMEOUT, args->name_type, static_name, hash_or_id, + args->current_interval, [args]() { retry_handler(args); }, + /* is_retry= */ true); // backoff_increase_factor applied to third & later executions args->current_interval *= args->backoff_increase_factor; } -void HOT Scheduler::set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, - uint32_t initial_wait_time, uint8_t max_attempts, +void HOT Scheduler::set_retry_common_(Component *component, NameType name_type, const char *static_name, + uint32_t hash_or_id, uint32_t initial_wait_time, uint8_t max_attempts, std::function func, float backoff_increase_factor) { - const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); - - if (name_cstr != nullptr) - this->cancel_retry(component, name_cstr); + this->cancel_retry_(component, name_type, static_name, hash_or_id); if (initial_wait_time == SCHEDULER_DONT_RUN) return; - ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)", - name_cstr ? name_cstr : "", initial_wait_time, max_attempts, backoff_increase_factor); +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE + { + SchedulerNameLog name_log; + ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)", + name_log.format(name_type, static_name, hash_or_id), initial_wait_time, max_attempts, + backoff_increase_factor); + } +#endif if (backoff_increase_factor < 0.0001) { - ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name_cstr ? name_cstr : ""); + ESP_LOGE(TAG, "set_retry: backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, + (name_type == NameType::STATIC_STRING && static_name) ? static_name : ""); backoff_increase_factor = 1; } @@ -258,56 +312,56 @@ void HOT Scheduler::set_retry_common_(Component *component, bool is_static_strin args->func = std::move(func); args->component = component; args->scheduler = this; + args->name_type = name_type; + if (name_type == NameType::STATIC_STRING) { + args->name_.static_name = static_name; + } else { + args->name_.hash_or_id = hash_or_id; + } args->current_interval = initial_wait_time; args->backoff_increase_factor = backoff_increase_factor; args->retry_countdown = max_attempts; - // Store name - either as static pointer or owned copy - if (name_cstr == nullptr || name_cstr[0] == '\0') { - // Empty or null name - use empty string literal - args->name = ""; - args->name_is_dynamic = false; - } else if (is_static_string) { - // Static string - just store the pointer - args->name = name_cstr; - args->name_is_dynamic = false; - } else { - // Dynamic string - make a copy - size_t len = strlen(name_cstr); - char *copy = new char[len + 1]; - memcpy(copy, name_cstr, len + 1); - args->name = copy; - args->name_is_dynamic = true; - } - // First execution of `func` immediately - use set_timer_common_ with is_retry=true - // Pass is_static_string=true because args->name is owned by the shared_ptr - // which is captured in the lambda and outlives the SchedulerItem this->set_timer_common_( - component, SchedulerItem::TIMEOUT, true, args->name, 0, [args]() { retry_handler(args); }, + component, SchedulerItem::TIMEOUT, name_type, static_name, hash_or_id, 0, [args]() { retry_handler(args); }, /* is_retry= */ true); } +void HOT Scheduler::set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts, + std::function func, float backoff_increase_factor) { + this->set_retry_common_(component, NameType::STATIC_STRING, name, 0, initial_wait_time, max_attempts, std::move(func), + backoff_increase_factor); +} + +bool HOT Scheduler::cancel_retry_(Component *component, NameType name_type, const char *static_name, + uint32_t hash_or_id) { + return this->cancel_item_(component, name_type, static_name, hash_or_id, SchedulerItem::TIMEOUT, + /* match_retry= */ true); +} +bool HOT Scheduler::cancel_retry(Component *component, const char *name) { + return this->cancel_retry_(component, NameType::STATIC_STRING, name, 0); +} + void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function func, float backoff_increase_factor) { - this->set_retry_common_(component, false, &name, initial_wait_time, max_attempts, std::move(func), - backoff_increase_factor); + this->set_retry_common_(component, NameType::HASHED_STRING, nullptr, fnv1a_hash(name), initial_wait_time, + max_attempts, std::move(func), backoff_increase_factor); } -void HOT Scheduler::set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts, - std::function func, float backoff_increase_factor) { - this->set_retry_common_(component, true, name, initial_wait_time, max_attempts, std::move(func), - backoff_increase_factor); -} bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { - return this->cancel_retry(component, name.c_str()); + return this->cancel_retry_(component, NameType::HASHED_STRING, nullptr, fnv1a_hash(name)); } -bool HOT Scheduler::cancel_retry(Component *component, const char *name) { - // Cancel timeouts that have is_retry flag set - LockGuard guard{this->lock_}; - return this->cancel_item_locked_(component, name, SchedulerItem::TIMEOUT, /* match_retry= */ true); +void HOT Scheduler::set_retry(Component *component, uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, + std::function func, float backoff_increase_factor) { + this->set_retry_common_(component, NameType::NUMERIC_ID, nullptr, id, initial_wait_time, max_attempts, + std::move(func), backoff_increase_factor); +} + +bool HOT Scheduler::cancel_retry(Component *component, uint32_t id) { + return this->cancel_retry_(component, NameType::NUMERIC_ID, nullptr, id); } optional HOT Scheduler::next_schedule_in(uint32_t now) { @@ -391,10 +445,11 @@ void HOT Scheduler::call(uint32_t now) { item = this->pop_raw_locked_(); } - const char *name = item->get_name(); + SchedulerNameLog name_log; bool is_cancelled = is_item_removed_(item.get()); ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64 "%s", - item->get_type_str(), LOG_STR_ARG(item->get_source()), name ? name : "(null)", item->interval, + item->get_type_str(), LOG_STR_ARG(item->get_source()), + name_log.format(item->get_name_type(), item->get_name(), item->get_name_hash_or_id()), item->interval, item->get_next_execution() - now_64, item->get_next_execution(), is_cancelled ? " [CANCELLED]" : ""); old_items.push_back(std::move(item)); @@ -458,10 +513,13 @@ void HOT Scheduler::call(uint32_t now) { #endif #ifdef ESPHOME_DEBUG_SCHEDULER - const char *item_name = item->get_name(); - ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")", - item->get_type_str(), LOG_STR_ARG(item->get_source()), item_name ? item_name : "(null)", item->interval, - item->get_next_execution(), now_64); + { + SchedulerNameLog name_log; + ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")", + item->get_type_str(), LOG_STR_ARG(item->get_source()), + name_log.format(item->get_name_type(), item->get_name(), item->get_name_hash_or_id()), item->interval, + item->get_next_execution(), now_64); + } #endif /* ESPHOME_DEBUG_SCHEDULER */ // Warning: During callback(), a lot of stuff can happen, including: @@ -560,33 +618,29 @@ uint32_t HOT Scheduler::execute_item_(SchedulerItem *item, uint32_t now) { return guard.finish(); } -// Common implementation for cancel operations -bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, const void *name_ptr, - SchedulerItem::Type type) { - // Get the name as const char* - const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr); - - // obtain lock because this function iterates and can be called from non-loop task context +// Common implementation for cancel operations - handles locking +bool HOT Scheduler::cancel_item_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id, + SchedulerItem::Type type, bool match_retry) { LockGuard guard{this->lock_}; - return this->cancel_item_locked_(component, name_cstr, type); + return this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type, match_retry); } -// Helper to cancel items by name - must be called with lock held -bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type, - bool match_retry) { - // Early return if name is invalid - no items to cancel - if (name_cstr == nullptr) { +// Helper to cancel items - must be called with lock held +// name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id +bool HOT Scheduler::cancel_item_locked_(Component *component, NameType name_type, const char *static_name, + uint32_t hash_or_id, SchedulerItem::Type type, bool match_retry) { + // Early return if static string name is invalid + if (name_type == NameType::STATIC_STRING && static_name == nullptr) { return false; } size_t total_cancelled = 0; - // Check all containers for matching items #ifndef ESPHOME_THREAD_SINGLE // Mark items in defer queue as cancelled (they'll be skipped when processed) if (type == SchedulerItem::TIMEOUT) { - total_cancelled += - this->mark_matching_items_removed_locked_(this->defer_queue_, component, name_cstr, type, match_retry); + total_cancelled += this->mark_matching_items_removed_locked_(this->defer_queue_, component, name_type, static_name, + hash_or_id, type, match_retry); } #endif /* not ESPHOME_THREAD_SINGLE */ @@ -596,14 +650,15 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c // would destroy the callback while it's running (use-after-free). // Only the main loop in call() should recycle items after execution completes. if (!this->items_.empty()) { - size_t heap_cancelled = - this->mark_matching_items_removed_locked_(this->items_, component, name_cstr, type, match_retry); + size_t heap_cancelled = this->mark_matching_items_removed_locked_(this->items_, component, name_type, static_name, + hash_or_id, type, match_retry); total_cancelled += heap_cancelled; this->to_remove_ += heap_cancelled; } // Cancel items in to_add_ - total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_, component, name_cstr, type, match_retry); + total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_, component, name_type, static_name, + hash_or_id, type, match_retry); return total_cancelled > 0; } @@ -612,8 +667,9 @@ uint64_t Scheduler::millis_64_(uint32_t now) { // THREAD SAFETY NOTE: // This function has three implementations, based on the precompiler flags // - ESPHOME_THREAD_SINGLE - Runs on single-threaded platforms (ESP8266, RP2040, etc.) - // - ESPHOME_THREAD_MULTI_NO_ATOMICS - Runs on multi-threaded platforms without atomics (LibreTiny) - // - ESPHOME_THREAD_MULTI_ATOMICS - Runs on multi-threaded platforms with atomics (ESP32, HOST, etc.) + // - ESPHOME_THREAD_MULTI_NO_ATOMICS - Runs on multi-threaded platforms without atomics (LibreTiny BK72xx) + // - ESPHOME_THREAD_MULTI_ATOMICS - Runs on multi-threaded platforms with atomics (ESP32, HOST, LibreTiny + // RTL87xx/LN882x, etc.) // // Make sure all changes are synchronized if you edit this function. // @@ -784,8 +840,6 @@ void Scheduler::recycle_item_main_loop_(std::unique_ptr item) { if (this->scheduler_item_pool_.size() < MAX_POOL_SIZE) { // Clear callback to release captured resources item->callback = nullptr; - // Clear dynamic name if any - item->clear_dynamic_name(); this->scheduler_item_pool_.push_back(std::move(item)); #ifdef ESPHOME_DEBUG_SCHEDULER ESP_LOGD(TAG, "Recycled item to pool (pool size now: %zu)", this->scheduler_item_pool_.size()); @@ -799,24 +853,44 @@ void Scheduler::recycle_item_main_loop_(std::unique_ptr item) { } #ifdef ESPHOME_DEBUG_SCHEDULER -void Scheduler::debug_log_timer_(const SchedulerItem *item, bool is_static_string, const char *name_cstr, - SchedulerItem::Type type, uint32_t delay, uint64_t now) { +void Scheduler::debug_log_timer_(const SchedulerItem *item, NameType name_type, const char *static_name, + uint32_t hash_or_id, SchedulerItem::Type type, uint32_t delay, uint64_t now) { // Validate static strings in debug mode - if (is_static_string && name_cstr != nullptr) { - validate_static_string(name_cstr); + if (name_type == NameType::STATIC_STRING && static_name != nullptr) { + validate_static_string(static_name); } // Debug logging + SchedulerNameLog name_log; const char *type_str = (type == SchedulerItem::TIMEOUT) ? "timeout" : "interval"; if (type == SchedulerItem::TIMEOUT) { ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()), - name_cstr ? name_cstr : "(null)", type_str, delay); + name_log.format(name_type, static_name, hash_or_id), type_str, delay); } else { ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ", offset=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()), - name_cstr ? name_cstr : "(null)", type_str, delay, + name_log.format(name_type, static_name, hash_or_id), type_str, delay, static_cast(item->get_next_execution() - now)); } } #endif /* ESPHOME_DEBUG_SCHEDULER */ +// Helper to get or create a scheduler item from the pool +// IMPORTANT: Caller must hold the scheduler lock before calling this function. +std::unique_ptr Scheduler::get_item_from_pool_locked_() { + std::unique_ptr item; + if (!this->scheduler_item_pool_.empty()) { + item = std::move(this->scheduler_item_pool_.back()); + this->scheduler_item_pool_.pop_back(); +#ifdef ESPHOME_DEBUG_SCHEDULER + ESP_LOGD(TAG, "Reused item from pool (pool size now: %zu)", this->scheduler_item_pool_.size()); +#endif + } else { + item = make_unique(); +#ifdef ESPHOME_DEBUG_SCHEDULER + ESP_LOGD(TAG, "Allocated new item (pool empty)"); +#endif + } + return item; +} + } // namespace esphome diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 5bf3d19adb..8c2e349180 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -1,9 +1,10 @@ #pragma once #include "esphome/core/defines.h" -#include -#include #include +#include +#include +#include #ifdef ESPHOME_THREAD_MULTI_ATOMICS #include #endif @@ -29,7 +30,9 @@ class Scheduler { template friend class DelayAction; public: - // Public API - accepts std::string for backward compatibility + // std::string overload - deprecated, use const char* or uint32_t instead + // Remove before 2026.7.0 + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function func); /** Set a timeout with a const char* name. @@ -39,14 +42,17 @@ class Scheduler { * - A string literal (e.g., "update") * - A static const char* variable * - A pointer with lifetime >= the scheduled task - * - * For dynamic strings, use the std::string overload instead. */ void set_timeout(Component *component, const char *name, uint32_t timeout, std::function func); + /// Set a timeout with a numeric ID (zero heap allocation) + void set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function func); + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(Component *component, const std::string &name); bool cancel_timeout(Component *component, const char *name); + bool cancel_timeout(Component *component, uint32_t id); + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_interval(Component *component, const std::string &name, uint32_t interval, std::function func); /** Set an interval with a const char* name. @@ -56,19 +62,29 @@ class Scheduler { * - A string literal (e.g., "update") * - A static const char* variable * - A pointer with lifetime >= the scheduled task - * - * For dynamic strings, use the std::string overload instead. */ void set_interval(Component *component, const char *name, uint32_t interval, std::function func); + /// Set an interval with a numeric ID (zero heap allocation) + void set_interval(Component *component, uint32_t id, uint32_t interval, std::function func); + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_interval(Component *component, const std::string &name); bool cancel_interval(Component *component, const char *name); + bool cancel_interval(Component *component, uint32_t id); + + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, std::function func, float backoff_increase_factor = 1.0f); void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts, std::function func, float backoff_increase_factor = 1.0f); + /// Set a retry with a numeric ID (zero heap allocation) + void set_retry(Component *component, uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, + std::function func, float backoff_increase_factor = 1.0f); + + ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_retry(Component *component, const std::string &name); bool cancel_retry(Component *component, const char *name); + bool cancel_retry(Component *component, uint32_t id); // Calculate when the next scheduled item should run // @param now Fresh timestamp from millis() - must not be stale/cached @@ -83,14 +99,22 @@ class Scheduler { void process_to_add(); + // Name storage type discriminator for SchedulerItem + // Used to distinguish between static strings, hashed strings, and numeric IDs + enum class NameType : uint8_t { + STATIC_STRING = 0, // const char* pointer to static/flash storage + HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string + NUMERIC_ID = 2 // uint32_t numeric identifier + }; + protected: struct SchedulerItem { // Ordered by size to minimize padding Component *component; - // Optimized name storage using tagged union + // Optimized name storage using tagged union - zero heap allocation union { - const char *static_name; // For string literals (no allocation) - char *dynamic_name; // For allocated strings + const char *static_name; // For STATIC_STRING (string literals, no allocation) + uint32_t hash_or_id; // For HASHED_STRING or NUMERIC_ID } name_; uint32_t interval; // Split time to handle millis() rollover. The scheduler combines the 32-bit millis() @@ -109,19 +133,19 @@ class Scheduler { // Place atomic separately since it can't be packed with bit fields std::atomic remove{false}; - // Bit-packed fields (3 bits used, 5 bits padding in 1 byte) - enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1; - bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[]) - bool is_retry : 1; // True if this is a retry timeout - // 5 bits padding -#else - // Single-threaded or multi-threaded without atomics: can pack all fields together // Bit-packed fields (4 bits used, 4 bits padding in 1 byte) enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1; + NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID) + bool is_retry : 1; // True if this is a retry timeout + // 4 bits padding +#else + // Single-threaded or multi-threaded without atomics: can pack all fields together + // Bit-packed fields (5 bits used, 3 bits padding in 1 byte) + enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1; bool remove : 1; - bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[]) - bool is_retry : 1; // True if this is a retry timeout - // 4 bits padding + NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID) + bool is_retry : 1; // True if this is a retry timeout + // 3 bits padding #endif // Constructor @@ -133,19 +157,19 @@ class Scheduler { #ifdef ESPHOME_THREAD_MULTI_ATOMICS // remove is initialized in the member declaration as std::atomic{false} type(TIMEOUT), - name_is_dynamic(false), + name_type_(NameType::STATIC_STRING), is_retry(false) { #else type(TIMEOUT), remove(false), - name_is_dynamic(false), + name_type_(NameType::STATIC_STRING), is_retry(false) { #endif name_.static_name = nullptr; } - // Destructor to clean up dynamic names - ~SchedulerItem() { clear_dynamic_name(); } + // Destructor - no dynamic memory to clean up + ~SchedulerItem() = default; // Delete copy operations to prevent accidental copies SchedulerItem(const SchedulerItem &) = delete; @@ -155,36 +179,31 @@ class Scheduler { SchedulerItem(SchedulerItem &&) = delete; SchedulerItem &operator=(SchedulerItem &&) = delete; - // Helper to get the name regardless of storage type - const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; } + // Helper to get the static name (only valid for STATIC_STRING type) + const char *get_name() const { return (name_type_ == NameType::STATIC_STRING) ? name_.static_name : nullptr; } - // Helper to clear dynamic name if allocated - void clear_dynamic_name() { - if (name_is_dynamic && name_.dynamic_name) { - delete[] name_.dynamic_name; - name_.dynamic_name = nullptr; - name_is_dynamic = false; - } + // Helper to get the hash or numeric ID (only valid for HASHED_STRING or NUMERIC_ID types) + uint32_t get_name_hash_or_id() const { return (name_type_ != NameType::STATIC_STRING) ? name_.hash_or_id : 0; } + + // Helper to get the name type + NameType get_name_type() const { return name_type_; } + + // Helper to set a static string name (no allocation) + void set_static_name(const char *name) { + name_.static_name = name; + name_type_ = NameType::STATIC_STRING; } - // Helper to set name with proper ownership - void set_name(const char *name, bool make_copy = false) { - // Clean up old dynamic name if any - clear_dynamic_name(); + // Helper to set a hashed string name (hash computed from std::string) + void set_hashed_name(uint32_t hash) { + name_.hash_or_id = hash; + name_type_ = NameType::HASHED_STRING; + } - if (!name) { - // nullptr case - no name provided - name_.static_name = nullptr; - } else if (make_copy) { - // Make a copy for dynamic strings (including empty strings) - size_t len = strlen(name); - name_.dynamic_name = new char[len + 1]; - memcpy(name_.dynamic_name, name, len + 1); - name_is_dynamic = true; - } else { - // Use static string directly (including empty strings) - name_.static_name = name; - } + // Helper to set a numeric ID name + void set_numeric_id(uint32_t id) { + name_.hash_or_id = id; + name_type_ = NameType::NUMERIC_ID; } static bool cmp(const std::unique_ptr &a, const std::unique_ptr &b); @@ -207,12 +226,18 @@ class Scheduler { }; // Common implementation for both timeout and interval - void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr, - uint32_t delay, std::function func, bool is_retry = false, bool skip_cancel = false); + // name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id + void set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type, const char *static_name, + uint32_t hash_or_id, uint32_t delay, std::function func, bool is_retry = false, + bool skip_cancel = false); // Common implementation for retry - void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time, - uint8_t max_attempts, std::function func, float backoff_increase_factor); + // name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id + void set_retry_common_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id, + uint32_t initial_wait_time, uint8_t max_attempts, std::function func, + float backoff_increase_factor); + // Common implementation for cancel_retry + bool cancel_retry_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id); uint64_t millis_64_(uint32_t now); // Cleanup logically deleted items from the scheduler @@ -222,21 +247,22 @@ class Scheduler { // Remove and return the front item from the heap // IMPORTANT: Caller must hold the scheduler lock before calling this function. std::unique_ptr pop_raw_locked_(); + // Get or create a scheduler item from the pool + // IMPORTANT: Caller must hold the scheduler lock before calling this function. + std::unique_ptr get_item_from_pool_locked_(); private: - // Helper to cancel items by name - must be called with lock held - bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type, bool match_retry = false); + // Helper to cancel items - must be called with lock held + // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id + bool cancel_item_locked_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id, + SchedulerItem::Type type, bool match_retry = false); - // Helper to extract name as const char* from either static string or std::string - inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) { - return is_static_string ? static_cast(name_ptr) : static_cast(name_ptr)->c_str(); - } + // Common implementation for cancel operations - handles locking + bool cancel_item_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id, + SchedulerItem::Type type, bool match_retry = false); - // Common implementation for cancel operations - bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type); - - // Helper to check if two scheduler item names match - inline bool HOT names_match_(const char *name1, const char *name2) const { + // Helper to check if two static string names match + inline bool HOT names_match_static_(const char *name1, const char *name2) const { // Check pointer equality first (common for static strings), then string contents // The core ESPHome codebase uses static strings (const char*) for component names, // making pointer comparison effective. The std::string overloads exist only for @@ -245,10 +271,11 @@ class Scheduler { } // Helper function to check if item matches criteria for cancellation + // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id // IMPORTANT: Must be called with scheduler lock held inline bool HOT matches_item_locked_(const std::unique_ptr &item, Component *component, - const char *name_cstr, SchedulerItem::Type type, bool match_retry, - bool skip_removed = true) const { + NameType name_type, const char *static_name, uint32_t hash_or_id, + SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const { // THREAD SAFETY: Check for nullptr first to prevent LoadProhibited crashes. On multi-threaded // platforms, items can be moved out of defer_queue_ during processing, leaving nullptr entries. // PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_() and @@ -261,7 +288,14 @@ class Scheduler { (match_retry && !item->is_retry)) { return false; } - return this->names_match_(item->get_name(), name_cstr); + // Name type must match + if (item->get_name_type() != name_type) + return false; + // For static strings, compare the string content; for hash/ID, compare the value + if (name_type == NameType::STATIC_STRING) { + return this->names_match_static_(item->get_name(), static_name); + } + return item->get_name_hash_or_id() == hash_or_id; } // Helper to execute a scheduler item @@ -283,7 +317,7 @@ class Scheduler { #ifdef ESPHOME_DEBUG_SCHEDULER // Helper for debug logging in set_timer_common_ - extracted to reduce code size - void debug_log_timer_(const SchedulerItem *item, bool is_static_string, const char *name_cstr, + void debug_log_timer_(const SchedulerItem *item, NameType name_type, const char *static_name, uint32_t hash_or_id, SchedulerItem::Type type, uint32_t delay, uint64_t now); #endif /* ESPHOME_DEBUG_SCHEDULER */ @@ -410,11 +444,13 @@ class Scheduler { } // Helper to mark matching items in a container as removed + // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id // Returns the number of items marked for removal // IMPORTANT: Must be called with scheduler lock held template - size_t mark_matching_items_removed_locked_(Container &container, Component *component, const char *name_cstr, - SchedulerItem::Type type, bool match_retry) { + size_t mark_matching_items_removed_locked_(Container &container, Component *component, NameType name_type, + const char *static_name, uint32_t hash_or_id, SchedulerItem::Type type, + bool match_retry) { size_t count = 0; for (auto &item : container) { // Skip nullptr items (can happen in defer_queue_ when items are being processed) @@ -423,8 +459,7 @@ class Scheduler { // the vector can still contain nullptr items from the processing loop. This check prevents crashes. if (!item) continue; - if (this->matches_item_locked_(item, component, name_cstr, type, match_retry)) { - // Mark item for removal (platform-specific) + if (this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, type, match_retry)) { this->set_item_removed_(item.get(), true); count++; } @@ -433,10 +468,12 @@ class Scheduler { } // Template helper to check if any item in a container matches our criteria + // name_type determines matching: STATIC_STRING uses static_name, others use hash_or_id // IMPORTANT: Must be called with scheduler lock held template - bool has_cancelled_timeout_in_container_locked_(const Container &container, Component *component, - const char *name_cstr, bool match_retry) const { + bool has_cancelled_timeout_in_container_locked_(const Container &container, Component *component, NameType name_type, + const char *static_name, uint32_t hash_or_id, + bool match_retry) const { for (const auto &item : container) { // Skip nullptr items (can happen in defer_queue_ when items are being processed) // The defer_queue_ uses index-based processing: items are std::moved out but left in the @@ -445,8 +482,8 @@ class Scheduler { if (!item) continue; if (is_item_removed_(item.get()) && - this->matches_item_locked_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry, - /* skip_removed= */ false)) { + this->matches_item_locked_(item, component, name_type, static_name, hash_or_id, SchedulerItem::TIMEOUT, + match_retry, /* skip_removed= */ false)) { return true; } } diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h index 505fdd906a..44ca79c81b 100644 --- a/esphome/core/string_ref.h +++ b/esphome/core/string_ref.h @@ -11,6 +11,10 @@ #include "esphome/components/json/json_util.h" #endif // USE_JSON +#ifdef USE_ESP8266 +#include +#endif // USE_ESP8266 + namespace esphome { /** @@ -107,6 +111,23 @@ inline bool operator!=(const StringRef &lhs, const char *rhs) { return !(lhs == inline bool operator!=(const char *lhs, const StringRef &rhs) { return !(rhs == lhs); } +#ifdef USE_ESP8266 +inline bool operator==(const StringRef &lhs, const __FlashStringHelper *rhs) { + PGM_P p = reinterpret_cast(rhs); + size_t rhs_len = strlen_P(p); + if (lhs.size() != rhs_len) { + return false; + } + return memcmp_P(lhs.c_str(), p, rhs_len) == 0; +} + +inline bool operator==(const __FlashStringHelper *lhs, const StringRef &rhs) { return rhs == lhs; } + +inline bool operator!=(const StringRef &lhs, const __FlashStringHelper *rhs) { return !(lhs == rhs); } + +inline bool operator!=(const __FlashStringHelper *lhs, const StringRef &rhs) { return !(rhs == lhs); } +#endif // USE_ESP8266 + inline bool operator<(const StringRef &lhs, const StringRef &rhs) { return std::lexicographical_compare(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(rhs)); } diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index d30dac4394..4047033f84 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -1,6 +1,7 @@ #include "time.h" // NOLINT #include "helpers.h" +#include #include namespace esphome { @@ -17,6 +18,18 @@ size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { return ::strftime(buffer, buffer_len, format, &c_tm); } +size_t ESPTime::strftime_to(std::span buffer, const char *format) { + struct tm c_tm = this->to_c_tm(); + size_t len = ::strftime(buffer.data(), buffer.size(), format, &c_tm); + if (len > 0) { + return len; + } + // Write "ERROR" to buffer on failure for consistent behavior + constexpr char error_str[] = "ERROR"; + std::copy_n(error_str, sizeof(error_str), buffer.data()); + return sizeof(error_str) - 1; // Length excluding null terminator +} + ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) { ESPTime res{}; res.second = uint8_t(c_tm->tm_sec); @@ -47,13 +60,9 @@ struct tm ESPTime::to_c_tm() { } std::string ESPTime::strftime(const char *format) { - struct tm c_tm = this->to_c_tm(); - char buf[128]; - size_t len = ::strftime(buf, sizeof(buf), format, &c_tm); - if (len > 0) { - return std::string(buf, len); - } - return "ERROR"; + char buf[STRFTIME_BUFFER_SIZE]; + size_t len = this->strftime_to(buf, format); + return std::string(buf, len); } std::string ESPTime::strftime(const std::string &format) { return this->strftime(format.c_str()); } diff --git a/esphome/core/time.h b/esphome/core/time.h index 68826dabdc..f6f1d57dbb 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace esphome { @@ -13,6 +14,9 @@ uint8_t days_in_month(uint8_t month, uint16_t year); /// A more user-friendly version of struct tm from time.h struct ESPTime { + /// Buffer size required for strftime output + static constexpr size_t STRFTIME_BUFFER_SIZE = 128; + /** seconds after the minute [0-60] * @note second is generally 0-59; the extra range is to accommodate leap seconds. */ @@ -43,14 +47,22 @@ struct ESPTime { */ size_t strftime(char *buffer, size_t buffer_len, const char *format); + /** Format time into a fixed-size buffer, returns length written. + * + * This is the preferred method for avoiding heap allocations. The buffer size is enforced at compile-time. + * On format error, writes "ERROR" to the buffer and returns 5. + * @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime + */ + size_t strftime_to(std::span buffer, const char *format); + /** Convert this ESPTime struct to a string as specified by the format argument. * @see https://en.cppreference.com/w/c/chrono/strftime * * @warning This method returns a dynamically allocated string which can cause heap fragmentation with some - * microcontrollers. + * microcontrollers. Prefer strftime_to() for heap-free formatting. * * @warning This method can return "ERROR" when the underlying strftime() call fails or when the - * output exceeds 128 bytes. + * output exceeds STRFTIME_BUFFER_SIZE bytes. */ std::string strftime(const std::string &format); diff --git a/esphome/coroutine.py b/esphome/coroutine.py index 0331c602c5..f5d512e510 100644 --- a/esphome/coroutine.py +++ b/esphome/coroutine.py @@ -114,6 +114,14 @@ class CoroPriority(enum.IntEnum): # Examples: web_server_ota (52) WEB_SERVER_OTA = 52 + # Preferences - must run before APPLICATION (safe_mode) because safe_mode + # uses an early return when entering safe mode, skipping all lower priority + # component registration. Without IntervalSyncer registered, preferences + # cannot be synced during shutdown in safe mode, causing issues like the + # boot counter never being cleared and devices getting stuck in safe mode. + # Examples: preferences (51) + PREFERENCES = 51 + # Application-level services # Examples: safe_mode (50) APPLICATION = 50 diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 1a47b346b7..cff0748c95 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -51,15 +51,19 @@ class AssignmentExpression(Expression): class VariableDeclarationExpression(Expression): - __slots__ = ("type", "modifier", "name") + __slots__ = ("type", "modifier", "name", "static") - def __init__(self, type_, modifier, name): + def __init__( + self, type_: "MockObj", modifier: str, name: ID, *, static: bool = False + ) -> None: self.type = type_ self.modifier = modifier self.name = name + self.static = static - def __str__(self): - return f"{self.type} {self.modifier}{self.name}" + def __str__(self) -> str: + prefix = "static " if self.static else "" + return f"{prefix}{self.type} {self.modifier}{self.name}" class ExpressionList(Expression): @@ -507,13 +511,17 @@ def with_local_variable(id_: ID, rhs: SafeExpType, callback: Callable, *args) -> CORE.add(RawStatement("}")) # output closing curly brace -def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": +def new_variable( + id_: ID, rhs: SafeExpType, type_: "MockObj" = None, *, static: bool = True +) -> "MockObj": """Declare and define a new variable, not pointer type, in the code generation. :param id_: The ID used to declare the variable. :param rhs: The expression to place on the right hand side of the assignment. :param type_: Manually define a type for the variable, only use this when it's not possible to do so during config validation phase (for example because of template arguments). + :param static: If True (default), declare with static storage class for optimization. + Set to False when the variable must have external linkage (e.g., to match library declarations). :return: The new variable as a MockObj. """ @@ -522,7 +530,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj obj = MockObj(id_, ".") if type_ is not None: id_.type = type_ - decl = VariableDeclarationExpression(id_.type, "", id_) + decl = VariableDeclarationExpression(id_.type, "", id_, static=static) CORE.add_global(decl) assignment = AssignmentExpression(None, "", id_, rhs) CORE.add(assignment) @@ -544,7 +552,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": obj = MockObj(id_, "->") if type_ is not None: id_.type = type_ - decl = VariableDeclarationExpression(id_.type, "*", id_) + decl = VariableDeclarationExpression(id_.type, "*", id_, static=True) CORE.add_global(decl) assignment = AssignmentExpression(None, None, id_, rhs) CORE.add(assignment) @@ -635,7 +643,7 @@ async def get_variable(id_: ID) -> "MockObj": Wait for the given ID to be defined in the code generation and return it as a MockObj. - This is a coroutine, you need to await it with a 'await' expression! + This is a coroutine, you need to await it with an 'await' expression! :param id_: The ID to retrieve :return: The variable as a MockObj. @@ -648,7 +656,7 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]: Wait for the given ID to be defined in the code generation and return it as a MockObj. - This is a coroutine, you need to await it with a 'await' expression! + This is a coroutine, you need to await it with an 'await' expression! :param id_: The ID to retrieve :return: The variable as a MockObj. diff --git a/esphome/helpers.py b/esphome/helpers.py index ea6abff50a..ae142b7f8b 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -35,6 +35,10 @@ IS_MACOS = platform.system() == "Darwin" IS_WINDOWS = platform.system() == "Windows" IS_LINUX = platform.system() == "Linux" +# FNV-1 hash constants (must match C++ in esphome/core/helpers.h) +FNV1_OFFSET_BASIS = 2166136261 +FNV1_PRIME = 16777619 + def ensure_unique_string(preferred_string, current_strings): test_string = preferred_string @@ -49,8 +53,17 @@ def ensure_unique_string(preferred_string, current_strings): return test_string +def fnv1_hash(string: str) -> int: + """FNV-1 32-bit hash function (multiply then XOR).""" + hash_value = FNV1_OFFSET_BASIS + for char in string: + hash_value = (hash_value * FNV1_PRIME) & 0xFFFFFFFF + hash_value ^= ord(char) + return hash_value + + def fnv1a_32bit_hash(string: str) -> int: - """FNV-1a 32-bit hash function. + """FNV-1a 32-bit hash function (XOR then multiply). Note: This uses 32-bit hash instead of 64-bit for several reasons: 1. ESPHome targets 32-bit microcontrollers with limited RAM (often <320KB) @@ -63,13 +76,22 @@ def fnv1a_32bit_hash(string: str) -> int: a handful of area_ids and device_ids (typically <10 areas and <100 devices), making collisions virtually impossible. """ - hash_value = 2166136261 + hash_value = FNV1_OFFSET_BASIS for char in string: hash_value ^= ord(char) - hash_value = (hash_value * 16777619) & 0xFFFFFFFF + hash_value = (hash_value * FNV1_PRIME) & 0xFFFFFFFF return hash_value +def fnv1_hash_object_id(name: str) -> int: + """Compute FNV-1 hash of name with snake_case + sanitize transformations. + + IMPORTANT: Must produce same result as C++ fnv1_hash_object_id() in helpers.h. + Used for pre-computing entity object_id hashes at code generation time. + """ + return fnv1_hash(sanitize(snake_case(name))) + + def strip_accents(value: str) -> str: """Remove accents from a string.""" import unicodedata @@ -424,9 +446,13 @@ def write_file_if_changed(path: Path, text: str) -> bool: return True -def copy_file_if_changed(src: Path, dst: Path) -> None: +def copy_file_if_changed(src: Path, dst: Path) -> bool: + """Copy file from src to dst if contents differ. + + Returns True if file was copied, False if files already matched. + """ if file_compare(src, dst): - return + return False dst.parent.mkdir(parents=True, exist_ok=True) try: shutil.copyfile(src, dst) @@ -441,11 +467,12 @@ def copy_file_if_changed(src: Path, dst: Path) -> None: with suppress(OSError): os.unlink(dst) shutil.copyfile(src, dst) - return + return True from esphome.core import EsphomeError raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err + return True def list_starts_with(list_, sub): diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 4573391bc1..045b3f9168 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -6,15 +6,15 @@ dependencies: espressif/mdns: version: 1.9.1 espressif/esp_wifi_remote: - version: 1.2.2 + version: 1.2.4 rules: - if: "target in [esp32h2, esp32p4]" espressif/eppp_link: - version: 1.1.3 + version: 1.1.4 rules: - if: "target in [esp32h2, esp32p4]" espressif/esp_hosted: - version: 2.7.0 + version: 2.9.3 rules: - if: "target in [esp32h2, esp32p4]" zorxx/multipart-parser: @@ -28,6 +28,8 @@ dependencies: rules: - if: "target in [esp32s2, esp32s3, esp32p4]" esphome/esp-hub75: - version: 0.1.7 + version: 0.2.2 rules: - if: "target in [esp32, esp32s2, esp32s3, esp32p4]" + esp32async/asynctcp: + version: 3.4.91 diff --git a/esphome/loader.py b/esphome/loader.py index 387443c032..968c8cf3e0 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -187,7 +187,14 @@ def install_meta_finder( def install_custom_components_meta_finder(): + # Remove before 2026.6.0 custom_components_dir = (Path(CORE.config_dir) / "custom_components").resolve() + if custom_components_dir.is_dir() and any(custom_components_dir.iterdir()): + _LOGGER.warning( + "The 'custom_components' folder is deprecated and will be removed in 2026.6.0. " + "Please use 'external_components' instead. " + "See https://esphome.io/components/external_components.html for more information." + ) install_meta_finder(custom_components_dir) diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 4d795ea5d9..e66f9a2c97 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -420,3 +420,8 @@ class IDEData: if path.endswith(".exe") else f"{path[:-3]}readelf" ) + + @property + def defines(self) -> list[str]: + """Return the list of preprocessor defines from idedata.""" + return self.raw.get("defines", []) diff --git a/esphome/writer.py b/esphome/writer.py index 684b3f9dc5..cb9c921693 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,11 +1,13 @@ from collections.abc import Callable import importlib +import json import logging import os from pathlib import Path import re import shutil import stat +import time from types import TracebackType from esphome import loader @@ -19,10 +21,12 @@ from esphome.const import ( from esphome.core import CORE, EsphomeError from esphome.helpers import ( copy_file_if_changed, + cpp_string_escape, get_str_env, is_ha_addon, read_file, walk_files, + write_file, write_file_if_changed, ) from esphome.storage_json import StorageJSON, storage_path @@ -170,6 +174,7 @@ VERSION_H_FORMAT = """\ """ DEFINES_H_TARGET = "esphome/core/defines.h" VERSION_H_TARGET = "esphome/core/version.h" +BUILD_INFO_DATA_H_TARGET = "esphome/core/build_info_data.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY @@ -203,10 +208,16 @@ def copy_src_tree(): include_s = "\n".join(include_l) source_files_copy = source_files_map.copy() - ignore_targets = [Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET)] + ignore_targets = [ + Path(x) for x in (DEFINES_H_TARGET, VERSION_H_TARGET, BUILD_INFO_DATA_H_TARGET) + ] for t in ignore_targets: - source_files_copy.pop(t) + source_files_copy.pop(t, None) + # Files to exclude from sources_changed tracking (generated files) + generated_files = {Path("esphome/core/build_info_data.h")} + + sources_changed = False for fname in walk_files(CORE.relative_src_path("esphome")): p = Path(fname) if p.suffix not in SOURCE_FILE_EXTENSIONS: @@ -220,28 +231,82 @@ def copy_src_tree(): if target not in source_files_copy: # Source file removed, delete target p.unlink() + if target not in generated_files: + sources_changed = True else: src_file = source_files_copy.pop(target) with src_file.path() as src_path: - copy_file_if_changed(src_path, p) + if copy_file_if_changed(src_path, p) and target not in generated_files: + sources_changed = True # Now copy new files for target, src_file in source_files_copy.items(): dst_path = CORE.relative_src_path(*target.parts) with src_file.path() as src_path: - copy_file_if_changed(src_path, dst_path) + if ( + copy_file_if_changed(src_path, dst_path) + and target not in generated_files + ): + sources_changed = True # Finally copy defines - write_file_if_changed( + if write_file_if_changed( CORE.relative_src_path("esphome", "core", "defines.h"), generate_defines_h() - ) + ): + sources_changed = True write_file_if_changed(CORE.relative_build_path("README.txt"), ESPHOME_README_TXT) - write_file_if_changed( + if write_file_if_changed( CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) - ) - write_file_if_changed( + ): + sources_changed = True + if write_file_if_changed( CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() + ): + sources_changed = True + + # Generate new build_info files if needed + build_info_data_h_path = CORE.relative_src_path( + "esphome", "core", "build_info_data.h" ) + build_info_json_path = CORE.relative_build_path("build_info.json") + config_hash, build_time, build_time_str, comment = get_build_info() + + # Defensively force a rebuild if the build_info files don't exist, or if + # there was a config change which didn't actually cause a source change + if not build_info_data_h_path.exists(): + sources_changed = True + else: + try: + existing = json.loads(build_info_json_path.read_text(encoding="utf-8")) + if ( + existing.get("config_hash") != config_hash + or existing.get("esphome_version") != __version__ + ): + sources_changed = True + except (json.JSONDecodeError, KeyError, OSError): + sources_changed = True + + # Write build_info header and JSON metadata + if sources_changed: + write_file( + build_info_data_h_path, + generate_build_info_data_h( + config_hash, build_time, build_time_str, comment + ), + ) + write_file( + build_info_json_path, + json.dumps( + { + "config_hash": config_hash, + "build_time": build_time, + "build_time_str": build_time_str, + "esphome_version": __version__, + }, + indent=2, + ) + + "\n", + ) platform = "esphome.components." + CORE.target_platform try: @@ -267,6 +332,43 @@ def generate_version_h(): ) +def get_build_info() -> tuple[int, int, str, str]: + """Calculate build_info values from current config. + + Returns: + Tuple of (config_hash, build_time, build_time_str, comment) + """ + config_hash = CORE.config_hash + build_time = int(time.time()) + build_time_str = time.strftime("%Y-%m-%d %H:%M:%S %z", time.localtime(build_time)) + comment = CORE.comment or "" + return config_hash, build_time, build_time_str, comment + + +def generate_build_info_data_h( + config_hash: int, build_time: int, build_time_str: str, comment: str +) -> str: + """Generate build_info_data.h header with config hash, build time, and comment.""" + # cpp_string_escape returns '"escaped"', slice off the quotes since template has them + escaped_comment = cpp_string_escape(comment)[1:-1] + # +1 for null terminator + comment_size = len(comment) + 1 + return f"""#pragma once +// Auto-generated build_info data +#define ESPHOME_CONFIG_HASH 0x{config_hash:08x}U // NOLINT +#define ESPHOME_BUILD_TIME {build_time} // NOLINT +#define ESPHOME_COMMENT_SIZE {comment_size} // NOLINT +#ifdef USE_ESP8266 +#include +static const char ESPHOME_BUILD_TIME_STR[] PROGMEM = "{build_time_str}"; +static const char ESPHOME_COMMENT_STR[] PROGMEM = "{escaped_comment}"; +#else +static const char ESPHOME_BUILD_TIME_STR[] = "{build_time_str}"; +static const char ESPHOME_COMMENT_STR[] = "{escaped_comment}"; +#endif +""" + + def write_cpp(code_s): path = CORE.relative_src_path("main.cpp") if path.is_file(): diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 359b72b48f..bba4bbf487 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Callable +from contextlib import suppress import functools import inspect from io import BytesIO, TextIOBase, TextIOWrapper @@ -501,13 +502,17 @@ def _load_yaml_internal_with_type( loader.dispose() -def dump(dict_, show_secrets=False): +def dump(dict_, show_secrets=False, sort_keys=False): """Dump YAML to a string and remove null.""" if show_secrets: _SECRET_VALUES.clear() _SECRET_CACHE.clear() return yaml.dump( - dict_, default_flow_style=False, allow_unicode=True, Dumper=ESPHomeDumper + dict_, + default_flow_style=False, + allow_unicode=True, + Dumper=ESPHomeDumper, + sort_keys=sort_keys, ) @@ -543,6 +548,9 @@ class ESPHomeDumper(yaml.SafeDumper): best_style = True if hasattr(mapping, "items"): mapping = list(mapping.items()) + if self.sort_keys: + with suppress(TypeError): + mapping = sorted(mapping) for item_key, item_value in mapping: node_key = self.represent_data(item_key) node_value = self.represent_data(item_value) diff --git a/platformio.ini b/platformio.ini index a27fb1f537..4180971b54 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,6 +38,8 @@ lib_deps_base = wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 pavlodn/HaierProtocol@0.9.31 ; haier + esphome/dsmr_parser@1.0.0 ; dsmr + polargoose/Crypto-no-arduino@0.4.0 ; dsmr https://github.com/esphome/TinyGPSPlus.git#v1.1.0 ; gps ; This is using the repository until a new release is published to PlatformIO https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library @@ -82,8 +84,6 @@ lib_deps = heman/AsyncMqttClient-esphome@1.0.0 ; mqtt fastled/FastLED@3.9.16 ; fastled_base freekode/TM1651@1.0.1 ; tm1651 - glmnet/Dsmr@0.7 ; dsmr - rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.9 ; midea tonia/HeatpumpIR@1.0.37 ; heatpumpir build_flags = @@ -133,9 +133,9 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.31-1/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.35/platform-espressif32.zip platform_packages = - pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.3.2/esp32-3.3.2.zip + pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.3.5/esp32-3.3.5.zip framework = arduino, espidf ; Arduino as an ESP-IDF component lib_deps = @@ -146,7 +146,6 @@ lib_deps = WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} - ESP32Async/AsyncTCP@3.4.5 ; async_tcp NetworkClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) @@ -156,6 +155,7 @@ lib_deps = esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@2.0.1 ; audio + kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word build_flags = ${common:arduino.build_flags} @@ -169,9 +169,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.31-1/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.35/platform-espressif32.zip platform_packages = - pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.5.1/esp-idf-v5.5.1.zip + pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.5.2/esp-idf-v5.5.2.tar.xz framework = espidf lib_deps = @@ -212,7 +212,7 @@ build_unflags = ; This are common settings for the LibreTiny (all variants) using Arduino. [common:libretiny-arduino] extends = common:arduino -platform = libretiny@1.9.1 +platform = libretiny@1.9.2 framework = arduino lib_compat_mode = soft lib_deps = diff --git a/requirements.txt b/requirements.txt index 71aaf47ddb..9994148cf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,28 +1,28 @@ cryptography==45.0.1 -voluptuous==0.15.2 +voluptuous==0.16.0 PyYAML==6.0.3 paho-mqtt==1.6.1 colorama==0.4.6 icmplib==3.0.4 -tornado==6.5.2 +tornado==6.5.4 tzlocal==5.3.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 -esphome-dashboard==20251013.0 -aioesphomeapi==43.2.1 +esphome-dashboard==20260110.0 +aioesphomeapi==43.13.0 zeroconf==0.148.0 puremagic==1.30 -ruamel.yaml==0.18.16 # dashboard_import +ruamel.yaml==0.19.1 # dashboard_import ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 pillow==11.3.0 -cairosvg==2.8.2 +resvg-py==0.2.5 freetype-py==2.5.1 jinja2==3.1.6 -bleak==2.0.0 +bleak==2.1.1 # esp-idf >= 5.0 requires this pyparsing >= 3.0 diff --git a/requirements_dev.txt b/requirements_dev.txt index 16e051fcd7..0884e5b5e4 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ # Useful stuff when working in a development environment clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating clang-tidy==18.1.8 # When updating clang-tidy, also update Dockerfile -yamllint==1.37.1 # also change in .pre-commit-config.yaml when updating +yamllint==1.38.0 # also change in .pre-commit-config.yaml when updating diff --git a/requirements_test.txt b/requirements_test.txt index 60656712b9..092a06fd66 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.4 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.14.8 # also change in .pre-commit-config.yaml when updating +ruff==0.14.11 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 3412fac5db..7625458f9f 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -339,6 +339,9 @@ def create_field_type_info( ) -> TypeInfo: """Create the appropriate TypeInfo instance for a field, handling repeated fields and custom options.""" if field.label == FieldDescriptorProto.LABEL_REPEATED: + # Check if this is a packed_buffer field (zero-copy packed repeated) + if get_field_opt(field, pb.packed_buffer, False): + return PackedBufferTypeInfo(field) # Check if this repeated field has fixed_array_with_length_define option if ( fixed_size := get_field_opt(field, pb.fixed_array_with_length_define) @@ -354,41 +357,28 @@ def create_field_type_info( return FixedArrayRepeatedType(field, size_define) return RepeatedTypeInfo(field) - # Check for mutually exclusive options on bytes fields - if field.type == 12: - has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False) - fixed_size = get_field_opt(field, pb.fixed_array_size, None) - - if has_pointer_to_buffer and fixed_size is not None: - raise ValueError( - f"Field '{field.name}' has both pointer_to_buffer and fixed_array_size. " - "These options are mutually exclusive. Use pointer_to_buffer for zero-copy " - "or fixed_array_size for traditional array storage." - ) - - if has_pointer_to_buffer: - # Zero-copy pointer approach - no size needed, will use size_t for length - return PointerToBytesBufferType(field, None) - - if fixed_size is not None: - # Traditional fixed array approach with copy - return FixedArrayBytesType(field, fixed_size) - - # Check for pointer_to_buffer option on string fields - if field.type == 9: - has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False) - - if has_pointer_to_buffer: - # Zero-copy pointer approach for strings - return PointerToBytesBufferType(field, None) - # Special handling for bytes fields if field.type == 12: + fixed_size = get_field_opt(field, pb.fixed_array_size, None) + + if fixed_size is not None: + # Traditional fixed array approach with copy (takes priority) + return FixedArrayBytesType(field, fixed_size) + + # For messages that decode (SOURCE_CLIENT or SOURCE_BOTH), use pointer + # for zero-copy access to the receive buffer + if needs_decode: + return PointerToBytesBufferType(field, None) + + # For SOURCE_SERVER (encode only), explicit annotation is still needed + if get_field_opt(field, pb.pointer_to_buffer, False): + return PointerToBytesBufferType(field, None) + return BytesType(field, needs_decode, needs_encode) - # Special handling for string fields + # Special handling for string fields - use StringRef for zero-copy if field.type == 9: - return StringType(field, needs_decode, needs_encode) + return PointerToStringBufferType(field, None) validate_field_type(field.type, field.name) return TYPE_INFO[field.type](field) @@ -593,15 +583,12 @@ class StringType(TypeInfo): def public_content(self) -> list[str]: content: list[str] = [] - # Check if no_zero_copy option is set - no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) - - # Add std::string storage if message needs decoding OR if no_zero_copy is set - if self._needs_decode or no_zero_copy: + # Add std::string storage if message needs decoding + if self._needs_decode: content.append(f"std::string {self.field_name}{{}};") - # Only add StringRef if encoding is needed AND no_zero_copy is not set - if self._needs_encode and not no_zero_copy: + # Add StringRef if encoding is needed + if self._needs_encode: content.extend( [ # Add StringRef field if message needs encoding @@ -616,27 +603,14 @@ class StringType(TypeInfo): @property def encode_content(self) -> str: - # Check if no_zero_copy option is set - no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) - - if no_zero_copy: - # Use the std::string directly - return f"buffer.encode_string({self.number}, this->{self.field_name});" # Use the StringRef return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);" def dump(self, name): - # Check if no_zero_copy option is set - no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) - # If name is 'it', this is a repeated field element - always use string if name == "it": return "append_quoted_string(out, StringRef(it));" - # If no_zero_copy is set, always use std::string - if no_zero_copy: - return f'out.append("\'").append(this->{self.field_name}).append("\'");' - # For SOURCE_CLIENT only, always use std::string if not self._needs_encode: return f'out.append("\'").append(this->{self.field_name}).append("\'");' @@ -656,13 +630,6 @@ class StringType(TypeInfo): @property def dump_content(self) -> str: - # Check if no_zero_copy option is set - no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) - - # If no_zero_copy is set, always use std::string - if no_zero_copy: - return f'dump_field(out, "{self.name}", this->{self.field_name});' - # For SOURCE_CLIENT only, use std::string if not self._needs_encode: return f'dump_field(out, "{self.name}", this->{self.field_name});' @@ -678,17 +645,8 @@ class StringType(TypeInfo): return o def get_size_calculation(self, name: str, force: bool = False) -> str: - # Check if no_zero_copy option is set - no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False) - - # For SOURCE_CLIENT only messages or no_zero_copy, use the string field directly - if not self._needs_encode or no_zero_copy: - # For no_zero_copy, we need to use .size() on the string - if no_zero_copy and name != "it": - field_id_size = self.calculate_field_id_size() - return ( - f"size.add_length({field_id_size}, this->{self.field_name}.size());" - ) + # For SOURCE_CLIENT only messages, use the string field directly + if not self._needs_encode: return self._get_simple_size_calculation(name, force, "add_length") # Check if this is being called from a repeated field context @@ -828,10 +786,32 @@ class BytesType(TypeInfo): @property def dump_content(self) -> str: - o = f'out.append(" {self.name}: ");\n' - o += self.dump(f"this->{self.field_name}") + "\n" - o += 'out.append("\\n");' - return o + # For SOURCE_CLIENT only, always use std::string + if not self._needs_encode: + return ( + f'dump_bytes_field(out, "{self.name}", ' + f"reinterpret_cast(this->{self.field_name}.data()), " + f"this->{self.field_name}.size());" + ) + + # For SOURCE_SERVER, always use pointer/length + if not self._needs_decode: + return ( + f'dump_bytes_field(out, "{self.name}", ' + f"this->{self.field_name}_ptr_, this->{self.field_name}_len_);" + ) + + # For SOURCE_BOTH, check if pointer is set (sending) or use string (received) + return ( + f"if (this->{self.field_name}_ptr_ != nullptr) {{\n" + f' dump_bytes_field(out, "{self.name}", ' + f"this->{self.field_name}_ptr_, this->{self.field_name}_len_);\n" + f"}} else {{\n" + f' dump_bytes_field(out, "{self.name}", ' + f"reinterpret_cast(this->{self.field_name}.data()), " + f"this->{self.field_name}.size());\n" + f"}}" + ) def get_size_calculation(self, name: str, force: bool = False) -> str: return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len_);" @@ -840,8 +820,8 @@ class BytesType(TypeInfo): return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes -class PointerToBytesBufferType(TypeInfo): - """Type for bytes fields that use pointer_to_buffer option for zero-copy.""" +class PointerToBufferTypeBase(TypeInfo): + """Base class for pointer_to_buffer types (bytes and strings) for zero-copy decoding.""" @classmethod def can_use_dump_field(cls) -> bool: @@ -851,29 +831,34 @@ class PointerToBytesBufferType(TypeInfo): self, field: descriptor.FieldDescriptorProto, size: int | None = None ) -> None: super().__init__(field) - # Size is not used for pointer_to_buffer - we always use size_t for length self.array_size = 0 @property - def cpp_type(self) -> str: - return "const uint8_t*" + def decode_length(self) -> str | None: + # This is handled in decode_length_content + return None @property - def default_value(self) -> str: - return "nullptr" + def wire_type(self) -> WireType: + """Get the wire type for this field.""" + return WireType.LENGTH_DELIMITED # Uses wire type 2 - @property - def reference_type(self) -> str: - return "const uint8_t*" + def get_estimated_size(self) -> int: + # field ID + length varint + typical data (assume small for pointer fields) + return self.calculate_field_id_size() + 2 + 16 - @property - def const_reference_type(self) -> str: - return "const uint8_t*" + +class PointerToBytesBufferType(PointerToBufferTypeBase): + """Type for bytes fields that use pointer_to_buffer option for zero-copy.""" + + cpp_type = "const uint8_t*" + default_value = "nullptr" + reference_type = "const uint8_t*" + const_reference_type = "const uint8_t*" @property def public_content(self) -> list[str]: # Use uint16_t for length - max packet size is well below 65535 - # Add pointer and length fields return [ f"const uint8_t* {self.field_name}{{nullptr}};", f"uint16_t {self.field_name}_len{{0}};", @@ -885,24 +870,12 @@ class PointerToBytesBufferType(TypeInfo): @property def decode_length_content(self) -> str | None: - # Decode directly stores the pointer to avoid allocation return f"""case {self.number}: {{ - // Use raw data directly to avoid allocation this->{self.field_name} = value.data(); this->{self.field_name}_len = value.size(); break; }}""" - @property - def decode_length(self) -> str | None: - # This is handled in decode_length_content - return None - - @property - def wire_type(self) -> WireType: - """Get the wire type for this bytes field.""" - return WireType.LENGTH_DELIMITED # Uses wire type 2 - def dump(self, name: str) -> str: return ( f"format_hex_pretty(this->{self.field_name}, this->{self.field_name}_len)" @@ -910,19 +883,140 @@ class PointerToBytesBufferType(TypeInfo): @property def dump_content(self) -> str: - # Custom dump that doesn't use dump_field template return ( - f'out.append(" {self.name}: ");\n' - + f"out.append({self.dump(self.field_name)});\n" - + 'out.append("\\n");' + f'dump_bytes_field(out, "{self.name}", ' + f"this->{self.field_name}, this->{self.field_name}_len);" ) def get_size_calculation(self, name: str, force: bool = False) -> str: - return f"size.add_length({self.number}, this->{self.field_name}_len);" + return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len);" + + +class PointerToStringBufferType(PointerToBufferTypeBase): + """Type for string fields that use pointer_to_buffer option for zero-copy. + + Uses StringRef instead of separate pointer and length fields. + """ + + cpp_type = "StringRef" + default_value = "" + reference_type = "StringRef &" + const_reference_type = "const StringRef &" + + @classmethod + def can_use_dump_field(cls) -> bool: + return True + + @property + def public_content(self) -> list[str]: + return [f"StringRef {self.field_name}{{}};"] + + @property + def encode_content(self) -> str: + return f"buffer.encode_string({self.number}, this->{self.field_name});" + + @property + def decode_length_content(self) -> str | None: + return f"""case {self.number}: {{ + this->{self.field_name} = StringRef(reinterpret_cast(value.data()), value.size()); + break; + }}""" + + def dump(self, name: str) -> str: + # Not used since we use dump_field, but required by abstract base class + return f'out.append("\'").append({name}.c_str(), {name}.size()).append("\'");' + + @property + def dump_content(self) -> str: + return f'dump_field(out, "{self.name}", this->{self.field_name});' + + def get_size_calculation(self, name: str, force: bool = False) -> str: + return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}.size());" def get_estimated_size(self) -> int: - # field ID + length varint + typical data (assume small for pointer fields) - return self.calculate_field_id_size() + 2 + 16 + return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string + + +class PackedBufferTypeInfo(TypeInfo): + """Type for packed repeated fields that expose raw buffer instead of decoding. + + When a repeated field is marked with [(packed_buffer) = true], this type + generates code that stores a pointer to the raw protobuf buffer along with + its length and the count of values. This enables zero-copy passthrough when + the consumer can decode the packed varints on-demand. + """ + + def __init__(self, field: descriptor.FieldDescriptorProto) -> None: + # packed_buffer is decode-only (SOURCE_CLIENT messages) + super().__init__(field, needs_decode=True, needs_encode=False) + + @property + def cpp_type(self) -> str: + # Not used - we have multiple fields + return "const uint8_t*" + + @property + def wire_type(self) -> WireType: + """Packed fields use LENGTH_DELIMITED wire type.""" + return WireType.LENGTH_DELIMITED + + @property + def public_content(self) -> list[str]: + """Generate three fields: data pointer, length, and count.""" + return [ + f"const uint8_t *{self.field_name}_data_{{nullptr}};", + f"uint16_t {self.field_name}_length_{{0}};", + f"uint16_t {self.field_name}_count_{{0}};", + ] + + @property + def decode_length_content(self) -> str: + """Store pointer to buffer and calculate count of packed varints.""" + return f"""case {self.number}: {{ + this->{self.field_name}_data_ = value.data(); + this->{self.field_name}_length_ = value.size(); + this->{self.field_name}_count_ = count_packed_varints(value.data(), value.size()); + break; + }}""" + + @property + def encode_content(self) -> str: + """No encoding - this is decode-only for SOURCE_CLIENT messages.""" + return None + + @property + def dump_content(self) -> str: + """Dump shows buffer info but not decoded values.""" + return ( + f'out.append(" {self.name}: ");\n' + + 'out.append("packed buffer [");\n' + + f"append_uint(out, this->{self.field_name}_count_);\n" + + 'out.append(" values, ");\n' + + f"append_uint(out, this->{self.field_name}_length_);\n" + + 'out.append(" bytes]\\n");' + ) + + def dump(self, name: str) -> str: + """Dump method for packed buffer - not typically used but required by abstract base.""" + return 'out.append("packed buffer");' + + def get_size_calculation(self, name: str, force: bool = False) -> str: + """No size calculation needed - decode-only.""" + return "" + + def get_estimated_size(self) -> int: + """Estimate size for packed buffer field. + + Typical IR/RF timing array has ~50-200 values, each encoded as 1-3 bytes. + Estimate 100 values * 2 bytes = 200 bytes typical. + """ + return ( + self.calculate_field_id_size() + 2 + 200 + ) # field ID + length varint + data + + @classmethod + def can_use_dump_field(cls) -> bool: + return False class FixedArrayBytesType(TypeInfo): @@ -989,10 +1083,10 @@ class FixedArrayBytesType(TypeInfo): @property def dump_content(self) -> str: - o = f'out.append(" {self.name}: ");\n' - o += f"out.append(format_hex_pretty(this->{self.field_name}, this->{self.field_name}_len));\n" - o += 'out.append("\\n");' - return o + return ( + f'dump_bytes_field(out, "{self.name}", ' + f"this->{self.field_name}, this->{self.field_name}_len);" + ) def get_size_calculation(self, name: str, force: bool = False) -> str: # Use the actual length stored in the _len field @@ -1215,6 +1309,9 @@ class FixedArrayRepeatedType(TypeInfo): """Helper to generate encode statement for a single element.""" if isinstance(self._ti, EnumType): return f"buffer.{self._ti.encode_func}({self.number}, static_cast({element}), true);" + # MessageType.encode_message doesn't have a force parameter + if isinstance(self._ti, MessageType): + return f"buffer.{self._ti.encode_func}({self.number}, {element});" return f"buffer.{self._ti.encode_func}({self.number}, {element}, true);" @property @@ -1536,6 +1633,15 @@ class RepeatedTypeInfo(TypeInfo): # std::vector is specialized for bool, reference does not work return isinstance(self._ti, BoolType) + def _encode_element_call(self, element: str) -> str: + """Helper to generate encode call for a single element.""" + if isinstance(self._ti, EnumType): + return f"buffer.{self._ti.encode_func}({self.number}, static_cast({element}), true);" + # MessageType.encode_message doesn't have a force parameter + if isinstance(self._ti, MessageType): + return f"buffer.{self._ti.encode_func}({self.number}, {element});" + return f"buffer.{self._ti.encode_func}({self.number}, {element}, true);" + @property def encode_content(self) -> str: if self._use_pointer: @@ -1546,17 +1652,11 @@ class RepeatedTypeInfo(TypeInfo): o += f" buffer.{self._ti.encode_func}({self.number}, it, strlen(it), true);\n" else: o = f"for (const auto &it : *this->{self.field_name}) {{\n" - if isinstance(self._ti, EnumType): - o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" - else: - o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + o += f" {self._encode_element_call('it')}\n" o += "}" return o o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" - if isinstance(self._ti, EnumType): - o += f" buffer.{self._ti.encode_func}({self.number}, static_cast(it), true);\n" - else: - o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n" + o += f" {self._encode_element_call('it')}\n" o += "}" return o @@ -2116,26 +2216,22 @@ def build_message_type( # dump_to method declaration in header prot = "#ifdef HAS_PROTO_MESSAGE_DUMP\n" - prot += "void dump_to(std::string &out) const override;\n" + prot += "const char *dump_to(DumpBuffer &out) const override;\n" prot += "#endif\n" public_content.append(prot) # dump_to implementation will go in dump_cpp - dump_impl = f"void {desc.name}::dump_to(std::string &out) const {{" + dump_impl = f"const char *{desc.name}::dump_to(DumpBuffer &out) const {{" if dump: - if len(dump) == 1 and len(dump[0]) + len(dump_impl) + 3 < 120: - dump_impl += f" {dump[0]} " - else: - dump_impl += "\n" - dump_impl += f' MessageDumpHelper helper(out, "{desc.name}");\n' - dump_impl += indent("\n".join(dump)) + "\n" + # Always use MessageDumpHelper for consistent output formatting + dump_impl += "\n" + dump_impl += f' MessageDumpHelper helper(out, "{desc.name}");\n' + dump_impl += indent("\n".join(dump)) + "\n" + dump_impl += " return out.c_str();\n" else: - o2 = f'out.append("{desc.name} {{}}");' - if len(dump_impl) + len(o2) + 3 < 120: - dump_impl += f" {o2} " - else: - dump_impl += "\n" - dump_impl += f" {o2}\n" + dump_impl += "\n" + dump_impl += f' out.append("{desc.name} {{}}");\n' + dump_impl += " return out.c_str();\n" dump_impl += "}\n" if base_class: @@ -2423,7 +2519,7 @@ def build_service_message_type( case += "// Empty message: no decode needed\n" if log: case += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" - case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' + case += f'this->log_receive_message_(LOG_STR("{func}"), msg);\n' case += "#endif\n" case += f"this->{func}(msg);\n" case += "break;" @@ -2490,7 +2586,7 @@ namespace esphome::api { namespace esphome::api { // Helper function to append a quoted string, handling empty StringRef -static inline void append_quoted_string(std::string &out, const StringRef &ref) { +static inline void append_quoted_string(DumpBuffer &out, const StringRef &ref) { out.append("'"); if (!ref.empty()) { out.append(ref.c_str()); @@ -2499,88 +2595,103 @@ static inline void append_quoted_string(std::string &out, const StringRef &ref) } // Common helpers for dump_field functions -static inline void append_field_prefix(std::string &out, const char *field_name, int indent) { +static inline void append_field_prefix(DumpBuffer &out, const char *field_name, int indent) { out.append(indent, ' ').append(field_name).append(": "); } -static inline void append_with_newline(std::string &out, const char *str) { +static inline void append_with_newline(DumpBuffer &out, const char *str) { out.append(str); out.append("\\n"); } +static inline void append_uint(DumpBuffer &out, uint32_t value) { + char buf[16]; + snprintf(buf, sizeof(buf), "%" PRIu32, value); + out.append(buf); +} + // RAII helper for message dump formatting class MessageDumpHelper { public: - MessageDumpHelper(std::string &out, const char *message_name) : out_(out) { + MessageDumpHelper(DumpBuffer &out, const char *message_name) : out_(out) { out_.append(message_name); out_.append(" {\\n"); } ~MessageDumpHelper() { out_.append(" }"); } private: - std::string &out_; + DumpBuffer &out_; }; // Helper functions to reduce code duplication in dump methods -static void dump_field(std::string &out, const char *field_name, int32_t value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, int32_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%" PRId32, value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, uint32_t value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, uint32_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%" PRIu32, value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, float value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, float value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%g", value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, uint64_t value, int indent = 2) { char buffer[64]; append_field_prefix(out, field_name, indent); snprintf(buffer, 64, "%" PRIu64, value); append_with_newline(out, buffer); } -static void dump_field(std::string &out, const char *field_name, bool value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, bool value, int indent = 2) { append_field_prefix(out, field_name, indent); out.append(YESNO(value)); out.append("\\n"); } -static void dump_field(std::string &out, const char *field_name, const std::string &value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, const std::string &value, int indent = 2) { append_field_prefix(out, field_name, indent); - out.append("'").append(value).append("'"); + out.append("'").append(value.c_str()).append("'"); out.append("\\n"); } -static void dump_field(std::string &out, const char *field_name, StringRef value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, StringRef value, int indent = 2) { append_field_prefix(out, field_name, indent); append_quoted_string(out, value); out.append("\\n"); } -static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, const char *value, int indent = 2) { append_field_prefix(out, field_name, indent); out.append("'").append(value).append("'"); out.append("\\n"); } template -static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) { +static void dump_field(DumpBuffer &out, const char *field_name, T value, int indent = 2) { append_field_prefix(out, field_name, indent); out.append(proto_enum_to_string(value)); out.append("\\n"); } +// Helper for bytes fields - uses stack buffer to avoid heap allocation +// Buffer sized for 160 bytes of data (480 chars with separators) to fit typical log buffer +static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint8_t *data, size_t len, int indent = 2) { + char hex_buf[format_hex_pretty_size(160)]; + append_field_prefix(out, field_name, indent); + format_hex_pretty_to(hex_buf, data, len); + append_with_newline(out, hex_buf); +} + """ content += "namespace enums {\n\n" @@ -2736,25 +2847,35 @@ static const char *const TAG = "api.service"; hpp += f"class {class_name} : public ProtoService {{\n" hpp += " public:\n" - # Add logging helper method declaration + # Add logging helper method declarations hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" hpp += " protected:\n" - hpp += " void log_send_message_(const char *name, const std::string &dump);\n" + hpp += " void log_send_message_(const char *name, const char *dump);\n" + hpp += ( + " void log_receive_message_(const LogString *name, const ProtoMessage &msg);\n" + ) hpp += " public:\n" hpp += "#endif\n\n" # Add non-template send_message method hpp += " bool send_message(const ProtoMessage &msg, uint8_t message_type) {\n" hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" - hpp += " this->log_send_message_(msg.message_name(), msg.dump());\n" + hpp += " DumpBuffer dump_buf;\n" + hpp += " this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));\n" hpp += "#endif\n" hpp += " return this->send_message_(msg, message_type);\n" hpp += " }\n\n" - # Add logging helper method implementation to cpp + # Add logging helper method implementations to cpp cpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" - cpp += f"void {class_name}::log_send_message_(const char *name, const std::string &dump) {{\n" - cpp += ' ESP_LOGVV(TAG, "send_message %s: %s", name, dump.c_str());\n' + cpp += ( + f"void {class_name}::log_send_message_(const char *name, const char *dump) {{\n" + ) + cpp += ' ESP_LOGVV(TAG, "send_message %s: %s", name, dump);\n' + cpp += "}\n" + cpp += f"void {class_name}::log_receive_message_(const LogString *name, const ProtoMessage &msg) {{\n" + cpp += " DumpBuffer dump_buf;\n" + cpp += ' ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf));\n' cpp += "}\n" cpp += "#endif\n\n" diff --git a/script/ci-custom.py b/script/ci-custom.py index 609d89403f..e63e61e096 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -552,6 +552,8 @@ def convert_path_to_relative(abspath, current): exclude=[ "esphome/components/libretiny/generate_components.py", "esphome/components/web_server/__init__.py", + # const.py has absolute import in docstring example for external components + "esphome/components/esp8266/const.py", ], ) def lint_relative_py_import(fname: Path, line, col, content): @@ -578,6 +580,7 @@ def lint_relative_py_import(fname: Path, line, col, content): ], exclude=[ "esphome/components/socket/headers.h", + "esphome/components/async_tcp/async_tcp.h", "esphome/components/esp32/core.cpp", "esphome/components/esp8266/core.cpp", "esphome/components/rp2040/core.cpp", @@ -616,6 +619,19 @@ def lint_esphome_h(fname, line, col, content): ) +@lint_content_find_check( + "CORE.using_esp_idf", + include=py_include, + exclude=["esphome/core/__init__.py", "script/ci-custom.py"], +) +def lint_using_esp_idf_deprecated(fname, line, col, content): + return ( + f"{highlight('CORE.using_esp_idf')} is deprecated and will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + f"Please use {highlight('CORE.is_esp32')} and/or {highlight('CORE.using_arduino')} instead." + ) + + @lint_content_check(include=["*.h"]) def lint_pragma_once(fname, content): if "#pragma once" not in content: @@ -663,6 +679,55 @@ def lint_trailing_whitespace(fname, match): return "Trailing whitespace detected" +# Heap-allocating helpers that cause fragmentation on long-running embedded devices. +# These return std::string and should be replaced with stack-based alternatives. +HEAP_ALLOCATING_HELPERS = { + "format_hex": "format_hex_to() with a stack buffer", + "format_hex_pretty": "format_hex_pretty_to() with a stack buffer", + "format_mac_address_pretty": "format_mac_addr_upper() with a stack buffer", + "get_mac_address": "get_mac_address_into_buffer() with a stack buffer", + "get_mac_address_pretty": "get_mac_address_pretty_into_buffer() with a stack buffer", + "str_truncate": "removal (function is unused)", + "str_upper_case": "removal (function is unused)", + "str_snake_case": "removal (function is unused)", +} + + +@lint_re_check( + # Use negative lookahead to exclude _to/_into_buffer variants + # format_hex(?!_) ensures we don't match format_hex_to, format_hex_pretty_to, etc. + # get_mac_address(?!_) ensures we don't match get_mac_address_into_buffer, etc. + # CPP_RE_EOL captures rest of line so NOLINT comments are detected + r"[^\w](" + r"format_hex(?!_)|" + r"format_hex_pretty(?!_)|" + r"format_mac_address_pretty|" + r"get_mac_address_pretty(?!_)|" + r"get_mac_address(?!_)|" + r"str_truncate|" + r"str_upper_case|" + r"str_snake_case" + r")\s*\(" + CPP_RE_EOL, + include=cpp_include, + exclude=[ + # The definitions themselves + "esphome/core/helpers.h", + "esphome/core/helpers.cpp", + ], +) +def lint_no_heap_allocating_helpers(fname, match): + func = match.group(1) + replacement = HEAP_ALLOCATING_HELPERS.get(func, "a stack-based alternative") + return ( + f"{highlight(func + '()')} allocates heap memory. On long-running embedded devices, " + f"repeated heap allocations fragment memory over time. Even infrequent allocations " + f"become time bombs - the heap eventually cannot satisfy requests even with free " + f"memory available.\n" + f"Please use {replacement} instead.\n" + f"(If strictly necessary, add `// NOLINT` to the end of the line)" + ) + + @lint_content_find_check( "ESP_LOG", include=["*.h", "*.tcc"], diff --git a/script/ci_memory_impact_extract.py b/script/ci_memory_impact_extract.py index 77d59417e3..dd91fa861c 100755 --- a/script/ci_memory_impact_extract.py +++ b/script/ci_memory_impact_extract.py @@ -92,18 +92,23 @@ def run_detailed_analysis(build_dir: str) -> dict | None: print(f"Build directory not found: {build_dir}", file=sys.stderr) return None - # Find firmware.elf + # Find firmware.elf (or raw_firmware.elf for LibreTiny) elf_path = None for elf_candidate in [ build_path / "firmware.elf", build_path / ".pioenvs" / build_path.name / "firmware.elf", + # LibreTiny uses raw_firmware.elf + build_path / "raw_firmware.elf", + build_path / ".pioenvs" / build_path.name / "raw_firmware.elf", ]: if elf_candidate.exists(): elf_path = str(elf_candidate) break if not elf_path: - print(f"firmware.elf not found in {build_dir}", file=sys.stderr) + print( + f"firmware.elf/raw_firmware.elf not found in {build_dir}", file=sys.stderr + ) return None # Find idedata.json - check multiple locations diff --git a/script/clang-tidy b/script/clang-tidy index 142b616119..17bcafacc7 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -253,19 +253,31 @@ def main(): print(f"Split {args.split_at}/{args.split_num}: checking {len(files)} files") # Print file count before adding header file - print(f"\nTotal files to check: {len(files)}") + print(f"\nTotal cpp files to check: {len(files)}") + + # Add header file for checking (before early exit check) + if args.all_headers and args.split_at in (None, 1): + # When --changed is used, only include changed headers instead of all headers + if args.changed: + all_headers = [ + os.path.relpath(p, cwd) for p in git_ls_files(["esphome/**/*.h"]) + ] + changed_headers = filter_changed(all_headers) + if changed_headers: + build_all_include(changed_headers) + files.insert(0, temp_header_file) + else: + print("No changed headers to check") + else: + build_all_include() + files.insert(0, temp_header_file) + print(f"Added all-include header file, new total: {len(files)}") # Early exit if no files to check if not files: print("No files to check - exiting early") return 0 - # Only build header file if we have actual files to check - if args.all_headers and args.split_at in (None, 1): - build_all_include() - files.insert(0, temp_header_file) - print(f"Added all-include header file, new total: {len(files)}") - # Print final file list before loading idedata print_file_list(files, "Final files to process:") diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 5cc3f2570a..a61c9bf08d 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -89,6 +89,8 @@ class Platform(StrEnum): ESP32_C6_IDF = "esp32-c6-idf" ESP32_S2_IDF = "esp32-s2-idf" ESP32_S3_IDF = "esp32-s3-idf" + BK72XX_ARD = "bk72xx-ard" # LibreTiny BK7231N + RP2040_ARD = "rp2040-ard" # Raspberry Pi Pico # Memory impact analysis constants @@ -120,6 +122,8 @@ PLATFORM_SPECIFIC_COMPONENTS = frozenset( # fastest build times, most sensitive to code size changes # 3. ESP32 IDF - Primary ESP32 platform, most representative of modern ESPHome # 4-6. Other ESP32 variants - Less commonly used but still supported +# 7. BK72XX - LibreTiny platform (good for detecting LibreTiny-specific changes) +# 8. RP2040 - Raspberry Pi Pico platform MEMORY_IMPACT_PLATFORM_PREFERENCE = [ Platform.ESP32_C6_IDF, # ESP32-C6 IDF (newest, supports Thread/Zigbee) Platform.ESP8266_ARD, # ESP8266 Arduino (most memory constrained, fastest builds) @@ -127,6 +131,8 @@ MEMORY_IMPACT_PLATFORM_PREFERENCE = [ Platform.ESP32_C3_IDF, # ESP32-C3 IDF Platform.ESP32_S2_IDF, # ESP32-S2 IDF Platform.ESP32_S3_IDF, # ESP32-S3 IDF + Platform.BK72XX_ARD, # LibreTiny BK7231N + Platform.RP2040_ARD, # Raspberry Pi Pico ] @@ -404,8 +410,8 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None: - wifi_component_esp_idf.cpp, *_idf.h -> ESP32 IDF variants - wifi_component_esp8266.cpp, *_esp8266.h -> ESP8266_ARD - *_esp32*.cpp -> ESP32 IDF (generic) - - *_libretiny.cpp, *_retiny.* -> LibreTiny (not in preference list) - - *_pico.cpp, *_rp2040.* -> RP2040 (not in preference list) + - *_libretiny.cpp, *_bk72*.* -> BK72XX (LibreTiny) + - *_pico.cpp, *_rp2040.* -> RP2040_ARD Args: filename: File path to check @@ -438,12 +444,13 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None: if "esp32" in filename_lower: return Platform.ESP32_IDF - # LibreTiny and RP2040 are not in MEMORY_IMPACT_PLATFORM_PREFERENCE - # so we don't return them as hints - # if "retiny" in filename_lower or "libretiny" in filename_lower: - # return None # No specific LibreTiny platform preference - # if "pico" in filename_lower or "rp2040" in filename_lower: - # return None # No RP2040 platform preference + # LibreTiny (via 'libretiny' pattern or BK72xx-specific files) + if "libretiny" in filename_lower or "bk72" in filename_lower: + return Platform.BK72XX_ARD + + # RP2040 / Raspberry Pi Pico + if "pico" in filename_lower or "rp2040" in filename_lower: + return Platform.RP2040_ARD return None diff --git a/script/helpers.py b/script/helpers.py index 06a50a3092..202ac9b5fc 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -156,22 +156,25 @@ def print_error_for_file(file: str | Path, body: str | None) -> None: print() -def build_all_include() -> None: - # Build a cpp file that includes all header files in this repo. - # Otherwise header-only integrations would not be tested by clang-tidy +def build_all_include(header_files: list[str] | None = None) -> None: + # Build a cpp file that includes header files for clang-tidy to check. + # If header_files is provided, only include those headers. + # Otherwise, include all header files in the esphome directory. - # Use git ls-files to find all .h files in the esphome directory - # This is much faster than walking the filesystem - cmd = ["git", "ls-files", "esphome/**/*.h"] - proc = subprocess.run(cmd, capture_output=True, text=True, check=True) + if header_files is None: + # Use git ls-files to find all .h files in the esphome directory + # This is much faster than walking the filesystem + cmd = ["git", "ls-files", "esphome/**/*.h"] + proc = subprocess.run(cmd, capture_output=True, text=True, check=True) - # Process git output - git already returns paths relative to repo root - headers = [ - f'#include "{include_p}"' - for line in proc.stdout.strip().split("\n") - if (include_p := line.replace(os.path.sep, "/")) - ] + # Process git output - git already returns paths relative to repo root + header_files = [ + line.replace(os.path.sep, "/") + for line in proc.stdout.strip().split("\n") + if line + ] + headers = [f'#include "{h}"' for h in header_files] headers.sort() headers.append("") content = "\n".join(headers) diff --git a/script/helpers_zephyr.py b/script/helpers_zephyr.py index f72b335e64..1242a60cf4 100644 --- a/script/helpers_zephyr.py +++ b/script/helpers_zephyr.py @@ -17,6 +17,7 @@ def load_idedata(environment, temp_folder, platformio_ini): """ #include int main() { return 0;} +extern "C" void zboss_signal_handler() {}; """, encoding="utf-8", ) @@ -27,6 +28,12 @@ int main() { return 0;} CONFIG_NEWLIB_LIBC=y CONFIG_BT=y CONFIG_ADC=y +#zigbee begin +CONFIG_ZIGBEE=y +CONFIG_CRYPTO=y +CONFIG_NVS=y +CONFIG_SETTINGS=y +#zigbee end """, encoding="utf-8", ) @@ -44,10 +51,11 @@ CONFIG_ADC=y def extract_defines(command): define_pattern = re.compile(r"-D\s*([^\s]+)") + ignore_prefixes = ("_ASMLANGUAGE", "NRF_802154_ECB_PRIORITY=") return [ - match + match.replace("\\", "") for match in define_pattern.findall(command) - if match not in ("_ASMLANGUAGE") + if not any(match.startswith(prefix) for prefix in ignore_prefixes) ] def find_cxx_path(commands): diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 322efb701a..72ca3f6e9c 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -13,7 +13,6 @@ CONFIG_ESP_TASK_WDT=y CONFIG_ESP_TASK_WDT_PANIC=y CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n -CONFIG_AUTOSTART_ARDUINO=y # esp32_ble CONFIG_BT_ENABLED=y diff --git a/tests/component_tests/binary_sensor/test_binary_sensor.py b/tests/component_tests/binary_sensor/test_binary_sensor.py index 86e0705023..ce4e64681f 100644 --- a/tests/component_tests/binary_sensor/test_binary_sensor.py +++ b/tests/component_tests/binary_sensor/test_binary_sensor.py @@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main): ) # Then - assert 'bs_1->set_name_and_object_id("test bs1", "test_bs1");' in main_cpp + assert 'bs_1->set_name("test bs1",' in main_cpp assert "bs_1->set_pin(" in main_cpp diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py index b21665288c..797b6fb1a4 100644 --- a/tests/component_tests/button/test_button.py +++ b/tests/component_tests/button/test_button.py @@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/button/test_button.yaml") # Then - assert 'wol_1->set_name_and_object_id("wol_test_1", "wol_test_1");' in main_cpp + assert 'wol_1->set_name("wol_test_1",' in main_cpp assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp diff --git a/tests/component_tests/epaper_spi/__init__.py b/tests/component_tests/epaper_spi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/epaper_spi/test_init.py b/tests/component_tests/epaper_spi/test_init.py new file mode 100644 index 0000000000..71e66cd043 --- /dev/null +++ b/tests/component_tests/epaper_spi/test_init.py @@ -0,0 +1,291 @@ +"""Tests for epaper_spi configuration validation.""" + +from collections.abc import Callable +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.epaper_spi.display import ( + CONFIG_SCHEMA, + FINAL_VALIDATE_SCHEMA, + MODELS, +) +from esphome.components.esp32 import ( + KEY_BOARD, + KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32S3, +) +from esphome.const import ( + CONF_BUSY_PIN, + CONF_CS_PIN, + CONF_DC_PIN, + CONF_DIMENSIONS, + CONF_HEIGHT, + CONF_INIT_SEQUENCE, + CONF_RESET_PIN, + CONF_WIDTH, + PlatformFramework, +) +from esphome.types import ConfigType +from tests.component_tests.types import SetCoreConfigCallable + + +def run_schema_validation( + config: ConfigType, with_final_validate: bool = False +) -> None: + """Run schema validation on a configuration. + + Args: + config: The configuration to validate + with_final_validate: If True, also run final validation (requires full config setup) + """ + result = CONFIG_SCHEMA(config) + if with_final_validate: + FINAL_VALIDATE_SCHEMA(result) + return result + + +@pytest.mark.parametrize( + ("config", "error_match"), + [ + pytest.param( + "a string", + "expected a dictionary", + id="invalid_string_config", + ), + pytest.param( + {"id": "display_id"}, + r"required key not provided @ data\['model'\]", + id="missing_model", + ), + pytest.param( + { + "id": "display_id", + "model": "ssd1677", + "dimensions": {"width": 200, "height": 200}, + }, + r"required key not provided @ data\['dc_pin'\]", + id="missing_dc_pin", + ), + ], +) +def test_basic_configuration_errors( + config: str | ConfigType, + error_match: str, + set_core_config: SetCoreConfigCallable, +) -> None: + """Test basic configuration validation errors""" + + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, + ) + + with pytest.raises(cv.Invalid, match=error_match): + CONFIG_SCHEMA(config) + + +def test_all_predefined_models( + set_core_config: SetCoreConfigCallable, + set_component_config: Callable[[str, Any], None], +) -> None: + """Test all predefined epaper models validate successfully with appropriate defaults.""" + + # Test all models, providing default values where necessary + for name, model in MODELS.items(): + # SEEED models are designed for ESP32-S3 hardware + if name in ("SEEED-EE04-MONO-4.26", "SEEED-RETERMINAL-E1002"): + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={ + KEY_BOARD: "esp32-s3-devkitc-1", + KEY_VARIANT: VARIANT_ESP32S3, + }, + ) + else: + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, + ) + + # Configure SPI component which is required by epaper_spi + set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19}) + + config = {"model": name} + + # Add ID field + config["id"] = "test_display" + + # Add required fields that don't have defaults + # Use safe GPIO pins that work on ESP32 (avoiding flash pins 6-11) + if not model.get_default(CONF_DC_PIN): + config[CONF_DC_PIN] = 21 + + # Add dimensions if not provided by model + if not model.get_default(CONF_WIDTH): + config[CONF_DIMENSIONS] = {CONF_HEIGHT: 240, CONF_WIDTH: 320} + + # Add init sequence if model doesn't provide one + if model.initsequence is None: + config[CONF_INIT_SEQUENCE] = [[0xA0, 0x01]] + + # Add other optional pins that some models might require + if not model.get_default(CONF_BUSY_PIN): + config[CONF_BUSY_PIN] = 22 + + if not model.get_default(CONF_RESET_PIN): + config[CONF_RESET_PIN] = 23 + + if not model.get_default(CONF_CS_PIN): + config[CONF_CS_PIN] = 5 + + run_schema_validation(config) + + +@pytest.mark.parametrize( + "model_name", + [pytest.param(name, id=name.lower()) for name in sorted(MODELS.keys())], +) +def test_individual_models( + model_name: str, + set_core_config: SetCoreConfigCallable, + set_component_config: Callable[[str, Any], None], +) -> None: + """Test each epaper model individually to ensure it validates correctly.""" + # SEEED models are designed for ESP32-S3 hardware + if model_name in ("SEEED-EE04-MONO-4.26", "SEEED-RETERMINAL-E1002"): + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={ + KEY_BOARD: "esp32-s3-devkitc-1", + KEY_VARIANT: VARIANT_ESP32S3, + }, + ) + else: + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, + ) + + # Configure SPI component which is required by epaper_spi + set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19}) + + model = MODELS[model_name] + config: dict[str, Any] = {"model": model_name, "id": "test_display"} + + # Add required fields based on model defaults + # Use safe GPIO pins that work on ESP32 + if not model.get_default(CONF_DC_PIN): + config[CONF_DC_PIN] = 21 + + if not model.get_default(CONF_WIDTH): + config[CONF_DIMENSIONS] = {CONF_HEIGHT: 240, CONF_WIDTH: 320} + + if model.initsequence is None: + config[CONF_INIT_SEQUENCE] = [[0xA0, 0x01]] + + if not model.get_default(CONF_BUSY_PIN): + config[CONF_BUSY_PIN] = 22 + + if not model.get_default(CONF_RESET_PIN): + config[CONF_RESET_PIN] = 23 + + if not model.get_default(CONF_CS_PIN): + config[CONF_CS_PIN] = 5 + + # This should not raise any exceptions + run_schema_validation(config) + + +def test_model_with_explicit_dimensions( + set_core_config: SetCoreConfigCallable, + set_component_config: Callable[[str, Any], None], +) -> None: + """Test model configuration with explicitly provided dimensions.""" + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, + ) + + # Configure SPI component which is required by epaper_spi + set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19}) + + run_schema_validation( + { + "id": "test_display", + "model": "ssd1677", + "dc_pin": 21, + "busy_pin": 22, + "reset_pin": 23, + "cs_pin": 5, + "dimensions": { + "width": 200, + "height": 200, + }, + } + ) + + +def test_model_with_transform( + set_core_config: SetCoreConfigCallable, + set_component_config: Callable[[str, Any], None], +) -> None: + """Test model configuration with transform options.""" + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, + ) + + # Configure SPI component which is required by epaper_spi + set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19}) + + run_schema_validation( + { + "id": "test_display", + "model": "ssd1677", + "dc_pin": 21, + "busy_pin": 22, + "reset_pin": 23, + "cs_pin": 5, + "dimensions": { + "width": 200, + "height": 200, + }, + "transform": { + "mirror_x": True, + "mirror_y": False, + }, + } + ) + + +def test_model_with_full_update_every( + set_core_config: SetCoreConfigCallable, + set_component_config: Callable[[str, Any], None], +) -> None: + """Test model configuration with full_update_every option.""" + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, + ) + + # Configure SPI component which is required by epaper_spi + set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19}) + + run_schema_validation( + { + "id": "test_display", + "model": "ssd1677", + "dc_pin": 21, + "busy_pin": 22, + "reset_pin": 23, + "cs_pin": 5, + "dimensions": { + "width": 200, + "height": 200, + }, + "full_update_every": 10, + } + ) diff --git a/tests/component_tests/image/test_init.py b/tests/component_tests/image/test_init.py index f0b132cef8..930bbac8d1 100644 --- a/tests/component_tests/image/test_init.py +++ b/tests/component_tests/image/test_init.py @@ -9,8 +9,14 @@ from typing import Any import pytest from esphome import config_validation as cv -from esphome.components.image import CONF_TRANSPARENCY, CONFIG_SCHEMA +from esphome.components.image import ( + CONF_TRANSPARENCY, + CONFIG_SCHEMA, + get_all_image_metadata, + get_image_metadata, +) from esphome.const import CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE +from esphome.core import CORE @pytest.mark.parametrize( @@ -235,3 +241,112 @@ def test_image_generation( "cat_img = new image::Image(uint8_t_id, 32, 24, image::IMAGE_TYPE_RGB565, image::TRANSPARENCY_OPAQUE);" in main_cpp ) + + +def test_image_to_code_defines_and_core_data( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Test that to_code() sets USE_IMAGE define and stores image metadata.""" + # Generate the main cpp which will call to_code + generate_main(component_config_path("image_test.yaml")) + + # Verify USE_IMAGE define was added + assert any(d.name == "USE_IMAGE" for d in CORE.defines), ( + "USE_IMAGE define should be set when images are configured" + ) + + # Use the public API to get image metadata + # The test config has an image with id 'cat_img' + cat_img_metadata = get_image_metadata("cat_img") + + assert cat_img_metadata is not None, ( + "Image metadata should be retrievable via get_image_metadata()" + ) + + # Verify the metadata has the expected attributes + assert hasattr(cat_img_metadata, "width"), "Metadata should have width attribute" + assert hasattr(cat_img_metadata, "height"), "Metadata should have height attribute" + assert hasattr(cat_img_metadata, "image_type"), ( + "Metadata should have image_type attribute" + ) + assert hasattr(cat_img_metadata, "transparency"), ( + "Metadata should have transparency attribute" + ) + + # Verify the values are correct (from the test image) + assert cat_img_metadata.width == 32, "Width should be 32" + assert cat_img_metadata.height == 24, "Height should be 24" + assert cat_img_metadata.image_type == "RGB565", "Type should be RGB565" + assert cat_img_metadata.transparency == "opaque", "Transparency should be opaque" + + +def test_image_to_code_multiple_images( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Test that to_code() stores metadata for multiple images.""" + generate_main(component_config_path("image_test.yaml")) + + # Use the public API to get all image metadata + all_metadata = get_all_image_metadata() + + assert isinstance(all_metadata, dict), ( + "get_all_image_metadata() should return a dictionary" + ) + + # Verify that at least one image is present + assert len(all_metadata) > 0, "Should have at least one image metadata entry" + + # Each image ID should map to an ImageMetaData object + for image_id, metadata in all_metadata.items(): + assert isinstance(image_id, str), "Image IDs should be strings" + + # Verify it's an ImageMetaData object with all required attributes + assert hasattr(metadata, "width"), ( + f"Metadata for '{image_id}' should have width" + ) + assert hasattr(metadata, "height"), ( + f"Metadata for '{image_id}' should have height" + ) + assert hasattr(metadata, "image_type"), ( + f"Metadata for '{image_id}' should have image_type" + ) + assert hasattr(metadata, "transparency"), ( + f"Metadata for '{image_id}' should have transparency" + ) + + # Verify values are valid + assert isinstance(metadata.width, int), ( + f"Width for '{image_id}' should be an integer" + ) + assert isinstance(metadata.height, int), ( + f"Height for '{image_id}' should be an integer" + ) + assert isinstance(metadata.image_type, str), ( + f"Type for '{image_id}' should be a string" + ) + assert isinstance(metadata.transparency, str), ( + f"Transparency for '{image_id}' should be a string" + ) + assert metadata.width > 0, f"Width for '{image_id}' should be positive" + assert metadata.height > 0, f"Height for '{image_id}' should be positive" + + +def test_get_image_metadata_nonexistent() -> None: + """Test that get_image_metadata returns None for non-existent image IDs.""" + # This should return None when no images are configured or ID doesn't exist + metadata = get_image_metadata("nonexistent_image_id") + assert metadata is None, ( + "get_image_metadata should return None for non-existent IDs" + ) + + +def test_get_all_image_metadata_empty() -> None: + """Test that get_all_image_metadata returns empty dict when no images configured.""" + # When CORE hasn't been initialized with images, should return empty dict + all_metadata = get_all_image_metadata() + assert isinstance(all_metadata, dict), ( + "get_all_image_metadata should always return a dict" + ) + # Length could be 0 or more depending on what's in CORE at test time diff --git a/tests/component_tests/mipi_spi/conftest.py b/tests/component_tests/mipi_spi/conftest.py index c3070c7965..eddf0987d0 100644 --- a/tests/component_tests/mipi_spi/conftest.py +++ b/tests/component_tests/mipi_spi/conftest.py @@ -20,9 +20,9 @@ def choose_variant_with_pins() -> Generator[Callable[[list], None]]: """ def chooser(pins: list) -> None: - for v in VARIANTS: + for variant in VARIANTS: try: - CORE.data[KEY_ESP32][KEY_VARIANT] = v + CORE.data[KEY_ESP32][KEY_VARIANT] = variant for pin in pins: if pin is not None: pin = gpio_pin_schema( diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index 0c7dea2286..bae39d3879 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -1,4 +1,4 @@ -"""Tests for mpip_spi configuration validation.""" +"""Tests for mipi_spi configuration validation.""" from collections.abc import Callable from pathlib import Path @@ -227,23 +227,6 @@ def test_esp32s3_specific_errors( run_schema_validation(config) -def test_framework_specific_errors( - set_core_config: SetCoreConfigCallable, -) -> None: - """Test framework-specific configuration errors""" - - set_core_config( - PlatformFramework.ESP32_ARDUINO, - platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, - ) - - with pytest.raises( - cv.Invalid, - match=r"This feature is only available with framework\(s\) esp-idf", - ): - run_schema_validation({"model": "wt32-sc01-plus"}) - - def test_custom_model_with_all_options( set_core_config: SetCoreConfigCallable, ) -> None: diff --git a/tests/component_tests/text/test_text.py b/tests/component_tests/text/test_text.py index bfc3131f6d..6b047bc62f 100644 --- a/tests/component_tests/text/test_text.py +++ b/tests/component_tests/text/test_text.py @@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text/test_text.yaml") # Then - assert 'it_1->set_name_and_object_id("test 1 text", "test_1_text");' in main_cpp + assert 'it_1->set_name("test 1 text",' in main_cpp def test_text_config_value_internal_set(generate_main): diff --git a/tests/component_tests/text_sensor/test_text_sensor.py b/tests/component_tests/text_sensor/test_text_sensor.py index 934ee67cef..1593d0b6d8 100644 --- a/tests/component_tests/text_sensor/test_text_sensor.py +++ b/tests/component_tests/text_sensor/test_text_sensor.py @@ -25,18 +25,9 @@ def test_text_sensor_sets_mandatory_fields(generate_main): main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml") # Then - assert ( - 'ts_1->set_name_and_object_id("Template Text Sensor 1", "template_text_sensor_1");' - in main_cpp - ) - assert ( - 'ts_2->set_name_and_object_id("Template Text Sensor 2", "template_text_sensor_2");' - in main_cpp - ) - assert ( - 'ts_3->set_name_and_object_id("Template Text Sensor 3", "template_text_sensor_3");' - in main_cpp - ) + assert 'ts_1->set_name("Template Text Sensor 1",' in main_cpp + assert 'ts_2->set_name("Template Text Sensor 2",' in main_cpp + assert 'ts_3->set_name("Template Text Sensor 3",' in main_cpp def test_text_sensor_config_value_internal_set(generate_main): diff --git a/tests/components/ac_dimmer/test.esp32-ard.yaml b/tests/components/ac_dimmer/test.esp32-ard.yaml deleted file mode 100644 index eaa4901f03..0000000000 --- a/tests/components/ac_dimmer/test.esp32-ard.yaml +++ /dev/null @@ -1,5 +0,0 @@ -substitutions: - gate_pin: GPIO4 - zero_cross_pin: GPIO5 - -<<: !include common.yaml diff --git a/tests/components/ac_dimmer/test.esp32-idf.yaml b/tests/components/ac_dimmer/test.esp32-idf.yaml new file mode 100644 index 0000000000..3ec069f430 --- /dev/null +++ b/tests/components/ac_dimmer/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + gate_pin: GPIO18 + zero_cross_pin: GPIO19 + +<<: !include common.yaml diff --git a/tests/components/animation/test.rp2040-ard.yaml b/tests/components/animation/test.rp2040-ard.yaml index 32fb4efb04..2c99e937f3 100644 --- a/tests/components/animation/test.rp2040-ard.yaml +++ b/tests/components/animation/test.rp2040-ard.yaml @@ -11,3 +11,4 @@ display: dc_pin: 21 reset_pin: 22 invert_colors: false + data_rate: 10MHz diff --git a/tests/components/aqi/common.yaml b/tests/components/aqi/common.yaml new file mode 100644 index 0000000000..4c8cbbfa3f --- /dev/null +++ b/tests/components/aqi/common.yaml @@ -0,0 +1,22 @@ +sensor: + - platform: template + id: pm25_sensor + name: "PM2.5" + lambda: "return 25.0;" + + - platform: template + id: pm10_sensor + name: "PM10" + lambda: "return 50.0;" + + - platform: aqi + name: "Air Quality Index (AQI)" + pm_2_5: pm25_sensor + pm_10_0: pm10_sensor + calculation_type: AQI + + - platform: aqi + name: "Air Quality Index (CAQI)" + pm_2_5: pm25_sensor + pm_10_0: pm10_sensor + calculation_type: CAQI diff --git a/tests/components/aqi/test.esp32-idf.yaml b/tests/components/aqi/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/aqi/test.esp8266-ard.yaml b/tests/components/aqi/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/aqi/test.rp2040-ard.yaml b/tests/components/aqi/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/aqi/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/bme68x_bsec2_i2c/common.yaml b/tests/components/bme68x_bsec2_i2c/common.yaml index bee964f433..a462bdaf7f 100644 --- a/tests/components/bme68x_bsec2_i2c/common.yaml +++ b/tests/components/bme68x_bsec2_i2c/common.yaml @@ -9,6 +9,7 @@ bme68x_bsec2_i2c: sensor: - platform: bme68x_bsec2 + id: bme_sensor temperature: name: BME68X Temperature pressure: diff --git a/tests/components/bthome_mithermometer/common.yaml b/tests/components/bthome_mithermometer/common.yaml new file mode 100644 index 0000000000..ba94e46878 --- /dev/null +++ b/tests/components/bthome_mithermometer/common.yaml @@ -0,0 +1,15 @@ +esp32_ble_tracker: + +sensor: + - platform: bthome_mithermometer + mac_address: A4:C1:38:4E:16:78 + temperature: + name: "BTHome Temperature" + humidity: + name: "BTHome Humidity" + battery_level: + name: "BTHome Battery" + battery_voltage: + name: "BTHome Battery Voltage" + signal_strength: + name: "BTHome Signal" diff --git a/tests/components/bthome_mithermometer/test.esp32-idf.yaml b/tests/components/bthome_mithermometer/test.esp32-idf.yaml new file mode 100644 index 0000000000..7a6541ae76 --- /dev/null +++ b/tests/components/bthome_mithermometer/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + ble: !include ../../test_build_components/common/ble/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/captive_portal/common.yaml b/tests/components/captive_portal/common.yaml index 25bc4a887a..6180a77502 100644 --- a/tests/components/captive_portal/common.yaml +++ b/tests/components/captive_portal/common.yaml @@ -3,3 +3,4 @@ wifi: password: password1 captive_portal: + compression: br diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml index 93f03e582e..42ec50911f 100644 --- a/tests/components/cc1101/common.yaml +++ b/tests/components/cc1101/common.yaml @@ -20,7 +20,7 @@ cc1101: on_packet: then: - lambda: |- - ESP_LOGD("cc1101", "packet %s rssi %.1f dBm lqi %u", format_hex(x).c_str(), rssi, lqi); + ESP_LOGD("cc1101", "packet %s freq_offset %.0f Hz rssi %.1f dBm lqi %u", format_hex(x).c_str(), freq_offset, rssi, lqi); button: - platform: template diff --git a/tests/components/chsc6x/test.rp2040-ard.yaml b/tests/components/chsc6x/test.rp2040-ard.yaml index 2e3613a4a3..eb21b8ec4b 100644 --- a/tests/components/chsc6x/test.rp2040-ard.yaml +++ b/tests/components/chsc6x/test.rp2040-ard.yaml @@ -9,6 +9,7 @@ display: invert_colors: True cs_pin: 20 dc_pin: 21 + data_rate: 20MHz pages: - id: page1 lambda: |- diff --git a/tests/components/copy/common.yaml b/tests/components/copy/common.yaml index a73b3467e6..a376004b2f 100644 --- a/tests/components/copy/common.yaml +++ b/tests/components/copy/common.yaml @@ -7,6 +7,9 @@ fan: - platform: speed id: fan_speed output: fan_output_1 + preset_modes: + - Eco + - Turbo - platform: copy source_id: fan_speed name: Fan Speed Copy diff --git a/tests/components/deep_sleep/test.bk72xx-ard.yaml b/tests/components/deep_sleep/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..2385fbb4db --- /dev/null +++ b/tests/components/deep_sleep/test.bk72xx-ard.yaml @@ -0,0 +1,14 @@ +deep_sleep: + run_duration: 30s + sleep_duration: 12h + wakeup_pin: + - pin: + number: P6 + - pin: P7 + wakeup_pin_mode: KEEP_AWAKE + - pin: + number: P10 + inverted: true + wakeup_pin_mode: INVERT_WAKEUP + +<<: !include common.yaml diff --git a/tests/components/display/common.yaml b/tests/components/display/common.yaml index 27abb23e03..a722a5f7c2 100644 --- a/tests/components/display/common.yaml +++ b/tests/components/display/common.yaml @@ -6,6 +6,7 @@ display: dc_pin: 13 reset_pin: 21 invert_colors: false + data_rate: 20MHz lambda: |- // Draw an analog clock in the center of the screen int centerX = it.get_width() / 2; diff --git a/tests/components/dsmr/test.esp32-idf.yaml b/tests/components/dsmr/test.esp32-idf.yaml new file mode 100644 index 0000000000..522f60db49 --- /dev/null +++ b/tests/components/dsmr/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + request_pin: GPIO15 + +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/epaper_spi/test.esp32-s3-idf.yaml b/tests/components/epaper_spi/test.esp32-s3-idf.yaml index d330b4127d..621a819c3c 100644 --- a/tests/components/epaper_spi/test.esp32-s3-idf.yaml +++ b/tests/components/epaper_spi/test.esp32-s3-idf.yaml @@ -8,15 +8,39 @@ display: dimensions: width: 800 height: 480 - cs_pin: GPIO5 - dc_pin: GPIO17 - reset_pin: GPIO16 - busy_pin: GPIO4 + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO17 + reset_pin: + allow_other_uses: true + number: GPIO16 + busy_pin: + allow_other_uses: true + number: GPIO4 rotation: 0 update_interval: 60s lambda: |- it.circle(64, 64, 50, Color::BLACK); + - platform: epaper_spi + spi_id: spi_bus + model: waveshare-2.13in-v3 + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO17 + reset_pin: + allow_other_uses: true + number: GPIO16 + busy_pin: + allow_other_uses: true + number: GPIO4 + - platform: epaper_spi model: seeed-reterminal-e1002 - platform: epaper_spi diff --git a/tests/components/esp32/test.esp32-idf.yaml b/tests/components/esp32/test.esp32-idf.yaml index 6338fe98dd..0e220623a1 100644 --- a/tests/components/esp32/test.esp32-idf.yaml +++ b/tests/components/esp32/test.esp32-idf.yaml @@ -3,6 +3,7 @@ esp32: framework: type: esp-idf advanced: + enable_ota_rollback: true enable_lwip_mdns_queries: true enable_lwip_bridge_interface: true disable_libc_locks_in_iram: false # Test explicit opt-out of RAM optimization diff --git a/tests/components/esp32_can/common.yaml b/tests/components/esp32_can/common.yaml index 4349c470f3..3b9b33c048 100644 --- a/tests/components/esp32_can/common.yaml +++ b/tests/components/esp32_can/common.yaml @@ -18,6 +18,7 @@ canbus: tx_pin: ${tx_pin} can_id: 4 bit_rate: 50kbps + mode: NORMAL on_frame: - can_id: 500 then: diff --git a/tests/components/esp32_can/test.esp32-c6-idf.yaml b/tests/components/esp32_can/test.esp32-c6-idf.yaml index 6ef730c378..ac978482fc 100644 --- a/tests/components/esp32_can/test.esp32-c6-idf.yaml +++ b/tests/components/esp32_can/test.esp32-c6-idf.yaml @@ -12,17 +12,7 @@ esphome: canbus_id: esp32_internal_can can_id: 0x100 data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] - - canbus.send: - # Extended ID explicit - canbus_id: esp32_internal_can_2 - use_extended_id: true - can_id: 0x100 - data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] - - canbus.send: - # Standard ID by default - canbus_id: esp32_internal_can_2 - can_id: 0x100 - data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + # Note: esp32_internal_can_2 uses LISTENONLY mode, so no send actions canbus: - platform: esp32_can @@ -31,6 +21,7 @@ canbus: tx_pin: GPIO7 can_id: 4 bit_rate: 50kbps + mode: NORMAL on_frame: - can_id: 500 then: @@ -62,6 +53,7 @@ canbus: tx_pin: GPIO9 can_id: 4 bit_rate: 50kbps + mode: LISTENONLY on_frame: - can_id: 500 then: diff --git a/tests/components/esp32_hosted/test.esp32-p4-idf.yaml b/tests/components/esp32_hosted/test-embedded.esp32-p4-idf.yaml similarity index 92% rename from tests/components/esp32_hosted/test.esp32-p4-idf.yaml rename to tests/components/esp32_hosted/test-embedded.esp32-p4-idf.yaml index 2a76f17e2f..9640032b34 100644 --- a/tests/components/esp32_hosted/test.esp32-p4-idf.yaml +++ b/tests/components/esp32_hosted/test-embedded.esp32-p4-idf.yaml @@ -3,5 +3,6 @@ update: - platform: esp32_hosted name: "Coprocessor Firmware Update" + type: embedded path: $component_dir/test_firmware.bin sha256: de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31 diff --git a/tests/components/esp32_hosted/test-http.esp32-p4-idf.yaml b/tests/components/esp32_hosted/test-http.esp32-p4-idf.yaml new file mode 100644 index 0000000000..17cde0f35d --- /dev/null +++ b/tests/components/esp32_hosted/test-http.esp32-p4-idf.yaml @@ -0,0 +1,10 @@ +<<: !include common.yaml + +http_request: + +update: + - platform: esp32_hosted + name: "Coprocessor Firmware Update" + type: http + source: https://esphome.github.io/esp-hosted-firmware/manifest/esp32c6.json + update_interval: 6h diff --git a/tests/components/event/common.yaml b/tests/components/event/common.yaml index 71cc19a6b0..555d049c70 100644 --- a/tests/components/event/common.yaml +++ b/tests/components/event/common.yaml @@ -7,3 +7,14 @@ event: - template_event_type2 on_event: - logger.log: Event fired + - lambda: |- + // Test get_last_event_type() returns StringRef + if (id(some_event).has_event()) { + auto event_type = id(some_event).get_last_event_type(); + // Compare with string literal using == + if (event_type == "template_event_type1") { + ESP_LOGD("test", "Event type is template_event_type1"); + } + // Log using %.*s format for StringRef + ESP_LOGD("test", "Event type: %.*s", (int) event_type.size(), event_type.c_str()); + } diff --git a/tests/components/fan/common.yaml b/tests/components/fan/common.yaml index 55c2a656fd..099bbfef08 100644 --- a/tests/components/fan/common.yaml +++ b/tests/components/fan/common.yaml @@ -9,3 +9,51 @@ fan: has_oscillating: true has_direction: true speed_count: 3 + +# Test lambdas using get_preset_mode() which returns StringRef +# These examples match the migration guide in the PR description +binary_sensor: + - platform: template + id: fan_has_preset + name: "Fan Has Preset" + lambda: |- + // Migration guide: Checking if preset mode is set + // Use empty() or has_preset_mode() + if (!id(test_fan).get_preset_mode().empty()) { + // preset is set + } + if (id(test_fan).has_preset_mode()) { + // preset is set + } + + // Migration guide: Comparing preset mode + // Use == operator directly (safe, works even when empty) + if (id(test_fan).get_preset_mode() == "Eco") { + return true; + } + + // Migration guide: Checking for no preset + if (id(test_fan).get_preset_mode().empty()) { + // no preset + } + if (!id(test_fan).has_preset_mode()) { + // no preset + } + + // Migration guide: Getting as std::string + std::string preset = std::string(id(test_fan).get_preset_mode()); + + // Migration guide: Logging option 1 + // Use .c_str() - works because StringRef points to null-terminated string in traits + ESP_LOGD("test", "Preset: %s", id(test_fan).get_preset_mode().c_str()); + + // Migration guide: Logging option 2 + // Use %.*s format (safer, no null-termination assumption) + auto preset_ref = id(test_fan).get_preset_mode(); + ESP_LOGD("test", "Preset: %.*s", (int)preset_ref.size(), preset_ref.c_str()); + + // Test != comparison + if (id(test_fan).get_preset_mode() != "Sleep") { + return true; + } + return false; diff --git a/tests/components/hmac_md5/common.yaml b/tests/components/hmac_md5/common.yaml new file mode 100644 index 0000000000..ac6d7ecbaa --- /dev/null +++ b/tests/components/hmac_md5/common.yaml @@ -0,0 +1,34 @@ +esphome: + on_boot: + - lambda: |- + // Test HMAC-MD5 functionality + #ifdef USE_MD5 + using esphome::hmac_md5::HmacMD5; + HmacMD5 hmac; + + // Test with key "key" and message "The quick brown fox jumps over the lazy dog" + const char* key = "key"; + const char* message = "The quick brown fox jumps over the lazy dog"; + + hmac.init(key, strlen(key)); + hmac.add(message, strlen(message)); + hmac.calculate(); + + char hex_output[33]; + hmac.get_hex(hex_output); + hex_output[32] = '\0'; + + ESP_LOGD("HMAC_MD5", "HMAC-MD5('%s', '%s') = %s", key, message, hex_output); + + // Expected: 80070713463e7749b90c2dc24911e275 + const char* expected = "80070713463e7749b90c2dc24911e275"; + if (strcmp(hex_output, expected) == 0) { + ESP_LOGI("HMAC_MD5", "Test PASSED"); + } else { + ESP_LOGE("HMAC_MD5", "Test FAILED. Expected %s", expected); + } + #else + ESP_LOGW("HMAC_MD5", "HMAC-MD5 not available on this platform"); + #endif + +hmac_md5: diff --git a/tests/components/hmac_md5/test.bk72xx-ard.yaml b/tests/components/hmac_md5/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.bk72xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_md5/test.esp32-idf.yaml b/tests/components/hmac_md5/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_md5/test.esp8266-ard.yaml b/tests/components/hmac_md5/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_md5/test.host.yaml b/tests/components/hmac_md5/test.host.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.host.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_md5/test.rp2040-ard.yaml b/tests/components/hmac_md5/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_md5/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/common.yaml b/tests/components/hmac_sha256/common.yaml new file mode 100644 index 0000000000..9bbed295fd --- /dev/null +++ b/tests/components/hmac_sha256/common.yaml @@ -0,0 +1,34 @@ +esphome: + on_boot: + - lambda: |- + // Test HMAC-SHA256 functionality + #ifdef USE_SHA256 + using esphome::hmac_sha256::HmacSHA256; + HmacSHA256 hmac; + + // Test with key "key" and message "The quick brown fox jumps over the lazy dog" + const char* key = "key"; + const char* message = "The quick brown fox jumps over the lazy dog"; + + hmac.init(key, strlen(key)); + hmac.add(message, strlen(message)); + hmac.calculate(); + + char hex_output[65]; + hmac.get_hex(hex_output); + hex_output[64] = '\0'; + + ESP_LOGD("HMAC_SHA256", "HMAC-SHA256('%s', '%s') = %s", key, message, hex_output); + + // Expected: f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8 + const char* expected = "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"; + if (strcmp(hex_output, expected) == 0) { + ESP_LOGI("HMAC_SHA256", "Test PASSED"); + } else { + ESP_LOGE("HMAC_SHA256", "Test FAILED. Expected %s", expected); + } + #else + ESP_LOGW("HMAC_SHA256", "HMAC-SHA256 not available on this platform"); + #endif + +hmac_sha256: diff --git a/tests/components/hmac_sha256/test.bk72xx-ard.yaml b/tests/components/hmac_sha256/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.bk72xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.esp32-ard.yaml b/tests/components/hmac_sha256/test.esp32-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.esp32-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.esp32-idf.yaml b/tests/components/hmac_sha256/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.esp8266-ard.yaml b/tests/components/hmac_sha256/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.host.yaml b/tests/components/hmac_sha256/test.host.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.host.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.rp2040-ard.yaml b/tests/components/hmac_sha256/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hmac_sha256/test.rtl87xx-ard.yaml b/tests/components/hmac_sha256/test.rtl87xx-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/hmac_sha256/test.rtl87xx-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/hub75/common.yaml b/tests/components/hub75/common.yaml new file mode 100644 index 0000000000..87e9e1c128 --- /dev/null +++ b/tests/components/hub75/common.yaml @@ -0,0 +1,12 @@ +esphome: + on_boot: + # Test simple value + - hub75.set_brightness: 200 + + # Test templatable value + - hub75.set_brightness: !lambda 'return 100;' + + # Test with explicit ID + - hub75.set_brightness: + id: my_hub75 + brightness: 50 diff --git a/tests/components/hub75/test.esp32-idf.yaml b/tests/components/hub75/test.esp32-idf.yaml index 9f6bd57292..dad2a02c24 100644 --- a/tests/components/hub75/test.esp32-idf.yaml +++ b/tests/components/hub75/test.esp32-idf.yaml @@ -1,8 +1,3 @@ -esp32: - board: esp32dev - framework: - type: esp-idf - display: - platform: hub75 id: my_hub75 @@ -37,3 +32,5 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + +<<: !include common.yaml diff --git a/tests/components/hub75/test.esp32-s3-idf-board.yaml b/tests/components/hub75/test.esp32-s3-idf-board.yaml index 9568ccf3aa..8c73a0fd44 100644 --- a/tests/components/hub75/test.esp32-s3-idf-board.yaml +++ b/tests/components/hub75/test.esp32-s3-idf-board.yaml @@ -1,8 +1,3 @@ -esp32: - board: esp32-s3-devkitc-1 - framework: - type: esp-idf - display: - platform: hub75 id: hub75_display_board @@ -11,6 +6,7 @@ display: panel_height: 32 double_buffer: true brightness: 128 + gamma_correct: gamma_2_2 pages: - id: page1 lambda: |- @@ -24,3 +20,5 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + +<<: !include common.yaml diff --git a/tests/components/hub75/test.esp32-s3-idf-rotate.yaml b/tests/components/hub75/test.esp32-s3-idf-rotate.yaml new file mode 100644 index 0000000000..9855fcb4e6 --- /dev/null +++ b/tests/components/hub75/test.esp32-s3-idf-rotate.yaml @@ -0,0 +1,39 @@ +display: + - platform: hub75 + id: my_hub75 + board: apollo-automation-rev6 + panel_width: 64 + panel_height: 64 + layout_rows: 1 + layout_cols: 2 + rotation: 90 + bit_depth: 4 + double_buffer: true + auto_clear_enabled: true + update_interval: 16ms + latch_blanking: 1 + clock_speed: 20MHz + lambda: |- + // Test clipping: 8 columns x 4 rows of 16x16 colored squares + Color colors[32] = { + Color(255, 0, 0), Color(0, 255, 0), Color(0, 0, 255), Color(255, 255, 0), + Color(255, 0, 255), Color(0, 255, 255), Color(255, 128, 0), Color(128, 0, 255), + Color(0, 128, 255), Color(255, 0, 128), Color(128, 255, 0), Color(0, 255, 128), + Color(255, 128, 128), Color(128, 255, 128), Color(128, 128, 255), Color(255, 255, 128), + Color(255, 128, 255), Color(128, 255, 255), Color(192, 64, 0), Color(64, 192, 0), + Color(0, 64, 192), Color(192, 0, 64), Color(64, 0, 192), Color(0, 192, 64), + Color(128, 64, 64), Color(64, 128, 64), Color(64, 64, 128), Color(128, 128, 64), + Color(128, 64, 128), Color(64, 128, 128), Color(255, 255, 255), Color(128, 128, 128) + }; + int idx = 0; + for (int row = 0; row < 4; row++) { + for (int col = 0; col < 8; col++) { + // Clipping mode: clip to square bounds, then fill "entire screen" + it.start_clipping(col * 16, row * 16, (col + 1) * 16, (row + 1) * 16); + it.fill(colors[idx]); + it.end_clipping(); + idx++; + } + } + +<<: !include common.yaml diff --git a/tests/components/hub75/test.esp32-s3-idf.yaml b/tests/components/hub75/test.esp32-s3-idf.yaml index db678c98a4..0adb4f991b 100644 --- a/tests/components/hub75/test.esp32-s3-idf.yaml +++ b/tests/components/hub75/test.esp32-s3-idf.yaml @@ -1,8 +1,3 @@ -esp32: - board: esp32-s3-devkitc-1 - framework: - type: esp-idf - display: - platform: hub75 id: my_hub75 @@ -10,6 +5,7 @@ display: panel_height: 32 double_buffer: true brightness: 128 + gamma_correct: cie1931 r1_pin: GPIO42 g1_pin: GPIO41 b1_pin: GPIO40 @@ -37,3 +33,5 @@ display: then: lambda: |- ESP_LOGD("display", "1 -> 2"); + +<<: !include common.yaml diff --git a/tests/components/ili9xxx/common.yaml b/tests/components/ili9xxx/common.yaml index 47384b1398..4665e55e4b 100644 --- a/tests/components/ili9xxx/common.yaml +++ b/tests/components/ili9xxx/common.yaml @@ -11,6 +11,7 @@ display: cs_pin: ${cs_pin1} dc_pin: ${dc_pin1} reset_pin: ${reset_pin1} + data_rate: 20MHz lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ili9xxx @@ -27,5 +28,6 @@ display: reset_pin: ${reset_pin2} auto_clear_enabled: false rotation: 90 + data_rate: 20MHz lambda: |- it.fill(Color::WHITE); diff --git a/tests/components/image/test.rp2040-ard.yaml b/tests/components/image/test.rp2040-ard.yaml index 40f17d57fe..03a9c42a38 100644 --- a/tests/components/image/test.rp2040-ard.yaml +++ b/tests/components/image/test.rp2040-ard.yaml @@ -9,6 +9,7 @@ display: cs_pin: 20 dc_pin: 21 reset_pin: 22 + data_rate: 20MHz invert_colors: true <<: !include common.yaml diff --git a/tests/components/infrared/common.yaml b/tests/components/infrared/common.yaml new file mode 100644 index 0000000000..cd2b10d31b --- /dev/null +++ b/tests/components/infrared/common.yaml @@ -0,0 +1,42 @@ +wifi: + ssid: MySSID + password: password1 + +api: + +remote_transmitter: + id: ir_transmitter + pin: ${tx_pin} + carrier_duty_percent: 50% + +remote_receiver: + id: ir_receiver + pin: ${rx_pin} + +# Test various hardware types with transmitter/receiver using infrared platform +infrared: + # Infrared transmitter + - platform: ir_rf_proxy + id: ir_tx + name: "IR Transmitter" + remote_transmitter_id: ir_transmitter + + # Infrared receiver + - platform: ir_rf_proxy + id: ir_rx + name: "IR Receiver" + remote_receiver_id: ir_receiver + + # RF 433MHz transmitter + - platform: ir_rf_proxy + id: rf_433_tx + name: "RF 433 Transmitter" + frequency: 433 MHz + remote_transmitter_id: ir_transmitter + + # RF 900MHz receiver + - platform: ir_rf_proxy + id: rf_900_rx + name: "RF 900 Receiver" + frequency: 900 MHz + remote_receiver_id: ir_receiver diff --git a/tests/components/infrared/test.esp32-idf.yaml b/tests/components/infrared/test.esp32-idf.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/infrared/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/infrared/test.esp8266-ard.yaml b/tests/components/infrared/test.esp8266-ard.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/infrared/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/infrared/test.rp2040-ard.yaml b/tests/components/infrared/test.rp2040-ard.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/infrared/test.rp2040-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/ir_rf_proxy/common.yaml b/tests/components/ir_rf_proxy/common.yaml new file mode 100644 index 0000000000..cd2b10d31b --- /dev/null +++ b/tests/components/ir_rf_proxy/common.yaml @@ -0,0 +1,42 @@ +wifi: + ssid: MySSID + password: password1 + +api: + +remote_transmitter: + id: ir_transmitter + pin: ${tx_pin} + carrier_duty_percent: 50% + +remote_receiver: + id: ir_receiver + pin: ${rx_pin} + +# Test various hardware types with transmitter/receiver using infrared platform +infrared: + # Infrared transmitter + - platform: ir_rf_proxy + id: ir_tx + name: "IR Transmitter" + remote_transmitter_id: ir_transmitter + + # Infrared receiver + - platform: ir_rf_proxy + id: ir_rx + name: "IR Receiver" + remote_receiver_id: ir_receiver + + # RF 433MHz transmitter + - platform: ir_rf_proxy + id: rf_433_tx + name: "RF 433 Transmitter" + frequency: 433 MHz + remote_transmitter_id: ir_transmitter + + # RF 900MHz receiver + - platform: ir_rf_proxy + id: rf_900_rx + name: "RF 900 Receiver" + frequency: 900 MHz + remote_receiver_id: ir_receiver diff --git a/tests/components/ir_rf_proxy/test.esp32-idf.yaml b/tests/components/ir_rf_proxy/test.esp32-idf.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/ir_rf_proxy/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/ir_rf_proxy/test.esp8266-ard.yaml b/tests/components/ir_rf_proxy/test.esp8266-ard.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/ir_rf_proxy/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/ir_rf_proxy/test.rp2040-ard.yaml b/tests/components/ir_rf_proxy/test.rp2040-ard.yaml new file mode 100644 index 0000000000..b516342f3b --- /dev/null +++ b/tests/components/ir_rf_proxy/test.rp2040-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +<<: !include common.yaml diff --git a/tests/components/ld2410/common.yaml b/tests/components/ld2410/common.yaml index 7d168bf7ec..71b1ffd049 100644 --- a/tests/components/ld2410/common.yaml +++ b/tests/components/ld2410/common.yaml @@ -3,6 +3,7 @@ ld2410: binary_sensor: - platform: ld2410 + id: ld2410_binary has_target: name: presence has_moving_target: @@ -14,6 +15,7 @@ binary_sensor: button: - platform: ld2410 + id: ld2410_button factory_reset: name: factory reset restart: @@ -23,6 +25,7 @@ button: number: - platform: ld2410 + id: ld2410_number light_threshold: name: light threshold timeout: @@ -79,6 +82,7 @@ number: select: - platform: ld2410 + id: ld2410_select distance_resolution: name: distance resolution baud_rate: @@ -90,6 +94,7 @@ select: sensor: - platform: ld2410 + id: ld2410_sensor light: name: light moving_distance: @@ -150,6 +155,7 @@ sensor: switch: - platform: ld2410 + id: ld2410_switch engineering_mode: name: control ld2410 engineering mode bluetooth: @@ -157,6 +163,7 @@ switch: text_sensor: - platform: ld2410 + id: ld2410_textsensor version: name: presenece sensor version mac_address: diff --git a/tests/components/ld2412/common.yaml b/tests/components/ld2412/common.yaml index 18c4612ffe..c5bda688dc 100644 --- a/tests/components/ld2412/common.yaml +++ b/tests/components/ld2412/common.yaml @@ -3,6 +3,7 @@ ld2412: binary_sensor: - platform: ld2412 + id: ld2412_binary dynamic_background_correction_status: name: Dynamic Background Correction Status has_target: @@ -14,6 +15,7 @@ binary_sensor: button: - platform: ld2412 + id: ld2412_button factory_reset: name: Factory reset restart: @@ -25,6 +27,7 @@ button: number: - platform: ld2412 + id: ld2412_number light_threshold: name: Light Threshold timeout: @@ -106,6 +109,7 @@ number: select: - platform: ld2412 + id: ld2412_select light_function: name: Light Function out_pin_level: @@ -129,6 +133,7 @@ select: sensor: - platform: ld2412 + id: ld2412_sensor light: name: Light moving_distance: @@ -214,6 +219,7 @@ sensor: switch: - platform: ld2412 + id: ld2412_switch bluetooth: name: Bluetooth engineering_mode: @@ -221,6 +227,7 @@ switch: text_sensor: - platform: ld2412 + id: ld2412_textsensor version: name: Firmware version mac_address: diff --git a/tests/components/ld2450/common.yaml b/tests/components/ld2450/common.yaml index 9dcefffd09..cfa3c922fc 100644 --- a/tests/components/ld2450/common.yaml +++ b/tests/components/ld2450/common.yaml @@ -3,6 +3,7 @@ ld2450: button: - platform: ld2450 + id: ld2450_button ld2450_id: ld2450_radar factory_reset: name: LD2450 Factory Reset @@ -13,6 +14,7 @@ button: sensor: - platform: ld2450 + id: ld2450_sensor ld2450_id: ld2450_radar target_count: name: Presence Target Count @@ -83,6 +85,7 @@ sensor: binary_sensor: - platform: ld2450 + id: ld2450_binary ld2450_id: ld2450_radar has_target: name: Presence @@ -93,6 +96,7 @@ binary_sensor: switch: - platform: ld2450 + id: ld2450_switch ld2450_id: ld2450_radar bluetooth: name: Bluetooth @@ -101,6 +105,7 @@ switch: text_sensor: - platform: ld2450 + id: ld2450_textsensor ld2450_id: ld2450_radar version: name: LD2450 Firmware @@ -118,6 +123,7 @@ text_sensor: number: - platform: ld2450 + id: ld2450_number ld2450_id: ld2450_radar presence_timeout: name: Timeout @@ -151,6 +157,7 @@ number: select: - platform: ld2450 + id: ld2450_select ld2450_id: ld2450_radar baud_rate: name: Baud Rate diff --git a/tests/components/libretiny/test.ln882x-ard.yaml b/tests/components/libretiny/test.ln882x-ard.yaml new file mode 100644 index 0000000000..fa33431b92 --- /dev/null +++ b/tests/components/libretiny/test.ln882x-ard.yaml @@ -0,0 +1,2 @@ +logger: + level: VERBOSE diff --git a/tests/components/libretiny/test.rtl87xx-ard.yaml b/tests/components/libretiny/test.rtl87xx-ard.yaml new file mode 100644 index 0000000000..fa33431b92 --- /dev/null +++ b/tests/components/libretiny/test.rtl87xx-ard.yaml @@ -0,0 +1,2 @@ +logger: + level: VERBOSE diff --git a/tests/components/light/common.yaml b/tests/components/light/common.yaml index 247fc19aba..55525fc67f 100644 --- a/tests/components/light/common.yaml +++ b/tests/components/light/common.yaml @@ -1,6 +1,65 @@ esphome: on_boot: then: + # Test LightEffect::get_name() returns StringRef + - lambda: |- + // Test LightEffect::get_name() returns StringRef + auto &effects = id(test_monochromatic_light).get_effects(); + if (!effects.empty()) { + // Test: get_name() returns StringRef + StringRef name = effects[0]->get_name(); + + // Test: comparison with string literal works directly + if (name == "Strobe") { + ESP_LOGI("test", "Found Strobe effect"); + } + + // Test: safe logging with %.*s format + ESP_LOGI("test", "Effect name: %.*s", (int) name.size(), name.c_str()); + + // Test: .c_str() for functions expecting const char* + ESP_LOGI("test", "Effect: %s", name.c_str()); + + // Test: explicit conversion to std::string + std::string name_str(name.c_str(), name.size()); + ESP_LOGI("test", "As string: %s", name_str.c_str()); + + // Test: size() method + ESP_LOGI("test", "Name length: %d", (int) name.size()); + } + + # Test LightState::get_effect_name() returns StringRef + - lambda: |- + // Test LightState::get_effect_name() returns StringRef + StringRef current_effect = id(test_monochromatic_light).get_effect_name(); + + // Test: comparison with "None" works directly + if (current_effect == "None") { + ESP_LOGI("test", "No effect active"); + } + + // Test: safe logging + ESP_LOGI("test", "Current effect: %.*s", (int) current_effect.size(), current_effect.c_str()); + + # Test str_equals_case_insensitive with StringRef + - lambda: |- + // Test str_equals_case_insensitive(StringRef, StringRef) + auto &effects = id(test_monochromatic_light).get_effects(); + if (!effects.empty()) { + StringRef name = effects[0]->get_name(); + + // Test: case-insensitive comparison + if (str_equals_case_insensitive(name, "STROBE")) { + ESP_LOGI("test", "Case-insensitive match works"); + } + + // Test: case-insensitive with StringRef from string + std::string search = "strobe"; + if (str_equals_case_insensitive(StringRef(search.c_str(), search.size()), name)) { + ESP_LOGI("test", "Reverse comparison works"); + } + } + - light.toggle: test_binary_light - light.turn_off: test_rgb_light - light.turn_on: diff --git a/tests/components/logger/test.bk72xx-ard.yaml b/tests/components/logger/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..c5bc9de6e8 --- /dev/null +++ b/tests/components/logger/test.bk72xx-ard.yaml @@ -0,0 +1,5 @@ +<<: !include common-default_uart.yaml + +logger: + id: logger_id + task_log_buffer_size: 1024B diff --git a/tests/components/logger/test.ln882x-ard.yaml b/tests/components/logger/test.ln882x-ard.yaml new file mode 100644 index 0000000000..c5bc9de6e8 --- /dev/null +++ b/tests/components/logger/test.ln882x-ard.yaml @@ -0,0 +1,5 @@ +<<: !include common-default_uart.yaml + +logger: + id: logger_id + task_log_buffer_size: 1024B diff --git a/tests/components/logger/test.rtl87xx-ard.yaml b/tests/components/logger/test.rtl87xx-ard.yaml new file mode 100644 index 0000000000..c5bc9de6e8 --- /dev/null +++ b/tests/components/logger/test.rtl87xx-ard.yaml @@ -0,0 +1,5 @@ +<<: !include common-default_uart.yaml + +logger: + id: logger_id + task_log_buffer_size: 1024B diff --git a/tests/components/mapping/test.rp2040-ard.yaml b/tests/components/mapping/test.rp2040-ard.yaml index acc305a701..fdfed5f6ab 100644 --- a/tests/components/mapping/test.rp2040-ard.yaml +++ b/tests/components/mapping/test.rp2040-ard.yaml @@ -7,6 +7,7 @@ display: platform: ili9xxx id: main_lcd model: ili9342 + data_rate: 31.25MHz cs_pin: 20 dc_pin: 21 reset_pin: 22 diff --git a/tests/components/mdns/test-comprehensive.esp8266-ard.yaml b/tests/components/mdns/test-comprehensive.esp8266-ard.yaml index 3129ca3143..7d3f61cf08 100644 --- a/tests/components/mdns/test-comprehensive.esp8266-ard.yaml +++ b/tests/components/mdns/test-comprehensive.esp8266-ard.yaml @@ -42,4 +42,3 @@ status_led: # Include API at priority 40 api: - password: "apipassword" diff --git a/tests/components/mhz19/common.yaml b/tests/components/mhz19/common.yaml index 94989fecbe..b12ca50197 100644 --- a/tests/components/mhz19/common.yaml +++ b/tests/components/mhz19/common.yaml @@ -6,3 +6,4 @@ sensor: name: MH-Z19 Temperature automatic_baseline_calibration: false update_interval: 15s + detection_range: 5000ppm diff --git a/tests/components/micronova/common.yaml b/tests/components/micronova/common.yaml index 660970350a..5870d3726f 100644 --- a/tests/components/micronova/common.yaml +++ b/tests/components/micronova/common.yaml @@ -41,7 +41,7 @@ sensor: switch: - platform: micronova stove: - name: Stove on/off + name: Stove text_sensor: - platform: micronova diff --git a/tests/components/midea/common.yaml b/tests/components/midea/common.yaml index fec85aee96..c7b18a6701 100644 --- a/tests/components/midea/common.yaml +++ b/tests/components/midea/common.yaml @@ -12,6 +12,25 @@ climate: x.set_mode(CLIMATE_MODE_FAN_ONLY); on_state: - logger.log: State changed! + - lambda: |- + // Test get_custom_fan_mode() returns StringRef + if (id(midea_unit).has_custom_fan_mode()) { + auto fan_mode = id(midea_unit).get_custom_fan_mode(); + // Compare with string literal using == + if (fan_mode == "SILENT") { + ESP_LOGD("test", "Fan mode is SILENT"); + } + // Log using %.*s format for StringRef + ESP_LOGD("test", "Custom fan mode: %.*s", (int) fan_mode.size(), fan_mode.c_str()); + } + // Test get_custom_preset() returns StringRef + if (id(midea_unit).has_custom_preset()) { + auto preset = id(midea_unit).get_custom_preset(); + // Check if empty + if (!preset.empty()) { + ESP_LOGD("test", "Custom preset: %.*s", (int) preset.size(), preset.c_str()); + } + } transmitter_id: xmitr period: 1s num_attempts: 5 diff --git a/tests/components/mmc5603/common.yaml b/tests/components/mmc5603/common.yaml index 6f6e35e9af..ddb5c3b25e 100644 --- a/tests/components/mmc5603/common.yaml +++ b/tests/components/mmc5603/common.yaml @@ -2,6 +2,7 @@ sensor: - platform: mmc5603 i2c_id: i2c_bus address: 0x30 + auto_set_reset: true field_strength_x: name: HMC5883L Field Strength X field_strength_y: diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index 284ac30337..4cf2692593 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -57,10 +57,14 @@ mqtt: - mqtt.publish: topic: some/topic payload: Hello + - lambda: |- + ESP_LOGD("MQTT", "Session present %d", session_present); on_disconnect: - mqtt.publish: topic: some/topic payload: Good-bye + - lambda: |- + ESP_LOGD("MQTT", "Disconnect reason %d", reason); publish_nan_as_none: false binary_sensor: @@ -87,6 +91,7 @@ button: - platform: template name: "Template Button" state_topic: some/topic/button + command_topic: !lambda return "some/topic/button/command"; qos: 2 on_press: - mqtt.disable @@ -291,7 +296,7 @@ event: fan: - platform: template name: Template Fan - state_topic: some/topic/fan + state_topic: !lambda return "some/topic/fan"; direction_state_topic: some/topic/direction/state direction_command_topic: some/topic/direction/command qos: 2 diff --git a/tests/components/nrf52/test.nrf52-adafruit.yaml b/tests/components/nrf52/test.nrf52-adafruit.yaml index 5fa0d6e88f..0ad31993ae 100644 --- a/tests/components/nrf52/test.nrf52-adafruit.yaml +++ b/tests/components/nrf52/test.nrf52-adafruit.yaml @@ -19,3 +19,5 @@ nrf52: reg0: voltage: 2.1V uicr_erase: true + framework: + version: "2.6.1-7" diff --git a/tests/components/online_image/common-rp2040.yaml b/tests/components/online_image/common-rp2040.yaml index 25891b94bc..bbb514bded 100644 --- a/tests/components/online_image/common-rp2040.yaml +++ b/tests/components/online_image/common-rp2040.yaml @@ -8,6 +8,7 @@ display: spi_id: spi_bus id: main_lcd model: ili9342 + data_rate: 20MHz cs_pin: 20 dc_pin: 17 reset_pin: 21 diff --git a/tests/components/opentherm/common.yaml b/tests/components/opentherm/common.yaml index 1e58a04bf0..fb5fb39eb8 100644 --- a/tests/components/opentherm/common.yaml +++ b/tests/components/opentherm/common.yaml @@ -92,7 +92,7 @@ sensor: ch_pump_starts: name: "Boiler Number of starts CH pump" dhw_pump_valve_starts: - name: "Boiler Number of starts DHW pump/valve" + name: "Boiler Number of starts DHW pump valve" dhw_burner_starts: name: "Boiler Number of starts burner during DHW mode" burner_operation_hours: @@ -139,7 +139,7 @@ binary_sensor: dhw_present: name: "Boiler DHW present" control_type_on_off: - name: "Boiler Control type is on/off" + name: "Boiler Control type is on-off" cooling_supported: name: "Boiler Cooling supported" dhw_storage_tank: @@ -153,9 +153,9 @@ binary_sensor: max_ch_setpoint_transfer_enabled: name: "Boiler CH maximum setpoint transfer enabled" dhw_setpoint_rw: - name: "Boiler DHW setpoint read/write" + name: "Boiler DHW setpoint read-write" max_ch_setpoint_rw: - name: "Boiler CH maximum setpoint read/write" + name: "Boiler CH maximum setpoint read-write" switch: - platform: opentherm diff --git a/tests/components/ota/test.host.yaml b/tests/components/ota/test.host.yaml new file mode 100644 index 0000000000..ae7c4d0add --- /dev/null +++ b/tests/components/ota/test.host.yaml @@ -0,0 +1,4 @@ +<<: !include common.yaml + +#host platform does not support wifi / network is automatically included +wifi: !remove diff --git a/tests/components/qr_code/common.yaml b/tests/components/qr_code/common.yaml index 15b4e387c6..2ffba67763 100644 --- a/tests/components/qr_code/common.yaml +++ b/tests/components/qr_code/common.yaml @@ -5,6 +5,7 @@ display: cs_pin: ${cs_pin} dc_pin: ${dc_pin} reset_pin: ${reset_pin} + data_rate: 500kHz invert_colors: false lambda: |- // Draw a QR code in the center of the screen diff --git a/tests/components/rd03d/common.yaml b/tests/components/rd03d/common.yaml new file mode 100644 index 0000000000..b13d899ab3 --- /dev/null +++ b/tests/components/rd03d/common.yaml @@ -0,0 +1,59 @@ +rd03d: + id: rd03d_radar + +sensor: + - platform: rd03d + rd03d_id: rd03d_radar + target_count: + name: Target Count + target_1: + x: + name: Target-1 X + y: + name: Target-1 Y + speed: + name: Target-1 Speed + angle: + name: Target-1 Angle + distance: + name: Target-1 Distance + resolution: + name: Target-1 Resolution + target_2: + x: + name: Target-2 X + y: + name: Target-2 Y + speed: + name: Target-2 Speed + angle: + name: Target-2 Angle + distance: + name: Target-2 Distance + resolution: + name: Target-2 Resolution + target_3: + x: + name: Target-3 X + y: + name: Target-3 Y + speed: + name: Target-3 Speed + angle: + name: Target-3 Angle + distance: + name: Target-3 Distance + resolution: + name: Target-3 Resolution + +binary_sensor: + - platform: rd03d + rd03d_id: rd03d_radar + target: + name: Presence + target_1: + name: Target-1 Presence + target_2: + name: Target-2 Presence + target_3: + name: Target-3 Presence diff --git a/tests/components/rd03d/test.esp32-idf.yaml b/tests/components/rd03d/test.esp32-idf.yaml new file mode 100644 index 0000000000..2d29656c94 --- /dev/null +++ b/tests/components/rd03d/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/rd03d/test.esp8266-ard.yaml b/tests/components/rd03d/test.esp8266-ard.yaml new file mode 100644 index 0000000000..5a05efa259 --- /dev/null +++ b/tests/components/rd03d/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/rd03d/test.rp2040-ard.yaml b/tests/components/rd03d/test.rp2040-ard.yaml new file mode 100644 index 0000000000..f1df2daf83 --- /dev/null +++ b/tests/components/rd03d/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index f101eea942..134ad4d046 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -9,6 +9,18 @@ esphome: id: template_sens state: !lambda "return 42.0;" + - water_heater.template.publish: + id: template_water_heater + target_temperature: 50.0 + mode: ECO + + # Templated + - water_heater.template.publish: + id: template_water_heater + current_temperature: !lambda "return 45.0;" + target_temperature: !lambda "return 55.0;" + mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;" + # Test C++ API: set_template() with stateless lambda (no captures) # NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break. - lambda: |- @@ -231,6 +243,7 @@ number: select: - platform: template + id: template_select name: "Template select" optimistic: true options: @@ -238,6 +251,37 @@ select: - two - three initial_option: two + # Test current_option() returning std::string_view - migration guide examples + on_value: + - lambda: |- + // Migration guide: Check if select has a state + // OLD: if (id(template_select).current_option() != nullptr) + // NEW: Check with .empty() + if (!id(template_select).current_option().empty()) { + ESP_LOGI("test", "Select has state"); + } + + // Migration guide: Compare option values + // OLD: if (strcmp(id(template_select).current_option(), "one") == 0) + // NEW: Direct comparison works safely even when empty + if (id(template_select).current_option() == "one") { + ESP_LOGI("test", "Option is 'one'"); + } + if (id(template_select).current_option() != "two") { + ESP_LOGI("test", "Option is not 'two'"); + } + + // Migration guide: Logging options + // Option 1: Using .c_str() - StringRef guarantees null-termination + ESP_LOGI("test", "Current option: %s", id(template_select).current_option().c_str()); + + // Option 2: Using %.*s format with size + auto option = id(template_select).current_option(); + ESP_LOGI("test", "Current option (safe): %.*s", (int) option.size(), option.c_str()); + + // Migration guide: Store in std::string + std::string stored_option(id(template_select).current_option()); + ESP_LOGI("test", "Stored: %s", stored_option.c_str()); lock: - platform: template @@ -299,6 +343,24 @@ alarm_control_panel: codes: - "1234" +water_heater: + - platform: template + id: template_water_heater + name: "Template Water Heater" + optimistic: true + current_temperature: !lambda "return 42.0f;" + mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;" + supported_modes: + - "OFF" + - ECO + - GAS + - ELECTRIC + - HEAT_PUMP + - HIGH_DEMAND + - PERFORMANCE + set_action: + - logger.log: "set_action" + datetime: - platform: template name: Date diff --git a/tests/components/thermostat/common.yaml b/tests/components/thermostat/common.yaml index 4aa87c0ac3..69e258f2e3 100644 --- a/tests/components/thermostat/common.yaml +++ b/tests/components/thermostat/common.yaml @@ -5,6 +5,7 @@ sensor: climate: - platform: thermostat + id: test_thermostat name: Test Thermostat sensor: thermostat_sensor humidity_sensor: thermostat_sensor @@ -15,6 +16,19 @@ climate: - name: Away default_target_temperature_low: 16°C default_target_temperature_high: 20°C + on_state: + - lambda: |- + // Test get_custom_preset() returns std::string_view + // "Default Preset" is a custom preset (not a standard ClimatePreset name) + if (id(test_thermostat).has_custom_preset()) { + auto preset = id(test_thermostat).get_custom_preset(); + // Compare with string literal using == + if (preset == "Default Preset") { + ESP_LOGD("test", "Preset is Default Preset"); + } + // Log using %.*s format for StringRef + ESP_LOGD("test", "Custom preset: %.*s", (int) preset.size(), preset.c_str()); + } idle_action: - logger.log: idle_action cool_action: @@ -41,6 +55,7 @@ climate: - logger.log: dry_mode fan_only_mode: - logger.log: fan_only_mode + heat_cool_mode: true fan_mode_auto_action: - logger.log: fan_mode_auto_action fan_mode_on_action: diff --git a/tests/components/tuya/common.yaml b/tests/components/tuya/common.yaml index e177b7d056..9986d398f1 100644 --- a/tests/components/tuya/common.yaml +++ b/tests/components/tuya/common.yaml @@ -38,11 +38,14 @@ light: dimmer_datapoint: 2 min_value_datapoint: 3 color_temperature_datapoint: 4 + color_datapoint: 5 min_value: 1 max_value: 100 cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds gamma_correct: 1 + color_type: RGB + color_type_lowercase: true number: - platform: tuya diff --git a/tests/components/uart/test.esp32-idf.yaml b/tests/components/uart/test.esp32-idf.yaml index 6ffd0d7282..2a97f9a5de 100644 --- a/tests/components/uart/test.esp32-idf.yaml +++ b/tests/components/uart/test.esp32-idf.yaml @@ -75,3 +75,11 @@ button: - uart.write: !lambda |- std::string cmd = "VALUE=" + str_sprintf("%.0f", id(test_number).state) + "\r\n"; return std::vector(cmd.begin(), cmd.end()); + +event: + - platform: uart + uart_id: uart_uart + name: "UART Event" + event_types: + - "string_event_A": "*A#" + - "bytes_event_B": [0x2A, 0x42, 0x23] diff --git a/tests/components/uart/test.esp8266-ard.yaml b/tests/components/uart/test.esp8266-ard.yaml index 566038ee3e..c2670b289a 100644 --- a/tests/components/uart/test.esp8266-ard.yaml +++ b/tests/components/uart/test.esp8266-ard.yaml @@ -31,3 +31,11 @@ button: name: "UART Button" uart_id: uart_uart data: [0xFF, 0xEE] + +event: + - platform: uart + uart_id: uart_uart + name: "UART Event" + event_types: + - "string_event_A": "*A#" + - "bytes_event_B": [0x2A, 0x42, 0x23] diff --git a/tests/components/update/common.yaml b/tests/components/update/common.yaml index 521a0a6a5c..40042945c8 100644 --- a/tests/components/update/common.yaml +++ b/tests/components/update/common.yaml @@ -9,6 +9,8 @@ esphome: update.is_available: then: - logger.log: "Update available" + else: + - update.check: - update.perform: force_update: true diff --git a/tests/components/water_heater/common.yaml b/tests/components/water_heater/common.yaml new file mode 100644 index 0000000000..8ec2b1b297 --- /dev/null +++ b/tests/components/water_heater/common.yaml @@ -0,0 +1,16 @@ +water_heater: + - platform: template + id: my_boiler + name: "Test Boiler" + min_temperature: 10 + max_temperature: 85 + target_temperature_step: 0.5 + current_temperature_step: 0.1 + optimistic: true + current_temperature: 45.0 + mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;" + visual: + min_temperature: 10 + max_temperature: 85 + target_temperature_step: 0.5 + current_temperature_step: 0.1 diff --git a/tests/components/web_server/common.yaml b/tests/components/web_server/common.yaml index eb768eeb91..35a605484c 100644 --- a/tests/components/web_server/common.yaml +++ b/tests/components/web_server/common.yaml @@ -36,3 +36,5 @@ datetime: optimistic: yes event: update: +water_heater: +infrared: diff --git a/tests/components/web_server/common_v2.yaml b/tests/components/web_server/common_v2.yaml index 2af5ceca44..f2b15e484d 100644 --- a/tests/components/web_server/common_v2.yaml +++ b/tests/components/web_server/common_v2.yaml @@ -4,3 +4,4 @@ packages: web_server: port: 8080 version: 2 + compression: br diff --git a/tests/components/web_server/test_v1.esp32-idf.yaml b/tests/components/web_server/test_v1.esp32-idf.yaml new file mode 100644 index 0000000000..389a930284 --- /dev/null +++ b/tests/components/web_server/test_v1.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common_v1.yaml diff --git a/tests/components/wifi/test.esp32-idf.yaml b/tests/components/wifi/test.esp32-idf.yaml index 3e01d7f990..b2b2233ef3 100644 --- a/tests/components/wifi/test.esp32-idf.yaml +++ b/tests/components/wifi/test.esp32-idf.yaml @@ -14,6 +14,7 @@ esphome: wifi: use_psram: true min_auth_mode: WPA + post_connect_roaming: false manual_ip: static_ip: 192.168.1.100 gateway: 192.168.1.1 diff --git a/tests/components/wifi/test.esp8266-ard.yaml b/tests/components/wifi/test.esp8266-ard.yaml index 9cb0e3cf48..709a639ad6 100644 --- a/tests/components/wifi/test.esp8266-ard.yaml +++ b/tests/components/wifi/test.esp8266-ard.yaml @@ -1,5 +1,6 @@ wifi: min_auth_mode: WPA2 + post_connect_roaming: true packages: - !include common.yaml diff --git a/tests/components/xpt2046/common.yaml b/tests/components/xpt2046/common.yaml index 3a8e3286a6..eddbd24d6a 100644 --- a/tests/components/xpt2046/common.yaml +++ b/tests/components/xpt2046/common.yaml @@ -6,6 +6,7 @@ display: cs_pin: ${disp_cs_pin} dc_pin: ${dc_pin} reset_pin: ${reset_pin} + data_rate: 20MHz invert_colors: false lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/zhlt01/common.yaml b/tests/components/zhlt01/common.yaml index d0fd531c87..483f9f3c4e 100644 --- a/tests/components/zhlt01/common.yaml +++ b/tests/components/zhlt01/common.yaml @@ -1,4 +1,4 @@ climate: - platform: zhlt01 - name: ZH/LT-01 Climate + name: ZH-LT-01 Climate transmitter_id: xmitr diff --git a/tests/components/zigbee/common.yaml b/tests/components/zigbee/common.yaml new file mode 100644 index 0000000000..11100e1e0c --- /dev/null +++ b/tests/components/zigbee/common.yaml @@ -0,0 +1,41 @@ +--- +binary_sensor: + - platform: template + name: "Garage Door Open 1" + - platform: template + name: "Garage Door Open 2" + - platform: template + name: "Garage Door Open 3" + - platform: template + name: "Garage Door Open 4" + - platform: template + name: "Garage Door Open 5" + - platform: template + name: "Garage Door Internal" + internal: True + +sensor: + - platform: template + name: "Analog 1" + lambda: return 10.0; + - platform: template + name: "Analog 2" + lambda: return 11.0; + +zigbee: + wipe_on_boot: true + on_join: + then: + - logger.log: "Joined network" + +output: + - platform: template + id: output_factory + type: binary + write_action: + - zigbee.factory_reset + +switch: + - platform: template + name: "Template Switch" + optimistic: true diff --git a/tests/components/zigbee/test.nrf52-adafruit.yaml b/tests/components/zigbee/test.nrf52-adafruit.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/zigbee/test.nrf52-adafruit.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/zigbee/test.nrf52-mcumgr.yaml b/tests/components/zigbee/test.nrf52-mcumgr.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/zigbee/test.nrf52-mcumgr.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/zigbee/test.nrf52-xiao-ble.yaml b/tests/components/zigbee/test.nrf52-xiao-ble.yaml new file mode 100644 index 0000000000..d2ce552de3 --- /dev/null +++ b/tests/components/zigbee/test.nrf52-xiao-ble.yaml @@ -0,0 +1,5 @@ +<<: !include common.yaml + +zigbee: + wipe_on_boot: once + power_source: battery diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index afd393c095..e6fe733807 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -12,7 +12,7 @@ using namespace esphome; void setup() { - App.pre_setup("livingroom", "LivingRoom", "comment", __DATE__ ", " __TIME__, false); + App.pre_setup("livingroom", "LivingRoom", false); auto *log = new logger::Logger(115200, 512); // NOLINT log->pre_setup(); log->set_uart_selection(logger::UART_SELECTION_UART0); diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 965363972f..50e8d4122b 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -51,6 +51,9 @@ if platform.system() == "Windows": import pty # not available on Windows +# Register assert rewrite for entity_utils so assertions have proper error messages +pytest.register_assert_rewrite("tests.integration.entity_utils") + def _get_platformio_env(cache_dir: Path) -> dict[str, str]: """Get environment variables for PlatformIO with shared cache.""" diff --git a/tests/integration/entity_utils.py b/tests/integration/entity_utils.py new file mode 100644 index 0000000000..7596983ee2 --- /dev/null +++ b/tests/integration/entity_utils.py @@ -0,0 +1,145 @@ +"""Utilities for computing entity object_id in integration tests. + +This module contains the algorithm that aioesphomeapi will use to compute +object_id client-side from API data. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from esphome.helpers import fnv1_hash_object_id, sanitize, snake_case + +if TYPE_CHECKING: + from aioesphomeapi import DeviceInfo, EntityInfo + + +def compute_object_id(name: str) -> str: + """Compute object_id from name using snake_case + sanitize.""" + return sanitize(snake_case(name)) + + +def infer_name_add_mac_suffix(device_info: DeviceInfo) -> bool: + """Infer name_add_mac_suffix from device name ending with MAC suffix.""" + mac_suffix = device_info.mac_address.replace(":", "")[-6:].lower() + return device_info.name.endswith(f"-{mac_suffix}") + + +def _get_name_for_object_id( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> str: + """Get the name used for object_id computation. + + This is the algorithm that aioesphomeapi will use to determine which + name to use for computing object_id client-side from API data. + + Args: + entity: The entity to get name for + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Returns: + The name to use for object_id computation + """ + if entity.name: + # Named entity: use entity name + return entity.name + if entity.device_id != 0: + # Empty name on sub-device: use sub-device name + return device_id_to_name[entity.device_id] + if infer_name_add_mac_suffix(device_info) or device_info.friendly_name: + # Empty name on main device with MAC suffix or friendly_name: use friendly_name + # (even if empty - this is bug-for-bug compatibility for MAC suffix case) + return device_info.friendly_name + # Empty name on main device, no friendly_name: use device name + return device_info.name + + +def compute_entity_object_id( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> str: + """Compute expected object_id for an entity. + + Args: + entity: The entity to compute object_id for + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Returns: + The computed object_id string + """ + name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name) + return compute_object_id(name_for_id) + + +def compute_entity_hash( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> int: + """Compute expected object_id hash for an entity. + + Args: + entity: The entity to compute hash for + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Returns: + The computed FNV-1 hash + """ + name_for_id = _get_name_for_object_id(entity, device_info, device_id_to_name) + return fnv1_hash_object_id(name_for_id) + + +def verify_entity_object_id( + entity: EntityInfo, + device_info: DeviceInfo, + device_id_to_name: dict[int, str], +) -> None: + """Verify an entity's object_id and hash match the expected values. + + Args: + entity: The entity to verify + device_info: Device info from the API + device_id_to_name: Mapping of device_id to device name for sub-devices + + Raises: + AssertionError: If object_id or hash doesn't match expected value + """ + expected_object_id = compute_entity_object_id( + entity, device_info, device_id_to_name + ) + assert entity.object_id == expected_object_id, ( + f"object_id mismatch for entity '{entity.name}': " + f"expected '{expected_object_id}', got '{entity.object_id}'" + ) + + expected_hash = compute_entity_hash(entity, device_info, device_id_to_name) + assert entity.key == expected_hash, ( + f"hash mismatch for entity '{entity.name}': " + f"expected {expected_hash:#x}, got {entity.key:#x}" + ) + + +def verify_all_entities( + entities: list[EntityInfo], + device_info: DeviceInfo, +) -> None: + """Verify all entities have correct object_id and hash values. + + Args: + entities: List of entities to verify + device_info: Device info from the API + + Raises: + AssertionError: If any entity's object_id or hash doesn't match + """ + # Build device_id -> name lookup from sub-devices + device_id_to_name = {d.device_id: d.name for d in device_info.devices} + + for entity in entities: + verify_entity_object_id(entity, device_info, device_id_to_name) diff --git a/tests/integration/fixtures/alarm_control_panel_state_transitions.yaml b/tests/integration/fixtures/alarm_control_panel_state_transitions.yaml new file mode 100644 index 0000000000..1edb401a0d --- /dev/null +++ b/tests/integration/fixtures/alarm_control_panel_state_transitions.yaml @@ -0,0 +1,106 @@ +esphome: + name: alarm-state-transitions + friendly_name: "Alarm Control Panel State Transitions Test" + +logger: + +host: + +globals: + - id: door_sensor_state + type: bool + initial_value: "false" + - id: chime_sensor_state + type: bool + initial_value: "false" + +switch: + # Switch to control the door sensor state + - platform: template + id: door_sensor_switch + name: "Door Sensor Switch" + optimistic: true + turn_on_action: + - globals.set: + id: door_sensor_state + value: "true" + turn_off_action: + - globals.set: + id: door_sensor_state + value: "false" + # Switch to control the chime sensor state + - platform: template + id: chime_sensor_switch + name: "Chime Sensor Switch" + optimistic: true + turn_on_action: + - globals.set: + id: chime_sensor_state + value: "true" + turn_off_action: + - globals.set: + id: chime_sensor_state + value: "false" + +binary_sensor: + - platform: template + id: door_sensor + name: "Door Sensor" + lambda: |- + return id(door_sensor_state); + - platform: template + id: chime_sensor + name: "Chime Sensor" + lambda: |- + return id(chime_sensor_state); + +alarm_control_panel: + - platform: template + id: test_alarm + name: "Test Alarm" + codes: + - "1234" + requires_code_to_arm: true + # Short timeouts for faster testing + arming_away_time: 50ms + arming_home_time: 50ms + arming_night_time: 50ms + pending_time: 50ms + trigger_time: 100ms + restore_mode: ALWAYS_DISARMED + binary_sensors: + - input: door_sensor + bypass_armed_home: false + bypass_armed_night: false + chime: false + trigger_mode: DELAYED + - input: chime_sensor + bypass_armed_home: true + bypass_armed_night: true + chime: true + trigger_mode: DELAYED + on_state: + - logger.log: "State changed" + on_disarmed: + - logger.log: "Alarm disarmed" + on_arming: + - logger.log: "Alarm arming" + on_armed_away: + - logger.log: "Alarm armed away" + on_armed_home: + - logger.log: "Alarm armed home" + on_armed_night: + - logger.log: "Alarm armed night" + on_pending: + - logger.log: "Alarm pending" + on_triggered: + - logger.log: "Alarm triggered" + on_cleared: + - logger.log: "Alarm cleared" + on_chime: + - logger.log: "Chime activated" + on_ready: + - logger.log: "Sensors ready state changed" + +api: + batch_delay: 0ms diff --git a/tests/integration/fixtures/api_conditional_memory.yaml b/tests/integration/fixtures/api_conditional_memory.yaml index 22e8ed79d6..4a0923b93f 100644 --- a/tests/integration/fixtures/api_conditional_memory.yaml +++ b/tests/integration/fixtures/api_conditional_memory.yaml @@ -24,6 +24,14 @@ api: - logger.log: format: "Client %s disconnected from %s" args: [client_info.c_str(), client_address.c_str()] + # Verify fix for issue #11131: api.connected should reflect true state in trigger + - if: + condition: + api.connected: + then: + - logger.log: "Other clients still connected" + else: + - logger.log: "No clients remaining" logger: level: DEBUG diff --git a/tests/integration/fixtures/build_info.yaml b/tests/integration/fixtures/build_info.yaml new file mode 100644 index 0000000000..5d6101543a --- /dev/null +++ b/tests/integration/fixtures/build_info.yaml @@ -0,0 +1,31 @@ +esphome: + name: build-info-test +host: +api: +logger: + +text_sensor: + - platform: template + name: "Config Hash" + id: config_hash_sensor + update_interval: 100ms + lambda: |- + char buf[16]; + snprintf(buf, sizeof(buf), "0x%08x", App.get_config_hash()); + return std::string(buf); + - platform: template + name: "Build Time" + id: build_time_sensor + update_interval: 100ms + lambda: |- + char buf[32]; + snprintf(buf, sizeof(buf), "%ld", (long)App.get_build_time()); + return std::string(buf); + - platform: template + name: "Build Time String" + id: build_time_str_sensor + update_interval: 100ms + lambda: |- + char buf[Application::BUILD_TIME_STR_SIZE]; + App.get_build_time_string(buf); + return std::string(buf); diff --git a/tests/integration/fixtures/fnv1_hash_object_id.yaml b/tests/integration/fixtures/fnv1_hash_object_id.yaml new file mode 100644 index 0000000000..2097b2fbf9 --- /dev/null +++ b/tests/integration/fixtures/fnv1_hash_object_id.yaml @@ -0,0 +1,76 @@ +esphome: + name: fnv1-hash-object-id-test + platformio_options: + build_flags: + - "-DDEBUG" + on_boot: + - lambda: |- + using esphome::fnv1_hash_object_id; + + // Test basic lowercase (hash matches Python fnv1_hash_object_id("foo")) + uint32_t hash_foo = fnv1_hash_object_id("foo", 3); + if (hash_foo == 0x408f5e13) { + ESP_LOGI("FNV1_OID", "foo PASSED"); + } else { + ESP_LOGE("FNV1_OID", "foo FAILED: 0x%08x != 0x408f5e13", hash_foo); + } + + // Test uppercase conversion (should match lowercase) + uint32_t hash_Foo = fnv1_hash_object_id("Foo", 3); + if (hash_Foo == 0x408f5e13) { + ESP_LOGI("FNV1_OID", "upper PASSED"); + } else { + ESP_LOGE("FNV1_OID", "upper FAILED: 0x%08x != 0x408f5e13", hash_Foo); + } + + // Test space to underscore conversion ("foo bar" -> "foo_bar") + uint32_t hash_space = fnv1_hash_object_id("foo bar", 7); + if (hash_space == 0x3ae35aa1) { + ESP_LOGI("FNV1_OID", "space PASSED"); + } else { + ESP_LOGE("FNV1_OID", "space FAILED: 0x%08x != 0x3ae35aa1", hash_space); + } + + // Test underscore preserved ("foo_bar") + uint32_t hash_underscore = fnv1_hash_object_id("foo_bar", 7); + if (hash_underscore == 0x3ae35aa1) { + ESP_LOGI("FNV1_OID", "underscore PASSED"); + } else { + ESP_LOGE("FNV1_OID", "underscore FAILED: 0x%08x != 0x3ae35aa1", hash_underscore); + } + + // Test hyphen preserved ("foo-bar") + uint32_t hash_hyphen = fnv1_hash_object_id("foo-bar", 7); + if (hash_hyphen == 0x438b12e3) { + ESP_LOGI("FNV1_OID", "hyphen PASSED"); + } else { + ESP_LOGE("FNV1_OID", "hyphen FAILED: 0x%08x != 0x438b12e3", hash_hyphen); + } + + // Test special chars become underscore ("foo!bar" -> "foo_bar") + uint32_t hash_special = fnv1_hash_object_id("foo!bar", 7); + if (hash_special == 0x3ae35aa1) { + ESP_LOGI("FNV1_OID", "special PASSED"); + } else { + ESP_LOGE("FNV1_OID", "special FAILED: 0x%08x != 0x3ae35aa1", hash_special); + } + + // Test complex name ("My Sensor Name" -> "my_sensor_name") + uint32_t hash_complex = fnv1_hash_object_id("My Sensor Name", 14); + if (hash_complex == 0x2760962a) { + ESP_LOGI("FNV1_OID", "complex PASSED"); + } else { + ESP_LOGE("FNV1_OID", "complex FAILED: 0x%08x != 0x2760962a", hash_complex); + } + + // Test empty string returns FNV1_OFFSET_BASIS + uint32_t hash_empty = fnv1_hash_object_id("", 0); + if (hash_empty == 0x811c9dc5) { + ESP_LOGI("FNV1_OID", "empty PASSED"); + } else { + ESP_LOGE("FNV1_OID", "empty FAILED: 0x%08x != 0x811c9dc5", hash_empty); + } + +host: +api: +logger: diff --git a/tests/integration/fixtures/fnv1a_hash.yaml b/tests/integration/fixtures/fnv1a_hash.yaml new file mode 100644 index 0000000000..d9c80601b8 --- /dev/null +++ b/tests/integration/fixtures/fnv1a_hash.yaml @@ -0,0 +1,60 @@ +esphome: + name: fnv1a-hash-test + platformio_options: + build_flags: + - "-DDEBUG" + on_boot: + - lambda: |- + using esphome::fnv1a_hash; + using esphome::fnv1a_hash_extend; + + // Test empty string (should return offset basis) + uint32_t hash_empty = fnv1a_hash(""); + if (hash_empty == 0x811c9dc5) { + ESP_LOGI("FNV1A", "empty PASSED"); + } else { + ESP_LOGE("FNV1A", "empty FAILED: 0x%08x != 0x811c9dc5", hash_empty); + } + + // Test known FNV-1a hashes + uint32_t hash_hello = fnv1a_hash("hello"); + if (hash_hello == 0x4f9f2cab) { + ESP_LOGI("FNV1A", "known_hello PASSED"); + } else { + ESP_LOGE("FNV1A", "known_hello FAILED: 0x%08x != 0x4f9f2cab", hash_hello); + } + + uint32_t hash_helloworld = fnv1a_hash("helloworld"); + if (hash_helloworld == 0x3b9f5c61) { + ESP_LOGI("FNV1A", "known_helloworld PASSED"); + } else { + ESP_LOGE("FNV1A", "known_helloworld FAILED: 0x%08x != 0x3b9f5c61", hash_helloworld); + } + + // Test fnv1a_hash_extend consistency + uint32_t hash1 = fnv1a_hash("hello"); + hash1 = fnv1a_hash_extend(hash1, "world"); + uint32_t hash2 = fnv1a_hash("helloworld"); + + if (hash1 == hash2) { + ESP_LOGI("FNV1A", "extend PASSED"); + } else { + ESP_LOGE("FNV1A", "extend FAILED: 0x%08x != 0x%08x", hash1, hash2); + } + + // Test with std::string + std::string str1 = "foo"; + std::string str2 = "bar"; + uint32_t hash3 = fnv1a_hash(str1); + hash3 = fnv1a_hash_extend(hash3, str2); + uint32_t hash4 = fnv1a_hash("foobar"); + + if (hash3 == hash4) { + ESP_LOGI("FNV1A", "string PASSED"); + } else { + ESP_LOGE("FNV1A", "string FAILED: 0x%08x != 0x%08x", hash3, hash4); + } + +host: +api: +logger: diff --git a/tests/integration/fixtures/host_logger_thread_safety.yaml b/tests/integration/fixtures/host_logger_thread_safety.yaml new file mode 100644 index 0000000000..e44a217b2b --- /dev/null +++ b/tests/integration/fixtures/host_logger_thread_safety.yaml @@ -0,0 +1,91 @@ +esphome: + name: host-logger-thread-test +host: +api: +logger: + +button: + - platform: template + name: "Start Thread Race Test" + id: start_test_button + on_press: + - lambda: |- + // Number of threads and messages per thread + static const int NUM_THREADS = 3; + static const int MESSAGES_PER_THREAD = 100; + + // Counters + static std::atomic total_messages_logged{0}; + + // Thread function - must be a regular function pointer for pthread + struct ThreadTest { + static void *thread_func(void *arg) { + int thread_id = *static_cast(arg); + + // Set thread name (different signatures on macOS vs Linux) + char thread_name[16]; + snprintf(thread_name, sizeof(thread_name), "LogThread%d", thread_id); + #ifdef __APPLE__ + pthread_setname_np(thread_name); + #else + pthread_setname_np(pthread_self(), thread_name); + #endif + + // Log messages with different log levels + for (int i = 0; i < MESSAGES_PER_THREAD; i++) { + switch (i % 4) { + case 0: + ESP_LOGI("thread_test", "THREAD%d_MSG%03d_INFO_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + case 1: + ESP_LOGD("thread_test", "THREAD%d_MSG%03d_DEBUG_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + case 2: + ESP_LOGW("thread_test", "THREAD%d_MSG%03d_WARN_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + case 3: + ESP_LOGE("thread_test", "THREAD%d_MSG%03d_ERROR_MESSAGE_WITH_DATA_%08X", + thread_id, i, i * 12345); + break; + } + total_messages_logged.fetch_add(1, std::memory_order_relaxed); + + // Small busy loop to vary timing between threads + int delay_count = (thread_id + 1) * 10; + while (delay_count-- > 0) { + asm volatile("" ::: "memory"); // Prevent optimization + } + } + return nullptr; + } + }; + + ESP_LOGI("thread_test", "RACE_TEST_START: Starting %d threads with %d messages each", + NUM_THREADS, MESSAGES_PER_THREAD); + + // Reset counter for this test run + total_messages_logged.store(0, std::memory_order_relaxed); + + pthread_t threads[NUM_THREADS]; + int thread_ids[NUM_THREADS]; + + // Create all threads + for (int i = 0; i < NUM_THREADS; i++) { + thread_ids[i] = i; + int ret = pthread_create(&threads[i], nullptr, ThreadTest::thread_func, &thread_ids[i]); + if (ret != 0) { + ESP_LOGE("thread_test", "RACE_TEST_ERROR: Failed to create thread %d", i); + return; + } + } + + // Wait for all threads to complete + for (int i = 0; i < NUM_THREADS; i++) { + pthread_join(threads[i], nullptr); + } + + ESP_LOGI("thread_test", "RACE_TEST_COMPLETE: All threads finished, total messages: %d", + total_messages_logged.load(std::memory_order_relaxed)); diff --git a/tests/integration/fixtures/host_mode_api_password.yaml b/tests/integration/fixtures/host_mode_api_password.yaml deleted file mode 100644 index 038b6871e0..0000000000 --- a/tests/integration/fixtures/host_mode_api_password.yaml +++ /dev/null @@ -1,14 +0,0 @@ -esphome: - name: host-mode-api-password -host: -api: - password: "test_password_123" -logger: - level: DEBUG -# Test sensor to verify connection works -sensor: - - platform: template - name: Test Sensor - id: test_sensor - lambda: return 42.0; - update_interval: 0.1s diff --git a/tests/integration/fixtures/object_id_api_verification.yaml b/tests/integration/fixtures/object_id_api_verification.yaml new file mode 100644 index 0000000000..386270fc2c --- /dev/null +++ b/tests/integration/fixtures/object_id_api_verification.yaml @@ -0,0 +1,125 @@ +esphome: + name: object-id-test + friendly_name: Test Device + # Enable MAC suffix - host MAC is 98:35:69:ab:f6:79, suffix is "abf679" + # friendly_name becomes "Test Device abf679" + name_add_mac_suffix: true + # Sub-devices for testing empty-name entities on devices + devices: + - id: sub_device_1 + name: Sub Device One + - id: sub_device_2 + name: Sub Device Two + +host: + +api: + +logger: + +sensor: + # Test 1: Basic name -> object_id = "temperature_sensor" + - platform: template + name: "Temperature Sensor" + id: sensor_basic + lambda: return 42.0; + update_interval: 60s + + # Test 2: Uppercase name -> object_id = "uppercase_name" + - platform: template + name: "UPPERCASE NAME" + id: sensor_uppercase + lambda: return 43.0; + update_interval: 60s + + # Test 3: Special characters -> object_id = "special__chars_" + - platform: template + name: "Special!@Chars#" + id: sensor_special + lambda: return 44.0; + update_interval: 60s + + # Test 4: Hyphen preserved -> object_id = "temp-sensor" + - platform: template + name: "Temp-Sensor" + id: sensor_hyphen + lambda: return 45.0; + update_interval: 60s + + # Test 5: Underscore preserved -> object_id = "temp_sensor" + - platform: template + name: "Temp_Sensor" + id: sensor_underscore + lambda: return 46.0; + update_interval: 60s + + # Test 6: Mixed case with spaces -> object_id = "living_room_temperature" + - platform: template + name: "Living Room Temperature" + id: sensor_mixed + lambda: return 47.0; + update_interval: 60s + + # Test 7: Empty name - uses friendly_name with MAC suffix + # friendly_name = "Test Device abf679" -> object_id = "test_device_abf679" + - platform: template + name: "" + id: sensor_empty_name + lambda: return 48.0; + update_interval: 60s + +binary_sensor: + # Test 8: Different platform same conversion rules + - platform: template + name: "Door Open" + id: binary_door + lambda: return true; + + # Test 9: Numbers in name -> object_id = "sensor_123" + - platform: template + name: "Sensor 123" + id: binary_numbers + lambda: return false; + +switch: + # Test 10: Long name with multiple spaces + - platform: template + name: "My Very Long Switch Name Here" + id: switch_long + lambda: return false; + turn_on_action: + - logger.log: "on" + turn_off_action: + - logger.log: "off" + +text_sensor: + # Test 11: Name starting with number (should work fine) + - platform: template + name: "123 Start" + id: text_num_start + lambda: return {"test"}; + update_interval: 60s + +button: + # Test 12: Named entity on sub-device -> object_id from entity name + - platform: template + name: "Device Button" + id: button_on_device + device_id: sub_device_1 + on_press: [] + + # Test 13: Empty name on sub-device -> object_id from device name + # Device name "Sub Device One" -> object_id = "sub_device_one" + - platform: template + name: "" + id: button_empty_on_device1 + device_id: sub_device_1 + on_press: [] + + # Test 14: Empty name on different sub-device + # Device name "Sub Device Two" -> object_id = "sub_device_two" + - platform: template + name: "" + id: button_empty_on_device2 + device_id: sub_device_2 + on_press: [] diff --git a/tests/integration/fixtures/object_id_friendly_name_no_mac_suffix.yaml b/tests/integration/fixtures/object_id_friendly_name_no_mac_suffix.yaml new file mode 100644 index 0000000000..7a86e37d08 --- /dev/null +++ b/tests/integration/fixtures/object_id_friendly_name_no_mac_suffix.yaml @@ -0,0 +1,27 @@ +esphome: + name: test-device + # friendly_name set but NO MAC suffix + # Empty-name entity should use friendly_name for object_id + friendly_name: My Friendly Device + +host: + +api: + +logger: + +sensor: + # Empty name entity - should use friendly_name for object_id + # friendly_name = "My Friendly Device" -> object_id = "my_friendly_device" + - platform: template + name: "" + id: sensor_empty_name + lambda: return 42.0; + update_interval: 60s + + # Named entity for comparison + - platform: template + name: "Temperature" + id: sensor_named + lambda: return 43.0; + update_interval: 60s diff --git a/tests/integration/fixtures/object_id_no_friendly_name_no_mac_suffix.yaml b/tests/integration/fixtures/object_id_no_friendly_name_no_mac_suffix.yaml new file mode 100644 index 0000000000..4a947e0f6a --- /dev/null +++ b/tests/integration/fixtures/object_id_no_friendly_name_no_mac_suffix.yaml @@ -0,0 +1,25 @@ +esphome: + name: test-device + # No friendly_name set, no MAC suffix + # OLD behavior: object_id = device name because Python pre-computed with fallback + +host: + +api: + +logger: + +sensor: + # Empty name entity - OLD behavior used device name as fallback + - platform: template + name: "" + id: sensor_empty_name + lambda: return 42.0; + update_interval: 60s + + # Named entity for comparison + - platform: template + name: "Temperature" + id: sensor_named + lambda: return 43.0; + update_interval: 60s diff --git a/tests/integration/fixtures/object_id_no_friendly_name_with_mac_suffix.yaml b/tests/integration/fixtures/object_id_no_friendly_name_with_mac_suffix.yaml new file mode 100644 index 0000000000..ab12e670a0 --- /dev/null +++ b/tests/integration/fixtures/object_id_no_friendly_name_with_mac_suffix.yaml @@ -0,0 +1,26 @@ +esphome: + name: test-device + # No friendly_name set, MAC suffix enabled + # OLD behavior: object_id = "" (empty) because is_object_id_dynamic_() used App.get_friendly_name() directly + name_add_mac_suffix: true + +host: + +api: + +logger: + +sensor: + # Empty name entity - OLD behavior produced empty object_id when MAC suffix enabled + - platform: template + name: "" + id: sensor_empty_name + lambda: return 42.0; + update_interval: 60s + + # Named entity for comparison + - platform: template + name: "Temperature" + id: sensor_named + lambda: return 43.0; + update_interval: 60s diff --git a/tests/integration/fixtures/scheduler_numeric_id_test.yaml b/tests/integration/fixtures/scheduler_numeric_id_test.yaml new file mode 100644 index 0000000000..bf60f2fda9 --- /dev/null +++ b/tests/integration/fixtures/scheduler_numeric_id_test.yaml @@ -0,0 +1,173 @@ +esphome: + name: scheduler-numeric-id-test + on_boot: + priority: -100 + then: + - logger.log: "Starting scheduler numeric ID tests" + +host: +api: +logger: + level: VERBOSE + +globals: + - id: timeout_counter + type: int + initial_value: '0' + - id: interval_counter + type: int + initial_value: '0' + - id: retry_counter + type: int + initial_value: '0' + - id: tests_done + type: bool + initial_value: 'false' + - id: results_reported + type: bool + initial_value: 'false' + +script: + - id: test_numeric_ids + then: + - logger.log: "Testing numeric ID timeouts and intervals" + - lambda: |- + auto *component1 = id(test_sensor1); + + // Test 1: Numeric ID with set_timeout (uint32_t) + App.scheduler.set_timeout(component1, 1001U, 50, []() { + ESP_LOGI("test", "Numeric timeout 1001 fired"); + id(timeout_counter) += 1; + }); + + // Test 2: Another numeric ID timeout + App.scheduler.set_timeout(component1, 1002U, 100, []() { + ESP_LOGI("test", "Numeric timeout 1002 fired"); + id(timeout_counter) += 1; + }); + + // Test 3: Numeric ID with set_interval + App.scheduler.set_interval(component1, 2001U, 200, []() { + ESP_LOGI("test", "Numeric interval 2001 fired, count: %d", id(interval_counter)); + id(interval_counter) += 1; + if (id(interval_counter) >= 3) { + App.scheduler.cancel_interval(id(test_sensor1), 2001U); + ESP_LOGI("test", "Cancelled numeric interval 2001"); + } + }); + + // Test 4: Cancel timeout with numeric ID + App.scheduler.set_timeout(component1, 3001U, 5000, []() { + ESP_LOGE("test", "ERROR: Timeout 3001 should have been cancelled"); + }); + App.scheduler.cancel_timeout(component1, 3001U); + ESP_LOGI("test", "Cancelled numeric timeout 3001"); + + // Test 5: Multiple timeouts with same numeric ID - only last should execute + for (int i = 0; i < 5; i++) { + App.scheduler.set_timeout(component1, 4001U, 300 + i*10, [i]() { + ESP_LOGI("test", "Duplicate numeric timeout %d fired", i); + id(timeout_counter) += 1; + }); + } + ESP_LOGI("test", "Created 5 timeouts with same numeric ID 4001"); + + // Test 6: Cancel non-existent numeric ID + bool cancelled_nonexistent = App.scheduler.cancel_timeout(component1, 9999U); + ESP_LOGI("test", "Cancel non-existent numeric ID result: %s", + cancelled_nonexistent ? "true (unexpected!)" : "false (expected)"); + + // Test 7: Component method uint32_t overloads + class TestNumericComponent : public Component { + public: + void test_numeric_methods() { + // Test set_timeout with uint32_t ID + this->set_timeout(5001U, 150, []() { + ESP_LOGI("test", "Component numeric timeout 5001 fired"); + id(timeout_counter) += 1; + }); + + // Test set_interval with uint32_t ID + // Capture 'this' pointer so we can cancel with correct component + auto *self = this; + this->set_interval(5002U, 400, [self]() { + ESP_LOGI("test", "Component numeric interval 5002 fired"); + id(interval_counter) += 1; + // Cancel after first fire - must use same component pointer + App.scheduler.cancel_interval(self, 5002U); + }); + } + }; + + static TestNumericComponent test_component; + test_component.test_numeric_methods(); + + // Test 8: Zero ID (edge case) + App.scheduler.set_timeout(component1, 0U, 200, []() { + ESP_LOGI("test", "Numeric timeout with ID 0 fired"); + id(timeout_counter) += 1; + }); + + // Test 9: Max uint32_t ID (edge case) + App.scheduler.set_timeout(component1, 0xFFFFFFFFU, 250, []() { + ESP_LOGI("test", "Numeric timeout with max ID fired"); + id(timeout_counter) += 1; + }); + + // Test 10: set_retry with numeric ID + App.scheduler.set_retry(component1, 6001U, 50, 3, + [](uint8_t retry_countdown) { + id(retry_counter)++; + ESP_LOGI("test", "Numeric retry 6001 attempt %d (countdown=%d)", + id(retry_counter), retry_countdown); + if (id(retry_counter) >= 2) { + ESP_LOGI("test", "Numeric retry 6001 done"); + return RetryResult::DONE; + } + return RetryResult::RETRY; + }); + + // Test 11: cancel_retry with numeric ID + App.scheduler.set_retry(component1, 6002U, 100, 5, + [](uint8_t retry_countdown) { + ESP_LOGE("test", "ERROR: Numeric retry 6002 should have been cancelled"); + return RetryResult::RETRY; + }); + App.scheduler.cancel_retry(component1, 6002U); + ESP_LOGI("test", "Cancelled numeric retry 6002"); + + - id: report_results + then: + - lambda: |- + ESP_LOGI("test", "Final results - Timeouts: %d, Intervals: %d, Retries: %d", + id(timeout_counter), id(interval_counter), id(retry_counter)); + +sensor: + - platform: template + name: Test Sensor 1 + id: test_sensor1 + lambda: return 1.0; + update_interval: never + +interval: + # Run numeric ID tests after boot + - interval: 0.1s + then: + - if: + condition: + lambda: 'return id(tests_done) == false;' + then: + - lambda: 'id(tests_done) = true;' + - script.execute: test_numeric_ids + - logger.log: "Started numeric ID tests" + + # Report results after tests complete + - interval: 0.2s + then: + - if: + condition: + lambda: 'return id(tests_done) && !id(results_reported);' + then: + - lambda: 'id(results_reported) = true;' + - delay: 1.5s + - script.execute: report_results diff --git a/tests/integration/fixtures/scheduler_retry_test.yaml b/tests/integration/fixtures/scheduler_retry_test.yaml index 11fff6c395..ffe9082a69 100644 --- a/tests/integration/fixtures/scheduler_retry_test.yaml +++ b/tests/integration/fixtures/scheduler_retry_test.yaml @@ -43,9 +43,6 @@ globals: - id: static_char_retry_counter type: int initial_value: '0' - - id: mixed_cancel_result - type: bool - initial_value: 'false' # Using different component types for each test to ensure isolation sensor: @@ -271,23 +268,6 @@ script: ESP_LOGI("test", "Static cancel result: %s", result ? "true" : "false"); }); - # Test 10: Mix string and const char* cancel - - logger.log: "=== Test 10: Mixed string/const char* ===" - - lambda: |- - auto *component = id(immediate_done_sensor); - - // Set with std::string - std::string str_name = "mixed_retry"; - App.scheduler.set_retry(component, str_name, 40, 3, - [](uint8_t retry_countdown) { - ESP_LOGI("test", "Mixed retry - should be cancelled"); - return RetryResult::RETRY; - }); - - // Cancel with const char* - id(mixed_cancel_result) = App.scheduler.cancel_retry(component, "mixed_retry"); - ESP_LOGI("test", "Mixed cancel result: %s", id(mixed_cancel_result) ? "true" : "false"); - # Wait for all tests to complete before reporting - delay: 500ms @@ -303,5 +283,4 @@ script: ESP_LOGI("test", "Multiple same name counter: %d (expected 20+)", id(multiple_same_name_counter)); ESP_LOGI("test", "Const char retry counter: %d (expected 1)", id(const_char_retry_counter)); ESP_LOGI("test", "Static char retry counter: %d (expected 1)", id(static_char_retry_counter)); - ESP_LOGI("test", "Mixed cancel result: %s (expected true)", id(mixed_cancel_result) ? "true" : "false"); ESP_LOGI("test", "All retry tests completed"); diff --git a/tests/integration/fixtures/strftime_to.yaml b/tests/integration/fixtures/strftime_to.yaml new file mode 100644 index 0000000000..bd157e110c --- /dev/null +++ b/tests/integration/fixtures/strftime_to.yaml @@ -0,0 +1,53 @@ +esphome: + name: strftime-to-test +host: +api: +logger: + +time: + - platform: homeassistant + id: ha_time + +text_sensor: + # Test strftime_to with a valid format + - platform: template + name: "Time Format Test" + id: time_format_test + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); // 2024-01-01 00:00:00 UTC + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + size_t len = now.strftime_to(buf, "%Y-%m-%d %H:%M:%S"); + return std::string(buf, len); + + # Test strftime_to with a short format + - platform: template + name: "Time Short Format" + id: time_short_format + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + size_t len = now.strftime_to(buf, "%H:%M"); + return std::string(buf, len); + + # Test strftime (std::string version) still works + - platform: template + name: "Time String Format" + id: time_string_format + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); + return now.strftime("%Y-%m-%d"); + + # Test strftime_to with empty/invalid format returns ERROR + - platform: template + name: "Time Error Format" + id: time_error_format + update_interval: 100ms + lambda: |- + auto now = ESPTime::from_epoch_local(1704067200); + char buf[ESPTime::STRFTIME_BUFFER_SIZE]; + // Empty format string causes strftime to return 0 + size_t len = now.strftime_to(buf, ""); + return std::string(buf, len); diff --git a/tests/integration/fixtures/syslog.yaml b/tests/integration/fixtures/syslog.yaml new file mode 100644 index 0000000000..df376087e3 --- /dev/null +++ b/tests/integration/fixtures/syslog.yaml @@ -0,0 +1,43 @@ +esphome: + name: syslog-test + +host: + +api: + services: + - service: log_long_message + then: + - lambda: |- + // Log a message that exceeds 508 bytes to test truncation + ESP_LOGI("trunctest", "START|%s|END", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + - service: log_short_message + then: + - lambda: |- + // Log a short message that should arrive complete (not truncated) + ESP_LOGI("shorttest", "BEGIN|SHORT_MESSAGE_CONTENT|FINISH"); + +logger: + level: DEBUG + +time: + - platform: host + id: host_time + +udp: + - id: syslog_udp + addresses: + - "127.0.0.1" + +syslog: + udp_id: syslog_udp + time_id: host_time + port: SYSLOG_PORT_PLACEHOLDER + level: DEBUG + strip: true + facility: 16 diff --git a/tests/integration/fixtures/text_command.yaml b/tests/integration/fixtures/text_command.yaml new file mode 100644 index 0000000000..cc91e2f792 --- /dev/null +++ b/tests/integration/fixtures/text_command.yaml @@ -0,0 +1,37 @@ +esphome: + name: host-text-command-test + +host: + +api: + batch_delay: 0ms + +logger: + +text: + - platform: template + name: "Test Text" + id: test_text + optimistic: true + min_length: 0 + max_length: 255 + mode: text + initial_value: "initial" + + - platform: template + name: "Test Password" + id: test_password + optimistic: true + min_length: 4 + max_length: 32 + mode: password + initial_value: "secret" + + - platform: template + name: "Test Text Long" + id: test_text_long + optimistic: true + min_length: 0 + max_length: 255 + mode: text + initial_value: "" diff --git a/tests/integration/fixtures/water_heater_template.yaml b/tests/integration/fixtures/water_heater_template.yaml new file mode 100644 index 0000000000..b54ebed789 --- /dev/null +++ b/tests/integration/fixtures/water_heater_template.yaml @@ -0,0 +1,23 @@ +esphome: + name: water-heater-template-test +host: +api: +logger: + +water_heater: + - platform: template + id: test_boiler + name: Test Boiler + optimistic: true + current_temperature: !lambda "return 45.0f;" + # Note: No mode lambda - we want optimistic mode changes to stick + # A mode lambda would override mode changes in loop() + supported_modes: + - "off" + - eco + - gas + - performance + visual: + min_temperature: 30.0 + max_temperature: 85.0 + target_temperature_step: 0.5 diff --git a/tests/integration/test_alarm_control_panel_state_transitions.py b/tests/integration/test_alarm_control_panel_state_transitions.py new file mode 100644 index 0000000000..09348f5bea --- /dev/null +++ b/tests/integration/test_alarm_control_panel_state_transitions.py @@ -0,0 +1,335 @@ +"""Integration test for alarm control panel state transitions.""" + +from __future__ import annotations + +import asyncio +import re + +import aioesphomeapi +from aioesphomeapi import ( + AlarmControlPanelCommand, + AlarmControlPanelEntityState, + AlarmControlPanelInfo, + AlarmControlPanelState, + SwitchInfo, +) +import pytest + +from .state_utils import InitialStateHelper +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_alarm_control_panel_state_transitions( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test alarm control panel state transitions. + + This comprehensive test verifies all state transitions and listener callbacks: + + 1. Basic arm/disarm sequences: + - DISARMED -> ARMING -> ARMED_AWAY -> DISARMED + - DISARMED -> ARMING -> ARMED_HOME -> DISARMED + - DISARMED -> ARMING -> ARMED_NIGHT -> DISARMED + + 2. Wrong code rejection + + 3. Sensor triggering while armed: + - ARMED_AWAY -> PENDING -> TRIGGERED (delayed sensor) + - TRIGGERED -> ARMED_AWAY (auto-reset after trigger_time, fires on_cleared) + + 4. Chime functionality: + - Sensor open while DISARMED triggers on_chime + + 5. Ready state: + - Sensor state changes trigger on_ready + """ + loop = asyncio.get_running_loop() + + # Track log messages for callback verification + log_lines: list[str] = [] + chime_future: asyncio.Future[bool] = loop.create_future() + ready_futures: list[asyncio.Future[bool]] = [] + cleared_future: asyncio.Future[bool] = loop.create_future() + + # Patterns to match log output from callbacks + chime_pattern = re.compile(r"Chime activated") + ready_pattern = re.compile(r"Sensors ready state changed") + cleared_pattern = re.compile(r"Alarm cleared") + + def on_log_line(line: str) -> None: + log_lines.append(line) + if not chime_future.done() and chime_pattern.search(line): + chime_future.set_result(True) + if ready_pattern.search(line): + # Create new future for each ready event + for fut in ready_futures: + if not fut.done(): + fut.set_result(True) + break + if not cleared_future.done() and cleared_pattern.search(line): + cleared_future.set_result(True) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + entities, _ = await client.list_entities_services() + + # Find entities + alarm_info: AlarmControlPanelInfo | None = None + door_switch_info: SwitchInfo | None = None + chime_switch_info: SwitchInfo | None = None + + for entity in entities: + if isinstance(entity, AlarmControlPanelInfo): + alarm_info = entity + elif isinstance(entity, SwitchInfo): + if entity.name == "Door Sensor Switch": + door_switch_info = entity + elif entity.name == "Chime Sensor Switch": + chime_switch_info = entity + + assert alarm_info is not None, "Alarm control panel not found" + assert door_switch_info is not None, "Door sensor switch not found" + assert chime_switch_info is not None, "Chime sensor switch not found" + + # Track state changes + states_received: list[AlarmControlPanelState] = [] + state_event = asyncio.Event() + + def on_state(state: aioesphomeapi.EntityState) -> None: + if ( + isinstance(state, AlarmControlPanelEntityState) + and state.key == alarm_info.key + ): + states_received.append(state.state) + state_event.set() + + # Use InitialStateHelper to handle initial state broadcast + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for initial states from all entities + await initial_state_helper.wait_for_initial_states() + + # Verify alarm panel started in DISARMED state + initial_alarm_state = initial_state_helper.initial_states.get(alarm_info.key) + assert initial_alarm_state is not None, "No initial alarm state received" + assert isinstance(initial_alarm_state, AlarmControlPanelEntityState) + assert initial_alarm_state.state == AlarmControlPanelState.DISARMED + + # Helper to wait for specific state + async def wait_for_state( + expected: AlarmControlPanelState, timeout: float = 5.0 + ) -> None: + deadline = loop.time() + timeout + while True: + remaining = deadline - loop.time() + if remaining <= 0: + raise TimeoutError( + f"Timeout waiting for state {expected}, " + f"last state: {states_received[-1] if states_received else 'none'}" + ) + await asyncio.wait_for(state_event.wait(), timeout=remaining) + state_event.clear() + if states_received[-1] == expected: + return + + # ===== Test wrong code rejection ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_AWAY, + code="0000", # Wrong code + ) + + # Should NOT transition - wait a bit and verify no state changes + with pytest.raises(asyncio.TimeoutError): + await asyncio.wait_for(state_event.wait(), timeout=0.5) + # No state changes should have occurred (list is empty) + assert len(states_received) == 0, f"Unexpected state changes: {states_received}" + + # ===== Test ARM_AWAY sequence ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_AWAY, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_AWAY) + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # ===== Test ARM_HOME sequence ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_HOME, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_HOME) + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # ===== Test ARM_NIGHT sequence ===== + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_NIGHT, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_NIGHT) + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # Verify basic state sequence (initial DISARMED is handled by InitialStateHelper) + expected_states = [ + AlarmControlPanelState.ARMING, # Arm away + AlarmControlPanelState.ARMED_AWAY, + AlarmControlPanelState.DISARMED, + AlarmControlPanelState.ARMING, # Arm home + AlarmControlPanelState.ARMED_HOME, + AlarmControlPanelState.DISARMED, + AlarmControlPanelState.ARMING, # Arm night + AlarmControlPanelState.ARMED_NIGHT, + AlarmControlPanelState.DISARMED, + ] + assert states_received == expected_states, ( + f"State sequence mismatch.\nExpected: {expected_states}\n" + f"Got: {states_received}" + ) + + # ===== Test PENDING -> TRIGGERED -> CLEARED sequence ===== + # This tests on_pending, on_triggered, and on_cleared callbacks + + # Arm away first + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.ARM_AWAY, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.ARMING) + await wait_for_state(AlarmControlPanelState.ARMED_AWAY) + + # Trip the door sensor (delayed mode triggers PENDING first) + client.switch_command(door_switch_info.key, True) + + # Should go to PENDING (delayed sensor) + await wait_for_state(AlarmControlPanelState.PENDING) + + # Should go to TRIGGERED after pending_time (50ms) + await wait_for_state(AlarmControlPanelState.TRIGGERED) + + # Close the sensor + client.switch_command(door_switch_info.key, False) + + # Wait for trigger_time to expire and auto-reset (100ms) + # The alarm should go back to ARMED_AWAY after trigger_time + # This transition FROM TRIGGERED fires on_cleared + await wait_for_state(AlarmControlPanelState.ARMED_AWAY, timeout=2.0) + + # Verify on_cleared was logged + try: + await asyncio.wait_for(cleared_future, timeout=1.0) + except TimeoutError: + pytest.fail(f"on_cleared callback not fired. Log lines: {log_lines[-20:]}") + + # Disarm + client.alarm_control_panel_command( + alarm_info.key, + AlarmControlPanelCommand.DISARM, + code="1234", + ) + await wait_for_state(AlarmControlPanelState.DISARMED) + + # Verify trigger sequence was added + assert AlarmControlPanelState.PENDING in states_received + assert AlarmControlPanelState.TRIGGERED in states_received + + # ===== Test chime (sensor open while disarmed) ===== + # The chime_sensor has chime: true, so opening it while disarmed + # should trigger on_chime callback + + # We're currently DISARMED - open the chime sensor + client.switch_command(chime_switch_info.key, True) + + # Wait for chime callback to be logged + try: + await asyncio.wait_for(chime_future, timeout=2.0) + except TimeoutError: + pytest.fail(f"on_chime callback not fired. Log lines: {log_lines[-20:]}") + + # Close the chime sensor and wait for alarm to become ready again + # We need to wait for this transition before testing door sensor, + # otherwise there's a race where the door sensor state change could + # arrive before the chime sensor state change, leaving the alarm in + # a continuous "not ready" state with no on_ready callback fired. + ready_after_chime_close: asyncio.Future[bool] = loop.create_future() + ready_futures.append(ready_after_chime_close) + + client.switch_command(chime_switch_info.key, False) + + # Wait for alarm to become ready again (chime sensor closed) + try: + await asyncio.wait_for(ready_after_chime_close, timeout=2.0) + except TimeoutError: + pytest.fail( + f"on_ready callback not fired when chime sensor closed. " + f"Log lines: {log_lines[-20:]}" + ) + + # ===== Test ready state changes ===== + # Now the alarm is confirmed ready. Opening/closing door sensor + # should trigger on_ready callbacks. + + # Set up futures for door sensor state changes + ready_future_1: asyncio.Future[bool] = loop.create_future() + ready_future_2: asyncio.Future[bool] = loop.create_future() + ready_futures.extend([ready_future_1, ready_future_2]) + + # Open door sensor (makes alarm not ready) + client.switch_command(door_switch_info.key, True) + + # Wait for first on_ready callback (not ready) + try: + await asyncio.wait_for(ready_future_1, timeout=2.0) + except TimeoutError: + pytest.fail( + f"on_ready callback not fired when sensor opened. " + f"Log lines: {log_lines[-20:]}" + ) + + # Close door sensor (makes alarm ready again) + client.switch_command(door_switch_info.key, False) + + # Wait for second on_ready callback (ready) + try: + await asyncio.wait_for(ready_future_2, timeout=2.0) + except TimeoutError: + pytest.fail( + f"on_ready callback not fired when sensor closed. " + f"Log lines: {log_lines[-20:]}" + ) + + # Final state should still be DISARMED + assert states_received[-1] == AlarmControlPanelState.DISARMED diff --git a/tests/integration/test_api_conditional_memory.py b/tests/integration/test_api_conditional_memory.py index 349b572859..91625770d9 100644 --- a/tests/integration/test_api_conditional_memory.py +++ b/tests/integration/test_api_conditional_memory.py @@ -23,12 +23,14 @@ async def test_api_conditional_memory( # Track log messages connected_future = loop.create_future() disconnected_future = loop.create_future() + no_clients_future = loop.create_future() service_simple_future = loop.create_future() service_args_future = loop.create_future() # Patterns to match in logs connected_pattern = re.compile(r"Client .* connected from") disconnected_pattern = re.compile(r"Client .* disconnected from") + no_clients_pattern = re.compile(r"No clients remaining") service_simple_pattern = re.compile(r"Simple service called") service_args_pattern = re.compile( r"Service called with: test_string, 123, 1, 42\.50" @@ -40,6 +42,8 @@ async def test_api_conditional_memory( connected_future.set_result(True) elif not disconnected_future.done() and disconnected_pattern.search(line): disconnected_future.set_result(True) + elif not no_clients_future.done() and no_clients_pattern.search(line): + no_clients_future.set_result(True) elif not service_simple_future.done() and service_simple_pattern.search(line): service_simple_future.set_result(True) elif not service_args_future.done() and service_args_pattern.search(line): @@ -109,3 +113,7 @@ async def test_api_conditional_memory( # Client disconnected here, wait for disconnect log await asyncio.wait_for(disconnected_future, timeout=5.0) + + # Verify fix for issue #11131: api.connected should be false in trigger + # when the last client disconnects + await asyncio.wait_for(no_clients_future, timeout=5.0) diff --git a/tests/integration/test_api_homeassistant.py b/tests/integration/test_api_homeassistant.py index 1343691f5f..3fe0dfe045 100644 --- a/tests/integration/test_api_homeassistant.py +++ b/tests/integration/test_api_homeassistant.py @@ -179,6 +179,12 @@ async def test_api_homeassistant( client.send_home_assistant_state("binary_sensor.external_motion", "", "ON") client.send_home_assistant_state("weather.home", "condition", "sunny") + # Test edge cases for zero-copy implementation safety + # Empty entity_id should be silently ignored (no crash) + client.send_home_assistant_state("", "", "should_be_ignored") + # Empty state with valid entity should work (use different entity to not interfere with test) + client.send_home_assistant_state("sensor.edge_case_empty_state", "", "") + # List entities and services _, services = await client.list_entities_services() diff --git a/tests/integration/test_build_info.py b/tests/integration/test_build_info.py new file mode 100644 index 0000000000..7079594471 --- /dev/null +++ b/tests/integration/test_build_info.py @@ -0,0 +1,117 @@ +"""Integration test for build_info values.""" + +from __future__ import annotations + +import asyncio +from datetime import datetime +import re +import time + +from aioesphomeapi import EntityState, TextSensorState +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_build_info( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that build_info values are sane.""" + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "build-info-test" + + # Verify compilation_time from device_info is present and parseable + # The format is ISO 8601 with timezone: "YYYY-MM-DD HH:MM:SS +ZZZZ" + compilation_time = device_info.compilation_time + assert compilation_time is not None + + # Validate the ISO format: "YYYY-MM-DD HH:MM:SS +ZZZZ" + parsed = datetime.strptime(compilation_time, "%Y-%m-%d %H:%M:%S %z") + assert parsed.year >= time.localtime().tm_year + + # Get entities + entities, _ = await client.list_entities_services() + + # Find our text sensors by object_id + config_hash_entity = next( + (e for e in entities if e.object_id == "config_hash"), None + ) + build_time_entity = next( + (e for e in entities if e.object_id == "build_time"), None + ) + build_time_str_entity = next( + (e for e in entities if e.object_id == "build_time_string"), None + ) + + assert config_hash_entity is not None, "Config Hash sensor not found" + assert build_time_entity is not None, "Build Time sensor not found" + assert build_time_str_entity is not None, "Build Time String sensor not found" + + # Wait for all three text sensors to have valid states + loop = asyncio.get_running_loop() + states: dict[int, TextSensorState] = {} + all_received = loop.create_future() + expected_keys = { + config_hash_entity.key, + build_time_entity.key, + build_time_str_entity.key, + } + + def on_state(state: EntityState) -> None: + if isinstance(state, TextSensorState) and not state.missing_state: + states[state.key] = state + if expected_keys <= states.keys() and not all_received.done(): + all_received.set_result(True) + + client.subscribe_states(on_state) + + try: + await asyncio.wait_for(all_received, timeout=5.0) + except TimeoutError: + pytest.fail( + f"Timeout waiting for text sensor states. Got: {list(states.keys())}" + ) + + config_hash_state = states[config_hash_entity.key] + build_time_state = states[build_time_entity.key] + build_time_str_state = states[build_time_str_entity.key] + + # Validate config_hash format (0x followed by 8 hex digits) + config_hash = config_hash_state.state + assert re.match(r"^0x[0-9a-f]{8}$", config_hash), ( + f"config_hash should be 0x followed by 8 hex digits, got: {config_hash}" + ) + + # Validate build_time is a reasonable Unix timestamp + build_time = int(build_time_state.state) + current_time = int(time.time()) + # Build time should be within last hour and not in the future + assert build_time <= current_time, ( + f"build_time {build_time} should not be in the future (current: {current_time})" + ) + assert build_time > current_time - 3600, ( + f"build_time {build_time} should be within the last hour" + ) + + # Validate build_time_str matches the new ISO format + build_time_str = build_time_str_state.state + # Format: "YYYY-MM-DD HH:MM:SS +ZZZZ" + parsed_build_time = datetime.strptime(build_time_str, "%Y-%m-%d %H:%M:%S %z") + assert parsed_build_time.year >= time.localtime().tm_year + + # Verify build_time_str matches what we get from build_time timestamp + expected_str = time.strftime("%Y-%m-%d %H:%M:%S %z", time.localtime(build_time)) + assert build_time_str == expected_str, ( + f"build_time_str '{build_time_str}' should match timestamp '{expected_str}'" + ) + + # Verify compilation_time matches build_time_str (they should be the same) + assert compilation_time == build_time_str, ( + f"compilation_time '{compilation_time}' should match " + f"build_time_str '{build_time_str}'" + ) diff --git a/tests/integration/test_fnv1_hash_object_id.py b/tests/integration/test_fnv1_hash_object_id.py new file mode 100644 index 0000000000..23e8ca04c2 --- /dev/null +++ b/tests/integration/test_fnv1_hash_object_id.py @@ -0,0 +1,75 @@ +"""Integration test for fnv1_hash_object_id function. + +This test verifies that the C++ fnv1_hash_object_id() function in +esphome/core/helpers.h produces the same hash values as the Python +fnv1_hash_object_id() function in esphome/helpers.py. + +If this test fails, one of the implementations has diverged and needs +to be updated to match the other. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_fnv1_hash_object_id( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that C++ fnv1_hash_object_id matches Python implementation.""" + + test_results: dict[str, str] = {} + all_tests_complete = asyncio.Event() + expected_tests = { + "foo", + "upper", + "space", + "underscore", + "hyphen", + "special", + "complex", + "empty", + } + + def on_log_line(line: str) -> None: + """Capture log lines with test results.""" + # Strip ANSI escape codes + clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) + # Look for our test result messages + # Format: "[timestamp][level][FNV1_OID:line]: test_name PASSED" + match = re.search(r"\[FNV1_OID:\d+\]:\s+(\w+)\s+(PASSED|FAILED)", clean_line) + if match: + test_name = match.group(1) + result = match.group(2) + test_results[test_name] = result + if set(test_results.keys()) >= expected_tests: + all_tests_complete.set() + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "fnv1-hash-object-id-test" + + # Wait for all tests to complete or timeout + try: + await asyncio.wait_for(all_tests_complete.wait(), timeout=2.0) + except TimeoutError: + pytest.fail(f"Tests timed out. Got results for: {set(test_results.keys())}") + + # Verify all tests passed + for test_name in expected_tests: + assert test_name in test_results, f"{test_name} test not found" + assert test_results[test_name] == "PASSED", ( + f"{test_name} test failed - C++ and Python hash mismatch" + ) diff --git a/tests/integration/test_fnv1a_hash.py b/tests/integration/test_fnv1a_hash.py new file mode 100644 index 0000000000..366ea42cda --- /dev/null +++ b/tests/integration/test_fnv1a_hash.py @@ -0,0 +1,69 @@ +"""Integration test for FNV-1a hash functions.""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_fnv1a_hash( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that FNV-1a hash functions work correctly.""" + + test_results = {} + all_tests_complete = asyncio.Event() + expected_tests = {"empty", "known_hello", "known_helloworld", "extend", "string"} + + def on_log_line(line: str) -> None: + """Capture log lines with test results.""" + # Strip ANSI escape codes + clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) + # Look for our test result messages + # Format: "[timestamp][level][FNV1A:line]: test_name PASSED" + match = re.search(r"\[FNV1A:\d+\]:\s+(\w+)\s+(PASSED|FAILED)", clean_line) + if match: + test_name = match.group(1) + result = match.group(2) + test_results[test_name] = result + if set(test_results.keys()) >= expected_tests: + all_tests_complete.set() + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "fnv1a-hash-test" + + # Wait for all tests to complete or timeout + try: + await asyncio.wait_for(all_tests_complete.wait(), timeout=2.0) + except TimeoutError: + pytest.fail(f"Tests timed out. Got results for: {set(test_results.keys())}") + + # Verify all tests passed + assert "empty" in test_results, "empty string test not found" + assert test_results["empty"] == "PASSED", "empty string test failed" + + assert "known_hello" in test_results, "known_hello test not found" + assert test_results["known_hello"] == "PASSED", "known_hello test failed" + + assert "known_helloworld" in test_results, "known_helloworld test not found" + assert test_results["known_helloworld"] == "PASSED", ( + "known_helloworld test failed" + ) + + assert "extend" in test_results, "fnv1a_hash_extend test not found" + assert test_results["extend"] == "PASSED", "fnv1a_hash_extend test failed" + + assert "string" in test_results, "std::string test not found" + assert test_results["string"] == "PASSED", "std::string test failed" diff --git a/tests/integration/test_host_logger_thread_safety.py b/tests/integration/test_host_logger_thread_safety.py new file mode 100644 index 0000000000..922ce00155 --- /dev/null +++ b/tests/integration/test_host_logger_thread_safety.py @@ -0,0 +1,182 @@ +"""Integration test for host logger thread safety. + +This test verifies that the logger's MPSC ring buffer correctly handles +multiple threads racing to log messages without corruption or data loss. +""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + +# Expected pattern for log messages from threads +# Format: THREADn_MSGnnn_LEVEL_MESSAGE_WITH_DATA_xxxxxxxx +THREAD_MSG_PATTERN = re.compile( + r"THREAD(\d+)_MSG(\d{3})_(INFO|DEBUG|WARN|ERROR)_MESSAGE_WITH_DATA_([0-9A-F]{8})" +) + +# Pattern for test start/complete markers +TEST_START_PATTERN = re.compile(r"RACE_TEST_START.*Starting (\d+) threads") +TEST_COMPLETE_PATTERN = re.compile(r"RACE_TEST_COMPLETE.*total messages: (\d+)") + +# Expected values +NUM_THREADS = 3 +MESSAGES_PER_THREAD = 100 +EXPECTED_TOTAL_MESSAGES = NUM_THREADS * MESSAGES_PER_THREAD + + +@pytest.mark.asyncio +async def test_host_logger_thread_safety( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that multiple threads can log concurrently without corruption. + + This test: + 1. Spawns 3 threads that each log 100 messages + 2. Collects all log output + 3. Verifies no lines are corrupted (partially written or interleaved) + 4. Verifies all expected messages were received + """ + collected_lines: list[str] = [] + test_complete_event = asyncio.Event() + + def line_callback(line: str) -> None: + """Collect log lines and detect test completion.""" + collected_lines.append(line) + if "RACE_TEST_COMPLETE" in line: + test_complete_event.set() + + # Run the test binary and collect output + async with ( + run_compiled(yaml_config, line_callback=line_callback), + api_client_connected() as client, + ): + # Verify connection works + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "host-logger-thread-test" + + # Get the button entity - find by name + entities, _ = await client.list_entities_services() + button_entities = [e for e in entities if e.name == "Start Thread Race Test"] + assert button_entities, "Could not find Start Thread Race Test button" + button_key = button_entities[0].key + + # Press the button to start the thread race test + client.button_command(button_key) + + # Wait for test to complete (with timeout) + try: + await asyncio.wait_for(test_complete_event.wait(), timeout=30.0) + except TimeoutError: + pytest.fail( + "Test did not complete within timeout. " + f"Collected {len(collected_lines)} lines." + ) + + # Give a bit more time for any remaining buffered messages + await asyncio.sleep(0.5) + + # Analyze collected log lines + thread_messages: dict[int, set[int]] = {i: set() for i in range(NUM_THREADS)} + corrupted_lines: list[str] = [] + test_started = False + test_completed = False + reported_total = 0 + + for line in collected_lines: + # Check for test start + start_match = TEST_START_PATTERN.search(line) + if start_match: + test_started = True + assert int(start_match.group(1)) == NUM_THREADS, ( + f"Unexpected thread count: {start_match.group(1)}" + ) + continue + + # Check for test completion + complete_match = TEST_COMPLETE_PATTERN.search(line) + if complete_match: + test_completed = True + reported_total = int(complete_match.group(1)) + continue + + # Check for thread messages + msg_match = THREAD_MSG_PATTERN.search(line) + if msg_match: + thread_id = int(msg_match.group(1)) + msg_num = int(msg_match.group(2)) + # level = msg_match.group(3) # INFO, DEBUG, WARN, ERROR + data_hex = msg_match.group(4) + + # Verify data value matches expected calculation + expected_data = f"{msg_num * 12345:08X}" + if data_hex != expected_data: + corrupted_lines.append( + f"Data mismatch in line: {line} " + f"(expected {expected_data}, got {data_hex})" + ) + continue + + # Track which messages we received from each thread + if 0 <= thread_id < NUM_THREADS: + thread_messages[thread_id].add(msg_num) + else: + corrupted_lines.append(f"Invalid thread ID in line: {line}") + continue + + # Check for partial/corrupted thread messages + # If a line contains part of a thread message pattern but doesn't match fully + # This could indicate line corruption from interleaving + if ( + "THREAD" in line + and "MSG" in line + and not msg_match + and "_MESSAGE_WITH_DATA_" in line + ): + corrupted_lines.append(f"Possibly corrupted line: {line}") + + # Assertions + assert test_started, "Test start marker not found in output" + assert test_completed, "Test completion marker not found in output" + assert reported_total == EXPECTED_TOTAL_MESSAGES, ( + f"Reported total {reported_total} != expected {EXPECTED_TOTAL_MESSAGES}" + ) + + # Check for corrupted lines + assert not corrupted_lines, ( + f"Found {len(corrupted_lines)} corrupted lines:\n" + + "\n".join(corrupted_lines[:10]) # Show first 10 + ) + + # Count total messages received + total_received = sum(len(msgs) for msgs in thread_messages.values()) + + # We may not receive all messages due to ring buffer overflow when buffer is full + # The test primarily verifies no corruption, not that we receive every message + # However, we should receive a reasonable number of messages + min_expected = EXPECTED_TOTAL_MESSAGES // 2 # At least 50% + assert total_received >= min_expected, ( + f"Received only {total_received} messages, expected at least {min_expected}. " + f"Per-thread breakdown: " + + ", ".join(f"Thread{i}: {len(msgs)}" for i, msgs in thread_messages.items()) + ) + + # Verify we got messages from all threads (proves concurrent logging worked) + for thread_id in range(NUM_THREADS): + assert thread_messages[thread_id], ( + f"No messages received from thread {thread_id}" + ) + + # Log summary for debugging + print("\nThread safety test summary:") + print(f" Total messages received: {total_received}/{EXPECTED_TOTAL_MESSAGES}") + for thread_id in range(NUM_THREADS): + received = len(thread_messages[thread_id]) + print(f" Thread {thread_id}: {received}/{MESSAGES_PER_THREAD} messages") diff --git a/tests/integration/test_host_mode_api_password.py b/tests/integration/test_host_mode_api_password.py deleted file mode 100644 index 5c5e689e45..0000000000 --- a/tests/integration/test_host_mode_api_password.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Integration test for API password authentication.""" - -from __future__ import annotations - -import asyncio - -from aioesphomeapi import APIConnectionError, InvalidAuthAPIError -import pytest - -from .types import APIClientConnectedFactory, RunCompiledFunction - - -@pytest.mark.asyncio -async def test_host_mode_api_password( - yaml_config: str, - run_compiled: RunCompiledFunction, - api_client_connected: APIClientConnectedFactory, -) -> None: - """Test API authentication with password.""" - async with run_compiled(yaml_config): - # Connect with correct password - async with api_client_connected(password="test_password_123") as client: - # Verify we can get device info - device_info = await client.device_info() - assert device_info is not None - assert device_info.uses_password is True - assert device_info.name == "host-mode-api-password" - - # Subscribe to states to ensure authenticated connection works - loop = asyncio.get_running_loop() - state_future: asyncio.Future[bool] = loop.create_future() - states = {} - - def on_state(state): - states[state.key] = state - if not state_future.done(): - state_future.set_result(True) - - client.subscribe_states(on_state) - - # Wait for at least one state with timeout - try: - await asyncio.wait_for(state_future, timeout=5.0) - except TimeoutError: - pytest.fail("No states received within timeout") - - # Should have received at least one state (the test sensor) - assert len(states) > 0 - - # Test with wrong password - should fail - # Try connecting with wrong password - try: - async with api_client_connected( - password="wrong_password", timeout=5 - ) as client: - # If we get here without exception, try to use the connection - # which should fail if auth failed - await client.device_info_and_list_entities() - # If we successfully got device info and entities, auth didn't fail properly - pytest.fail("Connection succeeded with wrong password") - except (InvalidAuthAPIError, APIConnectionError) as e: - # Expected - auth should fail - # Accept either InvalidAuthAPIError or generic APIConnectionError - # since the client might not always distinguish - assert ( - "password" in str(e).lower() - or "auth" in str(e).lower() - or "invalid" in str(e).lower() - ) diff --git a/tests/integration/test_object_id_api_verification.py b/tests/integration/test_object_id_api_verification.py new file mode 100644 index 0000000000..c8603e0682 --- /dev/null +++ b/tests/integration/test_object_id_api_verification.py @@ -0,0 +1,176 @@ +"""Integration test to verify object_id from API matches Python computation. + +This test verifies a three-way match between: +1. C++ object_id generation (get_object_id_to using to_sanitized_char/to_snake_case_char) +2. C++ hash generation (fnv1_hash_object_id in helpers.h) +3. Python computation (sanitize/snake_case in helpers.py, fnv1_hash_object_id) + +The API response contains C++ computed values, so verifying API == Python +implicitly verifies C++ == Python == API for both object_id and hash. + +This is important for the planned migration to remove object_id from the API +protocol and have clients (like aioesphomeapi) compute it from the name. +See: https://github.com/esphome/backlog/issues/76 + +Test cases covered: +- Named entities with various characters (uppercase, special chars, hyphens, etc.) +- Empty-name entities on main device (uses device's friendly_name with MAC suffix) +- Empty-name entities on sub-devices (uses sub-device's name) +- Named entities on sub-devices (uses entity name, not device name) +- MAC suffix handling (name_add_mac_suffix modifies friendly_name at runtime) +- Both object_id string and hash (key) verification +""" + +from __future__ import annotations + +import pytest + +from esphome.helpers import fnv1_hash_object_id + +from .entity_utils import compute_object_id, verify_all_entities +from .types import APIClientConnectedFactory, RunCompiledFunction + +# Host platform default MAC: 98:35:69:ab:f6:79 -> suffix "abf679" +MAC_SUFFIX = "abf679" + + +# Expected entities with their own names and expected object_ids +# Format: (entity_name, expected_object_id) +NAMED_ENTITIES = [ + # sensor platform + ("Temperature Sensor", "temperature_sensor"), + ("UPPERCASE NAME", "uppercase_name"), + ("Special!@Chars#", "special__chars_"), + ("Temp-Sensor", "temp-sensor"), + ("Temp_Sensor", "temp_sensor"), + ("Living Room Temperature", "living_room_temperature"), + # binary_sensor platform + ("Door Open", "door_open"), + ("Sensor 123", "sensor_123"), + # switch platform + ("My Very Long Switch Name Here", "my_very_long_switch_name_here"), + # text_sensor platform + ("123 Start", "123_start"), + # button platform - named entity on sub-device (uses entity name, not device name) + ("Device Button", "device_button"), +] + +# Sub-device names and their expected object_ids for empty-name entities +# Format: (device_name, expected_object_id) +SUB_DEVICE_EMPTY_NAME_ENTITIES = [ + ("Sub Device One", "sub_device_one"), + ("Sub Device Two", "sub_device_two"), +] + + +@pytest.mark.asyncio +async def test_object_id_api_verification( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that object_id from API matches Python computation. + + Tests: + 1. Named entities - object_id computed from entity name + 2. Empty-name entities - object_id computed from friendly_name (with MAC suffix) + 3. Hash verification - key can be computed from name + 4. Generic verification - all entities can have object_id computed from API data + """ + async with run_compiled(yaml_config), api_client_connected() as client: + # Get device info + device_info = await client.device_info() + assert device_info is not None + + # Device name should include MAC suffix (hyphen separator) + assert device_info.name == f"object-id-test-{MAC_SUFFIX}", ( + f"Device name mismatch: got '{device_info.name}'" + ) + # Friendly name should include MAC suffix (space separator) + expected_friendly_name = f"Test Device {MAC_SUFFIX}" + assert device_info.friendly_name == expected_friendly_name, ( + f"Friendly name mismatch: got '{device_info.friendly_name}'" + ) + + # Get all entities + entities, _ = await client.list_entities_services() + + # Create a map of entity names to entity info + entity_map = {} + for entity in entities: + entity_map[entity.name] = entity + + # === Test 1: Verify each named entity === + for entity_name, expected_object_id in NAMED_ENTITIES: + assert entity_name in entity_map, ( + f"Entity '{entity_name}' not found in API response. " + f"Available: {list(entity_map.keys())}" + ) + + entity = entity_map[entity_name] + + # Verify object_id matches expected + assert entity.object_id == expected_object_id, ( + f"Entity '{entity_name}': object_id mismatch. " + f"API returned '{entity.object_id}', expected '{expected_object_id}'" + ) + + # Verify Python computation matches + computed = compute_object_id(entity_name) + assert computed == expected_object_id, ( + f"Entity '{entity_name}': Python computation mismatch. " + f"Computed '{computed}', expected '{expected_object_id}'" + ) + + # Verify hash can be computed from the name + hash_from_name = fnv1_hash_object_id(entity_name) + assert hash_from_name == entity.key, ( + f"Entity '{entity_name}': hash mismatch. " + f"Python hash {hash_from_name:#x}, API key {entity.key:#x}" + ) + + # === Test 2: Verify empty-name entities === + # Empty-name entities have name="" in API, object_id comes from: + # - Main device: friendly_name (with MAC suffix) + # - Sub-device: device name + + # Get all empty-name entities + empty_name_entities = [e for e in entities if e.name == ""] + # We expect 3: 1 on main device, 2 on sub-devices + assert len(empty_name_entities) == 3, ( + f"Expected 3 empty-name entities, got {len(empty_name_entities)}" + ) + + # Build device_id -> device_name map from device_info + device_id_to_name = {d.device_id: d.name for d in device_info.devices} + + # Verify each empty-name entity + for entity in empty_name_entities: + if entity.device_id == 0: + # Main device - uses friendly_name with MAC suffix + expected_name = expected_friendly_name + else: + # Sub-device - uses device name + assert entity.device_id in device_id_to_name, ( + f"Entity device_id {entity.device_id} not found in devices" + ) + expected_name = device_id_to_name[entity.device_id] + + expected_object_id = compute_object_id(expected_name) + assert entity.object_id == expected_object_id, ( + f"Empty-name entity (device_id={entity.device_id}): object_id mismatch. " + f"API: '{entity.object_id}', expected: '{expected_object_id}' " + f"(from name '{expected_name}')" + ) + + # Verify hash matches + expected_hash = fnv1_hash_object_id(expected_name) + assert entity.key == expected_hash, ( + f"Empty-name entity (device_id={entity.device_id}): hash mismatch. " + f"API key: {entity.key:#x}, expected: {expected_hash:#x}" + ) + + # === Test 3: Verify ALL entities using the algorithm from entity_utils === + # This uses the algorithm that aioesphomeapi will use to compute object_id + # client-side from API data. + verify_all_entities(entities, device_info) diff --git a/tests/integration/test_object_id_friendly_name_no_mac_suffix.py b/tests/integration/test_object_id_friendly_name_no_mac_suffix.py new file mode 100644 index 0000000000..7199a2b371 --- /dev/null +++ b/tests/integration/test_object_id_friendly_name_no_mac_suffix.py @@ -0,0 +1,81 @@ +"""Integration test for object_id with friendly_name but no MAC suffix. + +This test covers Branch 4 of the algorithm: +- Empty name on main device +- NO MAC suffix enabled +- friendly_name IS set +- Result: use friendly_name for object_id +""" + +from __future__ import annotations + +import pytest + +from esphome.helpers import fnv1_hash_object_id + +from .entity_utils import ( + compute_object_id, + infer_name_add_mac_suffix, + verify_all_entities, +) +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_object_id_friendly_name_no_mac_suffix( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test object_id when friendly_name is set but no MAC suffix. + + This covers Branch 4 of the algorithm: + - Empty name entity + - name_add_mac_suffix = false (or not set) + - friendly_name = "My Friendly Device" + - Expected: object_id = "my_friendly_device" + """ + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + + # Device name should NOT include MAC suffix + assert device_info.name == "test-device" + + # Friendly name should be set + assert device_info.friendly_name == "My Friendly Device" + + entities, _ = await client.list_entities_services() + + # Find the empty-name entity + empty_name_entities = [e for e in entities if e.name == ""] + assert len(empty_name_entities) == 1 + + entity = empty_name_entities[0] + + # Should use friendly_name for object_id (Branch 4) + expected_object_id = compute_object_id("My Friendly Device") + assert expected_object_id == "my_friendly_device" # Verify our expectation + assert entity.object_id == expected_object_id, ( + f"Expected object_id '{expected_object_id}' from friendly_name, " + f"got '{entity.object_id}'" + ) + + # Hash should match friendly_name + expected_hash = fnv1_hash_object_id("My Friendly Device") + assert entity.key == expected_hash, ( + f"Expected hash {expected_hash:#x}, got {entity.key:#x}" + ) + + # Named entity should work normally + named_entities = [e for e in entities if e.name == "Temperature"] + assert len(named_entities) == 1 + assert named_entities[0].object_id == "temperature" + + # Verify our inference: no MAC suffix in this test + assert not infer_name_add_mac_suffix(device_info), ( + "Device name should NOT have MAC suffix" + ) + + # Verify the full algorithm from entity_utils works for ALL entities + verify_all_entities(entities, device_info) diff --git a/tests/integration/test_object_id_no_friendly_name.py b/tests/integration/test_object_id_no_friendly_name.py new file mode 100644 index 0000000000..b548f02fde --- /dev/null +++ b/tests/integration/test_object_id_no_friendly_name.py @@ -0,0 +1,140 @@ +"""Integration tests for object_id when friendly_name is not set. + +These tests verify bug-for-bug compatibility with the old behavior: + +1. With MAC suffix enabled + no friendly_name: + - OLD: is_object_id_dynamic_() was true, used App.get_friendly_name() directly + - OLD: object_id = "" (empty) because friendly_name was empty + - NEW: Must maintain same behavior for compatibility + +2. Without MAC suffix + no friendly_name: + - OLD: is_object_id_dynamic_() was false, used pre-computed object_id_c_str_ + - OLD: Python computed object_id with fallback to device name + - NEW: Must maintain same behavior (object_id = device name) +""" + +from __future__ import annotations + +import pytest + +from esphome.helpers import fnv1_hash_object_id + +from .entity_utils import compute_object_id, verify_all_entities +from .types import APIClientConnectedFactory, RunCompiledFunction + +# Host platform default MAC: 98:35:69:ab:f6:79 -> suffix "abf679" +MAC_SUFFIX = "abf679" + +# FNV1 offset basis - hash of empty string +FNV1_OFFSET_BASIS = 2166136261 + + +@pytest.mark.asyncio +async def test_object_id_no_friendly_name_with_mac_suffix( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test object_id when friendly_name not set but MAC suffix enabled. + + OLD behavior (bug-for-bug compatibility): + - is_object_id_dynamic_() returned true (no own name AND mac suffix enabled) + - format_dynamic_object_id() used App.get_friendly_name() directly + - Since friendly_name was empty, object_id was empty + + This was arguably a bug, but we maintain it for compatibility. + """ + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + + # Device name should include MAC suffix + expected_device_name = f"test-device-{MAC_SUFFIX}" + assert device_info.name == expected_device_name + + # Friendly name should be empty (not set in config) + assert device_info.friendly_name == "" + + entities, _ = await client.list_entities_services() + + # Find the empty-name entity + empty_name_entities = [e for e in entities if e.name == ""] + assert len(empty_name_entities) == 1 + + entity = empty_name_entities[0] + + # OLD behavior: object_id was empty because App.get_friendly_name() was empty + # This is bug-for-bug compatibility + assert entity.object_id == "", ( + f"Expected empty object_id for bug-for-bug compatibility, " + f"got '{entity.object_id}'" + ) + + # Hash should be FNV1_OFFSET_BASIS (hash of empty string) + assert entity.key == FNV1_OFFSET_BASIS, ( + f"Expected hash of empty string ({FNV1_OFFSET_BASIS:#x}), " + f"got {entity.key:#x}" + ) + + # Named entity should work normally + named_entities = [e for e in entities if e.name == "Temperature"] + assert len(named_entities) == 1 + assert named_entities[0].object_id == "temperature" + + # Verify the full algorithm from entity_utils works for ALL entities + verify_all_entities(entities, device_info) + + +@pytest.mark.asyncio +async def test_object_id_no_friendly_name_no_mac_suffix( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test object_id when friendly_name not set and no MAC suffix. + + OLD behavior: + - is_object_id_dynamic_() returned false (mac suffix not enabled) + - Used object_id_c_str_ which was pre-computed in Python + - Python used get_base_entity_object_id() with fallback to CORE.name + + Result: object_id = sanitize(snake_case(device_name)) + """ + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + + # Device name should NOT include MAC suffix + assert device_info.name == "test-device" + + # Friendly name should be empty (not set in config) + assert device_info.friendly_name == "" + + entities, _ = await client.list_entities_services() + + # Find the empty-name entity + empty_name_entities = [e for e in entities if e.name == ""] + assert len(empty_name_entities) == 1 + + entity = empty_name_entities[0] + + # OLD behavior: object_id was computed from device name + expected_object_id = compute_object_id("test-device") + assert entity.object_id == expected_object_id, ( + f"Expected object_id '{expected_object_id}' from device name, " + f"got '{entity.object_id}'" + ) + + # Hash should match device name + expected_hash = fnv1_hash_object_id("test-device") + assert entity.key == expected_hash, ( + f"Expected hash {expected_hash:#x}, got {entity.key:#x}" + ) + + # Named entity should work normally + named_entities = [e for e in entities if e.name == "Temperature"] + assert len(named_entities) == 1 + assert named_entities[0].object_id == "temperature" + + # Verify the full algorithm from entity_utils works for ALL entities + verify_all_entities(entities, device_info) diff --git a/tests/integration/test_scheduler_numeric_id_test.py b/tests/integration/test_scheduler_numeric_id_test.py new file mode 100644 index 0000000000..510256b9a4 --- /dev/null +++ b/tests/integration/test_scheduler_numeric_id_test.py @@ -0,0 +1,217 @@ +"""Test scheduler numeric ID (uint32_t) overloads.""" + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_numeric_id_test( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that scheduler handles numeric IDs (uint32_t) correctly.""" + # Track counts + timeout_count = 0 + interval_count = 0 + retry_count = 0 + + # Events for each test completion + numeric_timeout_1001_fired = asyncio.Event() + numeric_timeout_1002_fired = asyncio.Event() + numeric_interval_2001_fired = asyncio.Event() + numeric_interval_cancelled = asyncio.Event() + numeric_timeout_cancelled = asyncio.Event() + duplicate_timeout_fired = asyncio.Event() + component_timeout_fired = asyncio.Event() + component_interval_fired = asyncio.Event() + zero_id_timeout_fired = asyncio.Event() + max_id_timeout_fired = asyncio.Event() + numeric_retry_done = asyncio.Event() + numeric_retry_cancelled = asyncio.Event() + final_results_logged = asyncio.Event() + + # Track interval counts + numeric_interval_count = 0 + numeric_retry_count = 0 + + def on_log_line(line: str) -> None: + nonlocal timeout_count, interval_count, retry_count + nonlocal numeric_interval_count, numeric_retry_count + + # Strip ANSI color codes + clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) + + # Check for numeric timeout completions + if "Numeric timeout 1001 fired" in clean_line: + numeric_timeout_1001_fired.set() + timeout_count += 1 + + elif "Numeric timeout 1002 fired" in clean_line: + numeric_timeout_1002_fired.set() + timeout_count += 1 + + # Check for numeric interval + elif "Numeric interval 2001 fired" in clean_line: + match = re.search(r"count: (\d+)", clean_line) + if match: + numeric_interval_count = int(match.group(1)) + numeric_interval_2001_fired.set() + + elif "Cancelled numeric interval 2001" in clean_line: + numeric_interval_cancelled.set() + + elif "Cancelled numeric timeout 3001" in clean_line: + numeric_timeout_cancelled.set() + + # Check for duplicate timeout (only last should fire) + elif "Duplicate numeric timeout" in clean_line: + match = re.search(r"timeout (\d+) fired", clean_line) + if match and match.group(1) == "4": + duplicate_timeout_fired.set() + timeout_count += 1 + + # Check for component method tests + elif "Component numeric timeout 5001 fired" in clean_line: + component_timeout_fired.set() + timeout_count += 1 + + elif "Component numeric interval 5002 fired" in clean_line: + component_interval_fired.set() + interval_count += 1 + + # Check for edge case tests + elif "Numeric timeout with ID 0 fired" in clean_line: + zero_id_timeout_fired.set() + timeout_count += 1 + + elif "Numeric timeout with max ID fired" in clean_line: + max_id_timeout_fired.set() + timeout_count += 1 + + # Check for numeric retry tests + elif "Numeric retry 6001 attempt" in clean_line: + match = re.search(r"attempt (\d+)", clean_line) + if match: + numeric_retry_count = int(match.group(1)) + + elif "Numeric retry 6001 done" in clean_line: + numeric_retry_done.set() + + elif "Cancelled numeric retry 6002" in clean_line: + numeric_retry_cancelled.set() + + # Check for final results + elif "Final results" in clean_line: + match = re.search( + r"Timeouts: (\d+), Intervals: (\d+), Retries: (\d+)", clean_line + ) + if match: + timeout_count = int(match.group(1)) + interval_count = int(match.group(2)) + retry_count = int(match.group(3)) + final_results_logged.set() + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + # Verify we can connect + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "scheduler-numeric-id-test" + + # Wait for numeric timeout tests + try: + await asyncio.wait_for(numeric_timeout_1001_fired.wait(), timeout=0.5) + except TimeoutError: + pytest.fail("Numeric timeout 1001 did not fire within 0.5 seconds") + + try: + await asyncio.wait_for(numeric_timeout_1002_fired.wait(), timeout=0.5) + except TimeoutError: + pytest.fail("Numeric timeout 1002 did not fire within 0.5 seconds") + + try: + await asyncio.wait_for(numeric_interval_2001_fired.wait(), timeout=1.0) + except TimeoutError: + pytest.fail("Numeric interval 2001 did not fire within 1 second") + + try: + await asyncio.wait_for(numeric_interval_cancelled.wait(), timeout=2.0) + except TimeoutError: + pytest.fail("Numeric interval 2001 was not cancelled within 2 seconds") + + # Verify numeric interval ran at least twice + assert numeric_interval_count >= 2, ( + f"Expected numeric interval to run at least 2 times, got {numeric_interval_count}" + ) + + # Verify numeric timeout was cancelled + assert numeric_timeout_cancelled.is_set(), ( + "Numeric timeout 3001 should have been cancelled" + ) + + # Wait for duplicate timeout (only last one should fire) + try: + await asyncio.wait_for(duplicate_timeout_fired.wait(), timeout=1.0) + except TimeoutError: + pytest.fail("Duplicate numeric timeout did not fire within 1 second") + + # Wait for component method tests + try: + await asyncio.wait_for(component_timeout_fired.wait(), timeout=0.5) + except TimeoutError: + pytest.fail("Component numeric timeout did not fire within 0.5 seconds") + + try: + await asyncio.wait_for(component_interval_fired.wait(), timeout=1.0) + except TimeoutError: + pytest.fail("Component numeric interval did not fire within 1 second") + + # Wait for edge case tests + try: + await asyncio.wait_for(zero_id_timeout_fired.wait(), timeout=0.5) + except TimeoutError: + pytest.fail("Zero ID timeout did not fire within 0.5 seconds") + + try: + await asyncio.wait_for(max_id_timeout_fired.wait(), timeout=0.5) + except TimeoutError: + pytest.fail("Max ID timeout did not fire within 0.5 seconds") + + # Wait for numeric retry tests + try: + await asyncio.wait_for(numeric_retry_done.wait(), timeout=1.0) + except TimeoutError: + pytest.fail( + f"Numeric retry 6001 did not complete. Count: {numeric_retry_count}" + ) + + assert numeric_retry_count >= 2, ( + f"Expected at least 2 numeric retry attempts, got {numeric_retry_count}" + ) + + # Verify numeric retry was cancelled + assert numeric_retry_cancelled.is_set(), ( + "Numeric retry 6002 should have been cancelled" + ) + + # Wait for final results + try: + await asyncio.wait_for(final_results_logged.wait(), timeout=3.0) + except TimeoutError: + pytest.fail("Final results were not logged within 3 seconds") + + # Verify results + assert timeout_count >= 6, f"Expected at least 6 timeouts, got {timeout_count}" + assert interval_count >= 3, ( + f"Expected at least 3 interval fires, got {interval_count}" + ) + assert retry_count >= 2, ( + f"Expected at least 2 retry attempts, got {retry_count}" + ) diff --git a/tests/integration/test_scheduler_retry_test.py b/tests/integration/test_scheduler_retry_test.py index c04b7197c9..910034e5bb 100644 --- a/tests/integration/test_scheduler_retry_test.py +++ b/tests/integration/test_scheduler_retry_test.py @@ -25,7 +25,6 @@ async def test_scheduler_retry_test( multiple_name_done = asyncio.Event() const_char_done = asyncio.Event() static_char_done = asyncio.Event() - mixed_cancel_done = asyncio.Event() test_complete = asyncio.Event() # Track retry counts @@ -42,14 +41,13 @@ async def test_scheduler_retry_test( # Track specific test results cancel_result = None empty_cancel_result = None - mixed_cancel_result = None backoff_intervals = [] def on_log_line(line: str) -> None: nonlocal simple_retry_count, backoff_retry_count, immediate_done_count nonlocal cancel_retry_count, empty_name_retry_count, component_retry_count nonlocal multiple_name_count, const_char_retry_count, static_char_retry_count - nonlocal cancel_result, empty_cancel_result, mixed_cancel_result + nonlocal cancel_result, empty_cancel_result # Strip ANSI color codes clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) @@ -129,11 +127,6 @@ async def test_scheduler_retry_test( # This is part of test 9, but we don't track it separately pass - # Mixed cancel test - elif "Mixed cancel result:" in clean_line: - mixed_cancel_result = "true" in clean_line - mixed_cancel_done.set() - # Test completion elif "All retry tests completed" in clean_line: test_complete.set() @@ -279,16 +272,6 @@ async def test_scheduler_retry_test( f"Expected 1 static char retry call, got {static_char_retry_count}" ) - # Wait for mixed cancel test - try: - await asyncio.wait_for(mixed_cancel_done.wait(), timeout=1.0) - except TimeoutError: - pytest.fail("Mixed cancel test did not complete") - - assert mixed_cancel_result is True, ( - "Mixed string/const char cancel should have succeeded" - ) - # Wait for test completion try: await asyncio.wait_for(test_complete.wait(), timeout=1.0) diff --git a/tests/integration/test_strftime_to.py b/tests/integration/test_strftime_to.py new file mode 100644 index 0000000000..9220da148b --- /dev/null +++ b/tests/integration/test_strftime_to.py @@ -0,0 +1,111 @@ +"""Integration test for ESPTime::strftime_to() method.""" + +from __future__ import annotations + +import asyncio + +from aioesphomeapi import EntityState, TextSensorState +import pytest + +from .state_utils import InitialStateHelper, require_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_strftime_to( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test ESPTime::strftime_to() formats time correctly.""" + async with run_compiled(yaml_config), api_client_connected() as client: + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "strftime-to-test" + + # Get entities + entities, _ = await client.list_entities_services() + + # Find our text sensors + format_test = require_entity( + entities, "time_format_test", description="Time Format Test sensor" + ) + short_format = require_entity( + entities, "time_short_format", description="Time Short Format sensor" + ) + string_format = require_entity( + entities, "time_string_format", description="Time String Format sensor" + ) + error_format = require_entity( + entities, "time_error_format", description="Time Error Format sensor" + ) + + # Set up state tracking with InitialStateHelper + loop = asyncio.get_running_loop() + states: dict[int, TextSensorState] = {} + all_received = loop.create_future() + expected_keys = { + format_test.key, + short_format.key, + string_format.key, + error_format.key, + } + initial_state_helper = InitialStateHelper(entities) + + def on_state(state: EntityState) -> None: + if isinstance(state, TextSensorState) and not state.missing_state: + states[state.key] = state + if expected_keys <= states.keys() and not all_received.done(): + all_received.set_result(True) + + # Subscribe with the wrapper that filters initial states + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for initial states to be broadcast + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Wait for all expected states + try: + await asyncio.wait_for(all_received, timeout=5.0) + except TimeoutError: + pytest.fail( + f"Timeout waiting for text sensor states. Got: {list(states.keys())}" + ) + + # Validate strftime_to with full format + # Note: The exact output depends on timezone, but should contain date components + format_test_state = states[format_test.key].state + assert "2024" in format_test_state or "2023" in format_test_state, ( + f"Expected year in format test output, got: {format_test_state}" + ) + # Should have format like "YYYY-MM-DD HH:MM:SS" + assert len(format_test_state) == 19, ( + f"Expected 19 chars for datetime format, got {len(format_test_state)}: {format_test_state}" + ) + + # Validate short format (HH:MM) + short_format_state = states[short_format.key].state + assert len(short_format_state) == 5, ( + f"Expected 5 chars for HH:MM format, got {len(short_format_state)}: {short_format_state}" + ) + assert ":" in short_format_state, ( + f"Expected colon in HH:MM format, got: {short_format_state}" + ) + + # Validate string format (the std::string returning version) + string_format_state = states[string_format.key].state + assert len(string_format_state) == 10, ( + f"Expected 10 chars for YYYY-MM-DD format, got {len(string_format_state)}: {string_format_state}" + ) + assert string_format_state.count("-") == 2, ( + f"Expected two dashes in YYYY-MM-DD format, got: {string_format_state}" + ) + + # Validate error format returns "ERROR" + error_format_state = states[error_format.key].state + assert error_format_state == "ERROR", ( + f"Expected 'ERROR' for empty format string, got: {error_format_state}" + ) diff --git a/tests/integration/test_syslog.py b/tests/integration/test_syslog.py new file mode 100644 index 0000000000..b31a19392c --- /dev/null +++ b/tests/integration/test_syslog.py @@ -0,0 +1,284 @@ +"""Integration test for syslog component.""" + +from __future__ import annotations + +import asyncio +from collections.abc import AsyncGenerator +import contextlib +from contextlib import asynccontextmanager +from dataclasses import dataclass, field +import re +import socket +from typing import TypedDict + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +class ParsedSyslogMessage(TypedDict): + """Parsed syslog message components.""" + + pri: int + facility: int + severity: int + timestamp: str + hostname: str + tag: str + message: str + + +# RFC 3164 syslog message pattern: +# TIMESTAMP HOSTNAME TAG: MESSAGE +# Example: <134>Dec 20 14:30:45 syslog-test app: [D][app:029]: Running... +SYSLOG_PATTERN = re.compile( + r"<(\d+)>" # PRI (priority = facility * 8 + severity) + r"(\S+ +\d+ \d+:\d+:\d+|-)" # TIMESTAMP (BSD-style "%b %e %H:%M:%S", e.g. "Dec 20 14:30:45", or NILVALUE "-") + r" (\S+)" # HOSTNAME + r" (\S+):" # TAG + r" (.*)" # MESSAGE +) + + +@dataclass +class SyslogReceiver: + """Collects syslog messages received over UDP.""" + + messages: list[str] = field(default_factory=list) + message_received: asyncio.Event = field(default_factory=asyncio.Event) + _waiters: list[tuple[re.Pattern, asyncio.Event]] = field(default_factory=list) + + def on_message(self, msg: str) -> None: + """Called when a message is received.""" + self.messages.append(msg) + self.message_received.set() + # Check pattern waiters + for pattern, event in self._waiters: + if pattern.search(msg): + event.set() + + async def wait_for_messages(self, timeout: float = 10.0) -> None: + """Wait for at least one message to be received.""" + await asyncio.wait_for(self.message_received.wait(), timeout=timeout) + + async def wait_for_pattern(self, pattern: str, timeout: float = 5.0) -> str: + """Wait for a message matching the pattern.""" + compiled = re.compile(pattern) + event = asyncio.Event() + self._waiters.append((compiled, event)) + try: + # Check existing messages first + for msg in self.messages: + if compiled.search(msg): + return msg + # Wait for new message + await asyncio.wait_for(event.wait(), timeout=timeout) + # Find and return the matching message + for msg in reversed(self.messages): + if compiled.search(msg): + return msg + raise RuntimeError("Event set but no matching message found") + finally: + self._waiters.remove((compiled, event)) + + +@asynccontextmanager +async def syslog_udp_listener() -> AsyncGenerator[tuple[int, SyslogReceiver]]: + """Async context manager that listens for syslog UDP messages. + + Yields: + Tuple of (port, SyslogReceiver) where port is the UDP port to send to + and SyslogReceiver contains the received messages. + """ + # Create and bind UDP socket + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(("127.0.0.1", 0)) + sock.setblocking(False) + port = sock.getsockname()[1] + + receiver = SyslogReceiver() + + async def receive_messages() -> None: + """Background task to receive syslog messages.""" + loop = asyncio.get_running_loop() + while True: + try: + data = await loop.sock_recv(sock, 4096) + if data: + msg = data.decode("utf-8", errors="replace") + receiver.on_message(msg) + except BlockingIOError: + await asyncio.sleep(0.01) + except Exception: + break + + task = asyncio.create_task(receive_messages()) + try: + yield port, receiver + finally: + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + sock.close() + + +def parse_syslog_message(msg: str) -> ParsedSyslogMessage | None: + """Parse a syslog message and return its components.""" + match = SYSLOG_PATTERN.match(msg) + if not match: + return None + pri, timestamp, hostname, tag, message = match.groups() + pri_val = int(pri) + # PRI = facility * 8 + severity + facility = pri_val // 8 + severity = pri_val % 8 + return ParsedSyslogMessage( + pri=pri_val, + facility=facility, + severity=severity, + timestamp=timestamp, + hostname=hostname, + tag=tag, + message=message, + ) + + +@pytest.mark.asyncio +async def test_syslog( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test syslog component sends properly formatted messages.""" + async with syslog_udp_listener() as (udp_port, receiver): + # Replace the placeholder port in the config + config = yaml_config.replace("SYSLOG_PORT_PLACEHOLDER", str(udp_port)) + + async with run_compiled(config), api_client_connected() as client: + # Verify device is running + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "syslog-test" + + # Wait for syslog messages (ESPHome logs during startup) + try: + await receiver.wait_for_messages(timeout=10.0) + except TimeoutError: + pytest.fail("No syslog messages received within timeout") + + # Give it a moment to collect more messages + await asyncio.sleep(0.5) + + # Verify we received messages + assert len(receiver.messages) > 0, "No syslog messages received" + + # Parse and validate all messages + parsed_messages: list[ParsedSyslogMessage] = [] + for msg in receiver.messages: + parsed = parse_syslog_message(msg) + if parsed: + parsed_messages.append(parsed) + + assert len(parsed_messages) > 0, ( + f"No valid syslog messages found. Received: {receiver.messages[:5]}" + ) + + # Validate message format for all parsed messages + for parsed in parsed_messages: + # Validate PRI is in valid range (0-191) + assert 0 <= parsed["pri"] <= 191, f"Invalid PRI: {parsed['pri']}" + + # Validate facility matches config (16 = local0) + assert parsed["facility"] == 16, ( + f"Expected facility 16, got {parsed['facility']}" + ) + + # Validate severity is in valid range (0-7) + assert 0 <= parsed["severity"] <= 7, ( + f"Invalid severity: {parsed['severity']}" + ) + + # Validate hostname matches device name + assert parsed["hostname"] == "syslog-test", ( + f"Unexpected hostname: {parsed['hostname']}" + ) + + # Validate timestamp format (BSD or NILVALUE) + if parsed["timestamp"] != "-": + assert re.match( + r"[A-Z][a-z]{2} +\d+ \d{2}:\d{2}:\d{2}", + parsed["timestamp"], + ), f"Invalid timestamp format: {parsed['timestamp']}" + + # Verify we see different severity levels in the logs + severities_seen = {p["severity"] for p in parsed_messages} + # ESPHome startup logs should include at least INFO (5) or DEBUG (7) + assert len(severities_seen) >= 1, "Expected to see at least one severity" + + # Verify messages don't contain ANSI color codes (strip=true) + for parsed in parsed_messages: + assert "\x1b[" not in parsed["message"], ( + f"Color codes not stripped: {parsed['message'][:50]}" + ) + + # Verify message content is not empty for most messages + non_empty_messages = [p for p in parsed_messages if p["message"].strip()] + assert len(non_empty_messages) > 0, "All messages are empty" + + # Verify tag format (should be component name like "app", "wifi", etc.) + for parsed in parsed_messages: + assert len(parsed["tag"]) > 0, "Empty tag" + # Tag should not contain spaces or colons + assert " " not in parsed["tag"], f"Tag contains space: {parsed['tag']}" + + # Test message truncation - call service that logs a very long message + _, services = await client.list_entities_services() + log_service = next( + (s for s in services if s.name == "log_long_message"), None + ) + assert log_service is not None, "log_long_message service not found" + + # Call the service to trigger a long log message + await client.execute_service(log_service, {}) + + # Wait specifically for the truncation test message + try: + trunc_msg = await receiver.wait_for_pattern(r"trunctest.*START\|") + except TimeoutError: + pytest.fail( + f"Truncation test message not received. Got: {receiver.messages}" + ) + + # Verify message is truncated to max 508 bytes + assert len(trunc_msg) <= 508, f"Message exceeds 508 bytes: {len(trunc_msg)}" + + # Verify the message starts correctly but is truncated (no "|END") + assert "START|" in trunc_msg, "Message should contain START marker" + assert "|END" not in trunc_msg, ( + "Message should be truncated before END marker" + ) + + # Test short message - should arrive complete (not truncated) + short_service = next( + (s for s in services if s.name == "log_short_message"), None + ) + assert short_service is not None, "log_short_message service not found" + + await client.execute_service(short_service, {}) + + try: + short_msg = await receiver.wait_for_pattern(r"shorttest.*BEGIN\|") + except TimeoutError: + pytest.fail( + f"Short test message not received. Got: {receiver.messages[-10:]}" + ) + + # Verify short message arrived complete with both markers + assert "BEGIN|" in short_msg, "Short message missing BEGIN marker" + assert "|FINISH" in short_msg, ( + f"Short message truncated unexpectedly: {short_msg}" + ) + assert "SHORT_MESSAGE_CONTENT" in short_msg, ( + f"Short message content missing: {short_msg}" + ) diff --git a/tests/integration/test_text_command.py b/tests/integration/test_text_command.py new file mode 100644 index 0000000000..82fe981578 --- /dev/null +++ b/tests/integration/test_text_command.py @@ -0,0 +1,126 @@ +"""Integration test for text command zero-copy optimization. + +Tests that TextCommandRequest correctly handles the pointer_to_buffer +optimization for the state field, ensuring text values are properly +transmitted via the API. +""" + +from __future__ import annotations + +import asyncio +from typing import Any + +from aioesphomeapi import TextInfo, TextState +import pytest + +from .state_utils import InitialStateHelper, require_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_text_command( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test text command with various string values including edge cases.""" + loop = asyncio.get_running_loop() + async with run_compiled(yaml_config), api_client_connected() as client: + # Verify we can get device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "host-text-command-test" + + # Get list of entities + entities, _ = await client.list_entities_services() + + # Find our text entities using require_entity + test_text = require_entity(entities, "test_text", TextInfo, "Test Text entity") + test_password = require_entity( + entities, "test_password", TextInfo, "Test Password entity" + ) + test_text_long = require_entity( + entities, "test_text_long", TextInfo, "Test Text Long entity" + ) + + # Track state changes + states: dict[int, Any] = {} + state_futures: dict[int, asyncio.Future[Any]] = {} + + def on_state(state: Any) -> None: + states[state.key] = state + if state.key in state_futures and not state_futures[state.key].done(): + state_futures[state.key].set_result(state) + + # Set up InitialStateHelper to swallow initial state broadcasts + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states to be received + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Verify initial states were received + assert test_text.key in initial_state_helper.initial_states + initial_text_state = initial_state_helper.initial_states[test_text.key] + assert isinstance(initial_text_state, TextState) + assert initial_text_state.state == "initial" + + async def wait_for_state_change(key: int, timeout: float = 2.0) -> Any: + """Wait for a state change for the given entity key.""" + state_futures[key] = loop.create_future() + try: + return await asyncio.wait_for(state_futures[key], timeout) + finally: + state_futures.pop(key, None) + + # Test 1: Simple text value + client.text_command(key=test_text.key, state="hello world") + state = await wait_for_state_change(test_text.key) + assert state.state == "hello world" + + # Test 2: Empty string (edge case for zero-copy) + client.text_command(key=test_text.key, state="") + state = await wait_for_state_change(test_text.key) + assert state.state == "" + + # Test 3: Single character + client.text_command(key=test_text.key, state="x") + state = await wait_for_state_change(test_text.key) + assert state.state == "x" + + # Test 4: String with special characters + client.text_command(key=test_text.key, state="hello\tworld\n!") + state = await wait_for_state_change(test_text.key) + assert state.state == "hello\tworld\n!" + + # Test 5: Unicode characters + client.text_command(key=test_text.key, state="hello 世界 🌍") + state = await wait_for_state_change(test_text.key) + assert state.state == "hello 世界 🌍" + + # Test 6: Long string (tests buffer handling) + long_text = "a" * 200 + client.text_command(key=test_text_long.key, state=long_text) + state = await wait_for_state_change(test_text_long.key) + assert state.state == long_text + assert len(state.state) == 200 + + # Test 7: Password field (same mechanism, different mode) + client.text_command(key=test_password.key, state="newpassword123") + state = await wait_for_state_change(test_password.key) + assert state.state == "newpassword123" + + # Test 8: String with null bytes embedded (edge case) + # Note: protobuf strings should handle this but it's good to verify + client.text_command(key=test_text.key, state="before\x00after") + state = await wait_for_state_change(test_text.key) + assert state.state == "before\x00after" + + # Test 9: Rapid successive commands (tests buffer reuse) + for i in range(5): + client.text_command(key=test_text.key, state=f"rapid_{i}") + state = await wait_for_state_change(test_text.key) + assert state.state == f"rapid_{i}" diff --git a/tests/integration/test_water_heater_template.py b/tests/integration/test_water_heater_template.py new file mode 100644 index 0000000000..b5f1fb64c0 --- /dev/null +++ b/tests/integration/test_water_heater_template.py @@ -0,0 +1,109 @@ +"""Integration test for template water heater component.""" + +from __future__ import annotations + +import asyncio + +import aioesphomeapi +from aioesphomeapi import WaterHeaterInfo, WaterHeaterMode, WaterHeaterState +import pytest + +from .state_utils import InitialStateHelper +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_water_heater_template( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test template water heater basic state and mode changes.""" + loop = asyncio.get_running_loop() + async with run_compiled(yaml_config), api_client_connected() as client: + states: dict[int, aioesphomeapi.EntityState] = {} + gas_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future() + eco_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future() + + def on_state(state: aioesphomeapi.EntityState) -> None: + states[state.key] = state + if isinstance(state, WaterHeaterState): + # Wait for GAS mode + if state.mode == WaterHeaterMode.GAS and not gas_mode_future.done(): + gas_mode_future.set_result(state) + # Wait for ECO mode (we start at OFF, so test transitioning to ECO) + elif state.mode == WaterHeaterMode.ECO and not eco_mode_future.done(): + eco_mode_future.set_result(state) + + # Get entities and set up state synchronization + entities, services = await client.list_entities_services() + initial_state_helper = InitialStateHelper(entities) + water_heater_infos = [e for e in entities if isinstance(e, WaterHeaterInfo)] + assert len(water_heater_infos) == 1, ( + f"Expected exactly 1 water heater entity, got {len(water_heater_infos)}. Entity types: {[type(e).__name__ for e in entities]}" + ) + + test_water_heater = water_heater_infos[0] + + # Verify water heater entity info + assert test_water_heater.object_id == "test_boiler" + assert test_water_heater.name == "Test Boiler" + assert test_water_heater.min_temperature == 30.0 + assert test_water_heater.max_temperature == 85.0 + assert test_water_heater.target_temperature_step == 0.5 + + # Verify supported modes + supported_modes = test_water_heater.supported_modes + assert WaterHeaterMode.OFF in supported_modes, "Expected OFF in supported modes" + assert WaterHeaterMode.ECO in supported_modes, "Expected ECO in supported modes" + assert WaterHeaterMode.GAS in supported_modes, "Expected GAS in supported modes" + assert WaterHeaterMode.PERFORMANCE in supported_modes, ( + "Expected PERFORMANCE in supported modes" + ) + assert len(supported_modes) == 4, ( + f"Expected 4 supported modes, got {len(supported_modes)}: {supported_modes}" + ) + + # Subscribe with the wrapper that filters initial states + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + + # Wait for all initial states to be broadcast + try: + await initial_state_helper.wait_for_initial_states() + except TimeoutError: + pytest.fail("Timeout waiting for initial states") + + # Get initial state and verify + initial_state = initial_state_helper.initial_states.get(test_water_heater.key) + assert initial_state is not None, "Water heater initial state not found" + assert isinstance(initial_state, WaterHeaterState) + # Initial mode is OFF (default) since we don't have a mode lambda + # A mode lambda would override optimistic mode changes + assert initial_state.mode == WaterHeaterMode.OFF, ( + f"Expected initial mode OFF, got {initial_state.mode}" + ) + assert initial_state.current_temperature == 45.0, ( + f"Expected current temp 45.0, got {initial_state.current_temperature}" + ) + + # Test changing to GAS mode + client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.GAS) + + try: + gas_state = await asyncio.wait_for(gas_mode_future, timeout=5.0) + except TimeoutError: + pytest.fail("GAS mode change not received within 5 seconds") + + assert isinstance(gas_state, WaterHeaterState) + assert gas_state.mode == WaterHeaterMode.GAS + + # Test changing to ECO mode (from GAS) + client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.ECO) + + try: + eco_state = await asyncio.wait_for(eco_mode_future, timeout=5.0) + except TimeoutError: + pytest.fail("ECO mode change not received within 5 seconds") + + assert isinstance(eco_state, WaterHeaterState) + assert eco_state.mode == WaterHeaterMode.ECO diff --git a/tests/script/test_clang_tidy_hash.py b/tests/script/test_clang_tidy_hash.py index b1690a6a2d..e19e7886a2 100644 --- a/tests/script/test_clang_tidy_hash.py +++ b/tests/script/test_clang_tidy_hash.py @@ -49,7 +49,7 @@ def test_calculate_clang_tidy_hash_with_sdkconfig(tmp_path: Path) -> None: clang_tidy_content = b"Checks: '-*,readability-*'\n" requirements_version = "clang-tidy==18.1.5" platformio_content = b"[env:esp32]\nplatform = espressif32\n" - sdkconfig_content = b"CONFIG_AUTOSTART_ARDUINO=y\n" + sdkconfig_content = b"" requirements_content = "clang-tidy==18.1.5\n" # Create temporary files diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index 291a23967b..bd20cb3e21 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -1421,6 +1421,135 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) -> assert len(result["components"]) == 40 +# Tests for _detect_platform_hint_from_filename function + + +@pytest.mark.parametrize( + ("filename", "expected_platform"), + [ + # ESP-IDF platform detection + ("esphome/components/wifi/wifi_esp_idf.cpp", determine_jobs.Platform.ESP32_IDF), + ( + "esphome/components/wifi/wifi_component_esp_idf.cpp", + determine_jobs.Platform.ESP32_IDF, + ), + ( + "esphome/components/ethernet/ethernet_idf.cpp", + determine_jobs.Platform.ESP32_IDF, + ), + # ESP32 variant detection with IDF suffix + ( + "esphome/components/ble/esp32c3_idf.cpp", + determine_jobs.Platform.ESP32_C3_IDF, + ), + ( + "esphome/components/ble/esp32c6_idf.cpp", + determine_jobs.Platform.ESP32_C6_IDF, + ), + ( + "esphome/components/ble/esp32s2_idf.cpp", + determine_jobs.Platform.ESP32_S2_IDF, + ), + ( + "esphome/components/ble/esp32s3_idf.cpp", + determine_jobs.Platform.ESP32_S3_IDF, + ), + # ESP8266 detection + ( + "esphome/components/wifi/wifi_esp8266.cpp", + determine_jobs.Platform.ESP8266_ARD, + ), + ("esphome/core/helpers_esp8266.h", determine_jobs.Platform.ESP8266_ARD), + # Generic ESP32 detection (without IDF suffix) + ("esphome/components/wifi/wifi_esp32.cpp", determine_jobs.Platform.ESP32_IDF), + ( + "esphome/components/ethernet/ethernet_esp32.cpp", + determine_jobs.Platform.ESP32_IDF, + ), + # LibreTiny / BK72xx detection + ( + "esphome/components/wifi/wifi_libretiny.cpp", + determine_jobs.Platform.BK72XX_ARD, + ), + ("esphome/components/ble/ble_bk72xx.cpp", determine_jobs.Platform.BK72XX_ARD), + # RP2040 / Raspberry Pi Pico detection + ("esphome/components/gpio/gpio_rp2040.cpp", determine_jobs.Platform.RP2040_ARD), + ("esphome/components/wifi/wifi_rp2040.cpp", determine_jobs.Platform.RP2040_ARD), + ("esphome/components/i2c/i2c_pico.cpp", determine_jobs.Platform.RP2040_ARD), + ("esphome/components/spi/spi_pico.cpp", determine_jobs.Platform.RP2040_ARD), + ( + "tests/components/rp2040/test.rp2040-ard.yaml", + determine_jobs.Platform.RP2040_ARD, + ), + # No platform hint (generic files) + ("esphome/components/wifi/wifi.cpp", None), + ("esphome/components/sensor/sensor.h", None), + ("esphome/core/helpers.h", None), + ("README.md", None), + ], + ids=[ + "esp_idf_suffix", + "esp_idf_component_suffix", + "idf_suffix", + "esp32c3_idf", + "esp32c6_idf", + "esp32s2_idf", + "esp32s3_idf", + "esp8266_suffix", + "esp8266_core_header", + "generic_esp32", + "esp32_in_name", + "libretiny", + "bk72xx", + "rp2040_gpio", + "rp2040_wifi", + "pico_i2c", + "pico_spi", + "rp2040_test_yaml", + "generic_wifi_no_hint", + "generic_sensor_no_hint", + "core_helpers_no_hint", + "readme_no_hint", + ], +) +def test_detect_platform_hint_from_filename( + filename: str, expected_platform: determine_jobs.Platform | None +) -> None: + """Test _detect_platform_hint_from_filename correctly detects platform hints.""" + result = determine_jobs._detect_platform_hint_from_filename(filename) + assert result == expected_platform + + +@pytest.mark.parametrize( + ("filename", "expected_platform"), + [ + # RP2040/Pico with different cases + ("file_RP2040.cpp", determine_jobs.Platform.RP2040_ARD), + ("file_Rp2040.cpp", determine_jobs.Platform.RP2040_ARD), + ("file_PICO.cpp", determine_jobs.Platform.RP2040_ARD), + ("file_Pico.cpp", determine_jobs.Platform.RP2040_ARD), + # ESP8266 with different cases + ("file_ESP8266.cpp", determine_jobs.Platform.ESP8266_ARD), + # ESP32 with different cases + ("file_ESP32.cpp", determine_jobs.Platform.ESP32_IDF), + ], + ids=[ + "rp2040_uppercase", + "rp2040_mixedcase", + "pico_uppercase", + "pico_titlecase", + "esp8266_uppercase", + "esp32_uppercase", + ], +) +def test_detect_platform_hint_from_filename_case_insensitive( + filename: str, expected_platform: determine_jobs.Platform +) -> None: + """Test that platform detection is case-insensitive.""" + result = determine_jobs._detect_platform_hint_from_filename(filename) + assert result == expected_platform + + def test_component_batching_beta_branch_40_per_batch( tmp_path: Path, mock_should_run_integration_tests: Mock, diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index fc61841500..1a1bfffd03 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -58,6 +58,7 @@ def mock_write_file_if_changed() -> Generator[Mock, None, None]: def mock_copy_file_if_changed() -> Generator[Mock, None, None]: """Mock copy_file_if_changed for core.config.""" with patch("esphome.core.config.copy_file_if_changed") as mock: + mock.return_value = True yield mock diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 90b2f5edba..ab7bdbb98c 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -892,3 +892,74 @@ async def test_add_includes_overwrites_existing_files( mock_copy_file_if_changed.assert_called_once_with( include_file, CORE.build_path / "src" / "header.h" ) + + +def test_config_hash_returns_int() -> None: + """Test that config_hash returns an integer.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test"}} + assert isinstance(CORE.config_hash, int) + + +def test_config_hash_is_cached() -> None: + """Test that config_hash is computed once and cached.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test"}} + + # First access computes the hash + hash1 = CORE.config_hash + + # Modify config (without resetting cache) + CORE.config = {"esphome": {"name": "different"}} + + # Second access returns cached value + hash2 = CORE.config_hash + + assert hash1 == hash2 + + +def test_config_hash_reset_clears_cache() -> None: + """Test that reset() clears the cached config_hash.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test"}} + hash1 = CORE.config_hash + + # Reset clears the cache + CORE.reset() + CORE.config = {"esphome": {"name": "different"}} + + hash2 = CORE.config_hash + + # After reset, hash should be recomputed + assert hash1 != hash2 + + +def test_config_hash_deterministic_key_order() -> None: + """Test that config_hash is deterministic regardless of key insertion order.""" + CORE.reset() + # Create two configs with same content but different key order + config1 = {"z_key": 1, "a_key": 2, "nested": {"z_nested": "z", "a_nested": "a"}} + config2 = {"a_key": 2, "z_key": 1, "nested": {"a_nested": "a", "z_nested": "z"}} + + CORE.config = config1 + hash1 = CORE.config_hash + + CORE.reset() + CORE.config = config2 + hash2 = CORE.config_hash + + # Hashes should be equal because keys are sorted during serialization + assert hash1 == hash2 + + +def test_config_hash_different_for_different_configs() -> None: + """Test that different configs produce different hashes.""" + CORE.reset() + CORE.config = {"esphome": {"name": "test1"}} + hash1 = CORE.config_hash + + CORE.reset() + CORE.config = {"esphome": {"name": "test2"}} + hash2 = CORE.config_hash + + assert hash1 != hash2 diff --git a/tests/unit_tests/core/test_entity_helpers.py b/tests/unit_tests/core/test_entity_helpers.py index 01de0f27f9..a58d4784ce 100644 --- a/tests/unit_tests/core/test_entity_helpers.py +++ b/tests/unit_tests/core/test_entity_helpers.py @@ -27,13 +27,9 @@ from esphome.helpers import sanitize, snake_case from .common import load_config_from_fixture -# Pre-compiled regex patterns for extracting object IDs from expressions -# Matches both old format: .set_object_id("obj_id") -# and new format: .set_name_and_object_id("name", "obj_id") -OBJECT_ID_PATTERN = re.compile(r'\.set_object_id\(["\'](.*?)["\']\)') -COMBINED_PATTERN = re.compile( - r'\.set_name_and_object_id\(["\'].*?["\']\s*,\s*["\'](.*?)["\']\)' -) +# Pre-compiled regex pattern for extracting names from set_name calls +# Matches: .set_name("name", hash) or .set_name("name") +SET_NAME_PATTERN = re.compile(r'\.set_name\(["\']([^"\']*)["\']') FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers" @@ -276,14 +272,21 @@ def setup_test_environment() -> Generator[list[str], None, None]: def extract_object_id_from_expressions(expressions: list[str]) -> str | None: - """Extract the object ID that was set from the generated expressions.""" + """Extract the object ID that would be computed from set_name calls. + + Since object_id is now computed from the name (via snake_case + sanitize), + we extract the name from set_name() calls and compute the expected object_id. + For empty names, we fall back to CORE.friendly_name or CORE.name. + """ for expr in expressions: - # First try new combined format: .set_name_and_object_id("name", "obj_id") - if match := COMBINED_PATTERN.search(expr): - return match.group(1) - # Fall back to old format: .set_object_id("obj_id") - if match := OBJECT_ID_PATTERN.search(expr): - return match.group(1) + if match := SET_NAME_PATTERN.search(expr): + name = match.group(1) + if name: + return sanitize(snake_case(name)) + # Empty name - fall back to friendly_name or device name + if CORE.friendly_name: + return sanitize(snake_case(CORE.friendly_name)) + return sanitize(snake_case(CORE.name)) if CORE.name else None return None @@ -757,3 +760,140 @@ def test_entity_duplicate_validator_same_name_no_enhanced_message() -> None: r"Each entity on a device must have a unique name within its platform\.$", ): validator(config2) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_with_device( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty entity name on a sub-device. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime from the device's actual name. + """ + added_expressions = setup_test_environment + + # Mock get_variable to return a mock device + original_get_variable = entity_helpers.get_variable + + async def mock_get_variable(id_: ID) -> MockObj: + return MockObj("sub_device_1") + + entity_helpers.get_variable = mock_get_variable + + var = MockObj("sensor1") + device_id = ID("sub_device_1", type="Device") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + CONF_DEVICE_ID: device_id, + } + + await setup_entity(var, config, "sensor") + + entity_helpers.get_variable = original_get_variable + + # Check that set_device was called + assert any("sensor1.set_device" in expr for expr in added_expressions) + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_with_mac_suffix( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty name and MAC suffix enabled. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime from friendly_name (bug-for-bug compatibility). + """ + added_expressions = setup_test_environment + + # Set up CORE.config with name_add_mac_suffix enabled + CORE.config = {"name_add_mac_suffix": True} + # Set friendly_name to a specific value + CORE.friendly_name = "My Device" + + var = MockObj("sensor1") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + } + + await setup_entity(var, config, "sensor") + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_with_mac_suffix_no_friendly_name( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty name, MAC suffix enabled, but no friendly_name. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime. In this case C++ will hash the empty friendly_name + (bug-for-bug compatibility). + """ + added_expressions = setup_test_environment + + # Set up CORE.config with name_add_mac_suffix enabled + CORE.config = {"name_add_mac_suffix": True} + # Set friendly_name to empty + CORE.friendly_name = "" + + var = MockObj("sensor1") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + } + + await setup_entity(var, config, "sensor") + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) + + +@pytest.mark.asyncio +async def test_setup_entity_empty_name_no_mac_suffix_no_friendly_name( + setup_test_environment: list[str], +) -> None: + """Test setup_entity with empty name, no MAC suffix, and no friendly_name. + + For empty-name entities, Python passes 0 and C++ calculates the hash + at runtime from the device name. + """ + added_expressions = setup_test_environment + + # No MAC suffix (either not set or False) + CORE.config = {} + # No friendly_name + CORE.friendly_name = "" + # Device name is set + CORE.name = "my-test-device" + + var = MockObj("sensor1") + + config = { + CONF_NAME: "", + CONF_DISABLED_BY_DEFAULT: False, + } + + await setup_entity(var, config, "sensor") + + # For empty-name entities, Python passes 0 - C++ calculates hash at runtime + assert any('set_name("", 0)' in expr for expr in added_expressions), ( + f"Expected set_name with hash 0, got {added_expressions}" + ) diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index c9d7b7486e..9602010ad3 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -502,3 +502,77 @@ def test_only_with_user_value_overrides_default() -> None: result = schema({"mqtt_id": "custom_id"}) assert result.get("mqtt_id") == "custom_id" + + +@pytest.mark.parametrize("value", ("hello", "Hello World", "test_name", "温度")) +def test_string_no_slash__valid(value: str) -> None: + actual = config_validation.string_no_slash(value) + assert actual == value + + +@pytest.mark.parametrize( + ("value", "expected"), + ( + ("has/slash", "has⁄slash"), + ("a/b/c", "a⁄b⁄c"), + ("/leading", "⁄leading"), + ("trailing/", "trailing⁄"), + ), +) +def test_string_no_slash__slash_replaced_with_warning( + value: str, expected: str, caplog: pytest.LogCaptureFixture +) -> None: + """Test that '/' is auto-replaced with fraction slash and warning is logged.""" + actual = config_validation.string_no_slash(value) + assert actual == expected + assert "reserved as a URL path separator" in caplog.text + assert "will become an error in ESPHome 2026.7.0" in caplog.text + + +def test_string_no_slash__long_string_allowed() -> None: + # string_no_slash doesn't enforce length - use cv.Length() separately + long_value = "x" * 200 + assert config_validation.string_no_slash(long_value) == long_value + + +def test_string_no_slash__empty() -> None: + assert config_validation.string_no_slash("") == "" + + +@pytest.mark.parametrize("value", ("Temperature", "Living Room Light", "温度传感器")) +def test_validate_entity_name__valid(value: str) -> None: + actual = config_validation._validate_entity_name(value) + assert actual == value + + +def test_validate_entity_name__slash_replaced_with_warning( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that '/' in entity names is auto-replaced with fraction slash.""" + actual = config_validation._validate_entity_name("has/slash") + assert actual == "has⁄slash" + assert "reserved as a URL path separator" in caplog.text + + +def test_validate_entity_name__max_length() -> None: + # 120 chars should pass + assert config_validation._validate_entity_name("x" * 120) == "x" * 120 + + # 121 chars should fail + with pytest.raises(Invalid, match="too long.*121 chars.*Maximum.*120"): + config_validation._validate_entity_name("x" * 121) + + +def test_validate_entity_name__none_without_friendly_name() -> None: + # When name is "None" and friendly_name is not set, it should fail + CORE.friendly_name = None + with pytest.raises(Invalid, match="friendly_name is not set"): + config_validation._validate_entity_name("None") + + +def test_validate_entity_name__none_with_friendly_name() -> None: + # When name is "None" but friendly_name is set, it should return None + CORE.friendly_name = "My Device" + result = config_validation._validate_entity_name("None") + assert result is None + CORE.friendly_name = None # Reset diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 47b945e0eb..159d3230ab 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -279,6 +279,77 @@ def test_sanitize(text, expected): assert actual == expected +@pytest.mark.parametrize( + ("name", "expected_hash"), + ( + # Basic strings - hash of sanitize(snake_case(name)) + ("foo", 0x408F5E13), + ("Foo", 0x408F5E13), # Same as "foo" (lowercase) + ("FOO", 0x408F5E13), # Same as "foo" (lowercase) + # Spaces become underscores + ("foo bar", 0x3AE35AA1), # Transforms to "foo_bar" + ("Foo Bar", 0x3AE35AA1), # Same (lowercase + underscore) + # Already snake_case + ("foo_bar", 0x3AE35AA1), + # Special chars become underscores + ("foo!bar", 0x3AE35AA1), # Transforms to "foo_bar" + ("foo@bar", 0x3AE35AA1), # Transforms to "foo_bar" + # Hyphens are preserved + ("foo-bar", 0x438B12E3), + # Numbers are preserved + ("foo123", 0xF3B0067D), + # Empty string + ("", 0x811C9DC5), # FNV1_OFFSET_BASIS (no chars processed) + # Single char + ("a", 0x050C5D7E), + # Mixed case and spaces + ("My Sensor Name", 0x2760962A), # Transforms to "my_sensor_name" + ), +) +def test_fnv1_hash_object_id(name, expected_hash): + """Test fnv1_hash_object_id produces expected hashes. + + These expected values were computed to match the C++ implementation + in esphome/core/helpers.h. If this test fails after modifying either + implementation, ensure both Python and C++ versions stay in sync. + """ + actual = helpers.fnv1_hash_object_id(name) + + assert actual == expected_hash + + +def _fnv1_hash_py(s: str) -> int: + """Python implementation of FNV-1 hash for verification.""" + hash_val = 2166136261 # FNV1_OFFSET_BASIS + for c in s: + hash_val = (hash_val * 16777619) & 0xFFFFFFFF # FNV1_PRIME + hash_val ^= ord(c) + return hash_val + + +@pytest.mark.parametrize( + "name", + ( + "Simple", + "With Space", + "MixedCase", + "special!@#chars", + "already_snake_case", + "123numbers", + ), +) +def test_fnv1_hash_object_id_matches_manual_calculation(name): + """Verify fnv1_hash_object_id matches snake_case + sanitize + standard FNV-1.""" + # Manual calculation: snake_case -> sanitize -> fnv1_hash + transformed = helpers.sanitize(helpers.snake_case(name)) + expected = _fnv1_hash_py(transformed) + + # Direct calculation via fnv1_hash_object_id + actual = helpers.fnv1_hash_object_id(name) + + assert actual == expected + + @pytest.mark.parametrize( "text, expected", ((["127.0.0.1", "fe80::1", "2001::2"], ["2001::2", "127.0.0.1", "fe80::1"]),), diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index bd14395037..fd8f04ded5 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -4,9 +4,11 @@ from __future__ import annotations from collections.abc import Generator from dataclasses import dataclass +import json import logging from pathlib import Path import re +import time from typing import Any from unittest.mock import MagicMock, Mock, patch @@ -22,6 +24,7 @@ from esphome.__main__ import ( command_rename, command_update_all, command_wizard, + compile_program, detect_external_components, get_port_type, has_ip_address, @@ -31,6 +34,7 @@ from esphome.__main__ import ( has_non_ip_address, has_resolvable_address, mqtt_get_ip, + run_miniterm, show_logs, upload_program, upload_using_esptool, @@ -38,11 +42,13 @@ from esphome.__main__ import ( from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANT_ESP32 from esphome.const import ( CONF_API, + CONF_BAUD_RATE, CONF_BROKER, CONF_DISABLED, CONF_ESPHOME, CONF_LEVEL, CONF_LOG_TOPIC, + CONF_LOGGER, CONF_MDNS, CONF_MQTT, CONF_NAME, @@ -835,6 +841,7 @@ class MockArgs: configuration: str | None = None name: str | None = None dashboard: bool = False + reset: bool = False def test_upload_program_serial_esp32( @@ -2475,6 +2482,7 @@ def test_command_analyze_memory_success( "/path/to/objdump", "/path/to/readelf", set(), # No external components + idedata=mock_get_idedata.return_value, ) # Verify analysis was run @@ -2544,6 +2552,7 @@ def test_command_analyze_memory_with_external_components( "/path/to/objdump", "/path/to/readelf", {"my_custom_component"}, # External component detected + idedata=mock_get_idedata.return_value, ) @@ -2605,3 +2614,561 @@ def test_command_analyze_memory_no_idedata( assert result == 1 assert "Failed to get IDE data for memory analysis" in caplog.text + + +@pytest.fixture +def mock_compile_build_info_run_compile() -> Generator[Mock]: + """Mock platformio_api.run_compile for build_info tests.""" + with patch("esphome.platformio_api.run_compile", return_value=0) as mock: + yield mock + + +@pytest.fixture +def mock_compile_build_info_get_idedata() -> Generator[Mock]: + """Mock platformio_api.get_idedata for build_info tests.""" + mock_idedata = MagicMock() + with patch("esphome.platformio_api.get_idedata", return_value=mock_idedata) as mock: + yield mock + + +def _setup_build_info_test( + tmp_path: Path, + *, + create_firmware: bool = True, + create_build_info: bool = True, + build_info_content: str | None = None, + firmware_first: bool = False, +) -> tuple[Path, Path]: + """Set up build directory structure for build_info tests. + + Args: + tmp_path: Temporary directory path. + create_firmware: Whether to create firmware.bin file. + create_build_info: Whether to create build_info.json file. + build_info_content: Custom content for build_info.json, or None for default. + firmware_first: If True, create firmware before build_info (makes firmware older). + + Returns: + Tuple of (build_info_path, firmware_path). + """ + setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path, name="test_device") + + build_path = tmp_path / ".esphome" / "build" / "test_device" + pioenvs_path = build_path / ".pioenvs" / "test_device" + pioenvs_path.mkdir(parents=True, exist_ok=True) + + build_info_path = build_path / "build_info.json" + firmware_path = pioenvs_path / "firmware.bin" + + default_build_info = json.dumps( + { + "config_hash": 0x12345678, + "build_time": int(time.time()), + "build_time_str": "Dec 13 2025, 12:00:00", + "esphome_version": "2025.1.0", + } + ) + + def create_build_info_file() -> None: + if create_build_info: + content = ( + build_info_content + if build_info_content is not None + else default_build_info + ) + build_info_path.write_text(content) + + def create_firmware_file() -> None: + if create_firmware: + firmware_path.write_bytes(b"fake firmware") + + if firmware_first: + create_firmware_file() + time.sleep(0.01) # Ensure different timestamps + create_build_info_file() + else: + create_build_info_file() + time.sleep(0.01) # Ensure different timestamps + create_firmware_file() + + return build_info_path, firmware_path + + +def test_compile_program_emits_build_info_when_firmware_rebuilt( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program logs build_info when firmware is rebuilt.""" + _setup_build_info_test(tmp_path, firmware_first=False) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info: config_hash=0x12345678" in caplog.text + + +def test_compile_program_no_build_info_when_firmware_not_rebuilt( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when firmware wasn't rebuilt.""" + _setup_build_info_test(tmp_path, firmware_first=True) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_firmware_missing( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when firmware.bin doesn't exist.""" + _setup_build_info_test(tmp_path, create_firmware=False) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_json_missing( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when build_info.json doesn't exist.""" + _setup_build_info_test(tmp_path, create_build_info=False) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_json_invalid( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when build_info.json is invalid.""" + _setup_build_info_test(tmp_path, build_info_content="not valid json {{{") + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.DEBUG): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +def test_compile_program_no_build_info_when_json_missing_keys( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + mock_compile_build_info_run_compile: Mock, + mock_compile_build_info_get_idedata: Mock, +) -> None: + """Test that compile_program doesn't log build_info when build_info.json is missing required keys.""" + _setup_build_info_test( + tmp_path, build_info_content=json.dumps({"build_time": 1234567890}) + ) + + config: dict[str, Any] = {CONF_ESPHOME: {CONF_NAME: "test_device"}} + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = compile_program(args, config) + + assert result == 0 + assert "Build Info:" not in caplog.text + + +# Tests for run_miniterm serial log batching + + +# Sentinel to signal end of mock serial data (raises SerialException) +MOCK_SERIAL_END = object() + + +class MockSerial: + """Mock serial port for testing run_miniterm.""" + + def __init__(self, chunks: list[bytes | object]) -> None: + """Initialize with a list of chunks to return from read(). + + Args: + chunks: List of byte chunks to return sequentially. + Use MOCK_SERIAL_END sentinel to signal end of data. + Empty bytes b"" simulate timeout (no data available). + """ + self.chunks = list(chunks) + self.chunk_index = 0 + self.baudrate = 0 + self.port = "" + self.dtr = True + self.rts = True + self.timeout = 0.1 + self._is_open = False + + def __enter__(self) -> MockSerial: + self._is_open = True + return self + + def __exit__(self, *args: Any) -> None: + self._is_open = False + + @property + def in_waiting(self) -> int: + """Return number of bytes available.""" + if self.chunk_index < len(self.chunks): + chunk = self.chunks[self.chunk_index] + if chunk is MOCK_SERIAL_END: + return 0 + return len(chunk) # type: ignore[arg-type] + return 0 + + def read(self, size: int = 1) -> bytes: + """Read up to size bytes from the current chunk. + + This method respects the size argument and keeps any unconsumed + bytes in the current chunk so that subsequent calls to in_waiting + and read see the remaining data. + """ + if self.chunk_index < len(self.chunks): + chunk = self.chunks[self.chunk_index] + if chunk is MOCK_SERIAL_END: + # Sentinel means we're done - simulate port closed + import serial + + raise serial.SerialException("Port closed") + # Respect the requested size and keep any remaining bytes + if size <= 0: + return b"" + data = chunk[:size] # type: ignore[index] + remaining = chunk[size:] # type: ignore[index] + if remaining: + # Keep remaining bytes for the next read + self.chunks[self.chunk_index] = remaining # type: ignore[assignment] + else: + # Entire chunk consumed; advance to the next one + self.chunk_index += 1 + return data # type: ignore[return-value] + import serial + + raise serial.SerialException("Port closed") + + +def test_run_miniterm_batches_lines_with_same_timestamp( + capfd: CaptureFixture[str], +) -> None: + """Test that lines from the same chunk get the same timestamp.""" + # Simulate receiving multiple log lines in a single chunk + # This is how data arrives over USB - many lines at once + chunk = b"[I][app:100]: Line 1\r\n[I][app:100]: Line 2\r\n[I][app:100]: Line 3\r\n" + + mock_serial = MockSerial([chunk, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + ): + mock_bt.return_value = False + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 0 + + captured = capfd.readouterr() + lines = [line for line in captured.out.strip().split("\n") if line] + + # All 3 lines should have the same timestamp (first 13 chars like "[HH:MM:SS.mmm]") + assert len(lines) == 3 + timestamps = [line[:13] for line in lines] + assert timestamps[0] == timestamps[1] == timestamps[2], ( + f"Lines from same chunk should have same timestamp: {timestamps}" + ) + + +def test_run_miniterm_different_chunks_different_timestamps( + capfd: CaptureFixture[str], +) -> None: + """Test that lines from different chunks can have different timestamps.""" + # Two separate chunks - could have different timestamps + chunk1 = b"[I][app:100]: Chunk 1 Line\r\n" + chunk2 = b"[I][app:100]: Chunk 2 Line\r\n" + + mock_serial = MockSerial([chunk1, chunk2, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + ): + mock_bt.return_value = False + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 0 + + captured = capfd.readouterr() + lines = [line for line in captured.out.strip().split("\n") if line] + assert len(lines) == 2 + + +def test_run_miniterm_handles_split_lines() -> None: + """Test that partial lines are buffered until complete.""" + # Line split across two chunks + chunk1 = b"[I][app:100]: Start of " + chunk2 = b"line\r\n" + + mock_serial = MockSerial([chunk1, chunk2, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + patch("esphome.__main__.safe_print") as mock_print, + ): + mock_bt.return_value = False + run_miniterm(config, "/dev/ttyUSB0", args) + + # Should have printed exactly one complete line + assert mock_print.call_count == 1 + printed_line = mock_print.call_args[0][0] + assert "Start of line" in printed_line + + +def test_run_miniterm_backtrace_state_maintained() -> None: + """Test that backtrace_state is properly maintained across lines. + + ESP8266 backtraces span multiple lines between >>>stack>>> and <<>>stack>>>\r\n" + b"3ffffe90: 40220ef8 b66aa8c0 3fff0a4c 40204c84\r\n" + b"3ffffea0: 00000005 0000a635 3fff191c 4020413c\r\n" + b"<< bool: + """Track the backtrace_state progression.""" + backtrace_states.append((line, backtrace_state)) + # Simulate actual behavior + if ">>>stack>>>" in line: + return True + if "<<>>stack>>> - state should be False (before processing) + assert ">>>stack>>>" in backtrace_states[0][0] + assert backtrace_states[0][1] is False + + # Line 2: stack data - state should be True (after >>>stack>>>) + assert "40220ef8" in backtrace_states[1][0] + assert backtrace_states[1][1] is True + + # Line 3: more stack data - state should be True + assert "4020413c" in backtrace_states[2][0] + assert backtrace_states[2][1] is True + + # Line 4: << None: + """Test that empty reads (timeouts) are handled correctly. + + When read() returns empty bytes, the code should continue waiting + for more data without processing anything. + """ + # Simulate: empty read (timeout), then data, then empty read, then end + chunk = b"[I][app:100]: Test line\r\n" + + mock_serial = MockSerial([b"", chunk, b"", MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + ): + mock_bt.return_value = False + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 0 + + captured = capfd.readouterr() + lines = [line for line in captured.out.strip().split("\n") if line] + # Should have exactly one line despite empty reads + assert len(lines) == 1 + assert "Test line" in lines[0] + + +def test_run_miniterm_no_logger_returns_early( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that run_miniterm returns early if logger is not configured.""" + config: dict[str, Any] = {} # No logger config + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 1 + assert "Logger is not enabled" in caplog.text + + +def test_run_miniterm_baud_rate_zero_returns_early( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that run_miniterm returns early if baud_rate is 0.""" + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 0, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with caplog.at_level(logging.INFO): + result = run_miniterm(config, "/dev/ttyUSB0", args) + + assert result == 1 + assert "UART logging is disabled" in caplog.text + + +def test_run_miniterm_buffer_limit_prevents_unbounded_growth() -> None: + """Test that buffer is limited to prevent unbounded memory growth. + + If a device sends data without newlines, the buffer should be truncated + to SERIAL_BUFFER_MAX_SIZE to prevent memory exhaustion. + """ + # Use a small buffer limit for testing + test_buffer_limit = 100 + + # Create data larger than the limit without newlines + large_data_no_newline = b"X" * 150 # 150 bytes, no newline + final_line = b"END\r\n" + + mock_serial = MockSerial([large_data_no_newline, final_line, MOCK_SERIAL_END]) + + config = { + CONF_LOGGER: { + CONF_BAUD_RATE: 115200, + "deassert_rts_dtr": False, + } + } + args = MockArgs() + + with ( + patch("serial.Serial", return_value=mock_serial), + patch.object(platformio_api, "process_stacktrace") as mock_bt, + patch("esphome.__main__.safe_print") as mock_print, + patch("esphome.__main__.SERIAL_BUFFER_MAX_SIZE", test_buffer_limit), + ): + mock_bt.return_value = False + run_miniterm(config, "/dev/ttyUSB0", args) + + # Should have printed exactly one line + assert mock_print.call_count == 1 + printed_line = mock_print.call_args[0][0] + + # The line should contain "END" and some X's, but not all 150 X's + # because the buffer was truncated + assert "END" in printed_line + assert "X" in printed_line + # Verify truncation happened - we shouldn't have all 150 X's + # The buffer logic is: + # 1. Add 150 X's -> buffer = 150 bytes -> truncate to last 100 = 100 X's + # 2. Add "END\r\n" (5 bytes) -> buffer = 105 bytes -> truncate to last 100 + # = 95 X's + "END\r\n" + # 3. Find newline, extract line = "95 X's + END" + x_count = printed_line.count("X") + assert x_count < 150, f"Expected truncation but got {x_count} X's" + assert x_count == 95, f"Expected 95 X's after truncation but got {x_count}" diff --git a/tests/unit_tests/test_writer.py b/tests/unit_tests/test_writer.py index fa2ca0a696..ac05e0d31b 100644 --- a/tests/unit_tests/test_writer.py +++ b/tests/unit_tests/test_writer.py @@ -1,6 +1,10 @@ """Test writer module functionality.""" from collections.abc import Callable +from contextlib import contextmanager +from dataclasses import dataclass +from datetime import datetime +import json import os from pathlib import Path import stat @@ -27,6 +31,9 @@ from esphome.writer import ( clean_all, clean_build, clean_cmake_cache, + copy_src_tree, + generate_build_info_data_h, + get_build_info, storage_should_clean, storage_should_update_cmake_cache, update_storage_json, @@ -1253,3 +1260,775 @@ def test_clean_build_reraises_for_other_errors( finally: # Cleanup - restore write permission so tmp_path cleanup works os.chmod(subdir, stat.S_IRWXU) + + +# Tests for get_build_info() + + +@patch("esphome.writer.CORE") +def test_get_build_info_new_build( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns new build_time when no existing build_info.json.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + mock_core.comment = "Test comment" + + config_hash, build_time, build_time_str, comment = get_build_info() + + assert config_hash == 0x12345678 + assert isinstance(build_time, int) + assert build_time > 0 + assert isinstance(build_time_str, str) + # Verify build_time_str format matches expected pattern + assert len(build_time_str) >= 19 # e.g., "2025-12-15 16:27:44 +0000" + assert comment == "Test comment" + + +@patch("esphome.writer.CORE") +def test_get_build_info_always_returns_current_time( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info always returns current build_time.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + mock_core.comment = "" + + # Create existing build_info.json with matching config_hash and version + existing_build_time = 1700000000 + existing_build_time_str = "2023-11-14 22:13:20 +0000" + build_info_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, + "build_time": existing_build_time, + "build_time_str": existing_build_time_str, + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): + config_hash, build_time, build_time_str, comment = get_build_info() + + assert config_hash == 0x12345678 + # get_build_info now always returns current time + assert build_time != existing_build_time + assert build_time > existing_build_time + assert build_time_str != existing_build_time_str + + +@patch("esphome.writer.CORE") +def test_get_build_info_config_changed( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns new build_time when config hash changed.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0xABCDEF00 # Different from existing + mock_core.comment = "" + + # Create existing build_info.json with different config_hash + existing_build_time = 1700000000 + build_info_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, # Different + "build_time": existing_build_time, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): + config_hash, build_time, build_time_str, comment = get_build_info() + + assert config_hash == 0xABCDEF00 + assert build_time != existing_build_time # New time generated + assert build_time > existing_build_time + + +@patch("esphome.writer.CORE") +def test_get_build_info_version_changed( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns new build_time when ESPHome version changed.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + mock_core.comment = "" + + # Create existing build_info.json with different version + existing_build_time = 1700000000 + build_info_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, + "build_time": existing_build_time, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2024.12.0", # Old version + } + ) + ) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): # New version + config_hash, build_time, build_time_str, comment = get_build_info() + + assert config_hash == 0x12345678 + assert build_time != existing_build_time # New time generated + assert build_time > existing_build_time + + +@patch("esphome.writer.CORE") +def test_get_build_info_invalid_json( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info handles invalid JSON gracefully.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + mock_core.comment = "" + + # Create invalid JSON file + build_info_path.write_text("not valid json {{{") + + config_hash, build_time, build_time_str, comment = get_build_info() + + assert config_hash == 0x12345678 + assert isinstance(build_time, int) + assert build_time > 0 + + +@patch("esphome.writer.CORE") +def test_get_build_info_missing_keys( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info handles missing keys gracefully.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + mock_core.comment = "" + + # Create JSON with missing keys + build_info_path.write_text(json.dumps({"config_hash": 0x12345678})) + + with patch("esphome.writer.__version__", "2025.1.0-dev"): + config_hash, build_time, build_time_str, comment = get_build_info() + + assert config_hash == 0x12345678 + assert isinstance(build_time, int) + assert build_time > 0 + + +@patch("esphome.writer.CORE") +def test_get_build_info_build_time_str_format( + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test get_build_info returns correctly formatted build_time_str.""" + build_info_path = tmp_path / "build_info.json" + mock_core.relative_build_path.return_value = build_info_path + mock_core.config_hash = 0x12345678 + mock_core.comment = "" + + config_hash, build_time, build_time_str, comment = get_build_info() + + # Verify the format matches "%Y-%m-%d %H:%M:%S %z" + # e.g., "2025-12-15 16:27:44 +0000" + parsed = datetime.strptime(build_time_str, "%Y-%m-%d %H:%M:%S %z") + assert parsed.year >= 2024 + + +def test_generate_build_info_data_h_format() -> None: + """Test generate_build_info_data_h produces correct header content.""" + config_hash = 0x12345678 + build_time = 1700000000 + build_time_str = "2023-11-14 22:13:20 +0000" + comment = "Test comment" + + result = generate_build_info_data_h( + config_hash, build_time, build_time_str, comment + ) + + assert "#pragma once" in result + assert "#define ESPHOME_CONFIG_HASH 0x12345678U" in result + assert "#define ESPHOME_BUILD_TIME 1700000000" in result + assert "#define ESPHOME_COMMENT_SIZE 13" in result # len("Test comment") + 1 + assert 'ESPHOME_BUILD_TIME_STR[] = "2023-11-14 22:13:20 +0000"' in result + assert 'ESPHOME_COMMENT_STR[] = "Test comment"' in result + + +def test_generate_build_info_data_h_esp8266_progmem() -> None: + """Test generate_build_info_data_h includes PROGMEM for ESP8266.""" + result = generate_build_info_data_h(0xABCDEF01, 1700000000, "test", "comment") + + # Should have ESP8266 PROGMEM conditional + assert "#ifdef USE_ESP8266" in result + assert "#include " in result + assert "PROGMEM" in result + # Both build time and comment should have PROGMEM versions + assert 'ESPHOME_BUILD_TIME_STR[] PROGMEM = "test"' in result + assert 'ESPHOME_COMMENT_STR[] PROGMEM = "comment"' in result + + +def test_generate_build_info_data_h_hash_formatting() -> None: + """Test generate_build_info_data_h formats hash with leading zeros.""" + # Test with small hash value that needs leading zeros + result = generate_build_info_data_h(0x00000001, 0, "test", "") + assert "#define ESPHOME_CONFIG_HASH 0x00000001U" in result + + # Test with larger hash value + result = generate_build_info_data_h(0xFFFFFFFF, 0, "test", "") + assert "#define ESPHOME_CONFIG_HASH 0xffffffffU" in result + + +def test_generate_build_info_data_h_comment_escaping() -> None: + r"""Test generate_build_info_data_h properly escapes special characters in comment. + + Uses cpp_string_escape which outputs octal escapes for special characters: + - backslash (ASCII 92) -> \134 + - double quote (ASCII 34) -> \042 + - newline (ASCII 10) -> \012 + """ + # Test backslash escaping (ASCII 92 = octal 134) + result = generate_build_info_data_h(0, 0, "test", "backslash\\here") + assert 'ESPHOME_COMMENT_STR[] = "backslash\\134here"' in result + + # Test quote escaping (ASCII 34 = octal 042) + result = generate_build_info_data_h(0, 0, "test", 'has "quotes"') + assert 'ESPHOME_COMMENT_STR[] = "has \\042quotes\\042"' in result + + # Test newline escaping (ASCII 10 = octal 012) + result = generate_build_info_data_h(0, 0, "test", "line1\nline2") + assert 'ESPHOME_COMMENT_STR[] = "line1\\012line2"' in result + + +def test_generate_build_info_data_h_empty_comment() -> None: + """Test generate_build_info_data_h handles empty comment.""" + result = generate_build_info_data_h(0, 0, "test", "") + + assert "#define ESPHOME_COMMENT_SIZE 1" in result # Just null terminator + assert 'ESPHOME_COMMENT_STR[] = ""' in result + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_writes_build_info_files( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree writes build_info_data.h and build_info.json.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create mock source files for defines.h and version.h + mock_defines_h = esphome_core_path / "defines.h" + mock_defines_h.write_text("// mock defines.h") + mock_version_h = esphome_core_path / "version.h" + mock_version_h.write_text("// mock version.h") + + # Create mock FileResource that returns our temp files + @dataclass(frozen=True) + class MockFileResource: + package: str + resource: str + _path: Path + + @contextmanager + def path(self): + yield self._path + + # Create mock resources for defines.h and version.h (required by copy_src_tree) + mock_resources = [ + MockFileResource( + package="esphome.core", + resource="defines.h", + _path=mock_defines_h, + ), + MockFileResource( + package="esphome.core", + resource="version.h", + _path=mock_version_h, + ), + ] + + # Create mock component with resources + mock_component = MagicMock() + mock_component.resources = mock_resources + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "Test comment" + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [("core", mock_component)] + mock_walk_files.return_value = [] + + # Create mock module without copy_files attribute (causes AttributeError which is caught) + mock_module = MagicMock(spec=[]) # Empty spec = no copy_files attribute + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module", return_value=mock_module), + ): + copy_src_tree() + + # Verify build_info_data.h was written + build_info_h_path = esphome_core_path / "build_info_data.h" + assert build_info_h_path.exists() + build_info_h_content = build_info_h_path.read_text() + assert "#define ESPHOME_CONFIG_HASH 0xdeadbeefU" in build_info_h_content + assert "#define ESPHOME_BUILD_TIME" in build_info_h_content + assert "ESPHOME_BUILD_TIME_STR" in build_info_h_content + assert "#define ESPHOME_COMMENT_SIZE" in build_info_h_content + assert "ESPHOME_COMMENT_STR" in build_info_h_content + + # Verify build_info.json was written + build_info_json_path = build_path / "build_info.json" + assert build_info_json_path.exists() + build_info_json = json.loads(build_info_json_path.read_text()) + assert build_info_json["config_hash"] == 0xDEADBEEF + assert "build_time" in build_info_json + assert "build_time_str" in build_info_json + assert build_info_json["esphome_version"] == "2025.1.0-dev" + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_detects_config_hash_change( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree detects when config_hash changes.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create existing build_info.json with different config_hash + build_info_json_path = build_path / "build_info.json" + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0x12345678, # Different from current + "build_time": 1700000000, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + # Create existing build_info_data.h + build_info_h_path = esphome_core_path / "build_info_data.h" + build_info_h_path.write_text("// old build_info_data.h") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF # Different from existing + mock_core.comment = "" + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + mock_walk_files.return_value = [] + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info files were updated due to config_hash change + assert build_info_h_path.exists() + new_content = build_info_h_path.read_text() + assert "0xdeadbeef" in new_content.lower() + + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["config_hash"] == 0xDEADBEEF + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_detects_version_change( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree detects when esphome_version changes.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create existing build_info.json with different version + build_info_json_path = build_path / "build_info.json" + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": 1700000000, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2024.12.0", # Old version + } + ) + ) + + # Create existing build_info_data.h + build_info_h_path = esphome_core_path / "build_info_data.h" + build_info_h_path.write_text("// old build_info_data.h") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + mock_walk_files.return_value = [] + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), # New version + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info files were updated due to version change + assert build_info_h_path.exists() + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["esphome_version"] == "2025.1.0-dev" + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_handles_invalid_build_info_json( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree handles invalid build_info.json gracefully.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create invalid build_info.json + build_info_json_path = build_path / "build_info.json" + build_info_json_path.write_text("invalid json {{{") + + # Create existing build_info_data.h + build_info_h_path = esphome_core_path / "build_info_data.h" + build_info_h_path.write_text("// old build_info_data.h") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + mock_walk_files.return_value = [] + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info files were created despite invalid JSON + assert build_info_h_path.exists() + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["config_hash"] == 0xDEADBEEF + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_build_info_timestamp_behavior( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test build_info behaviour: regenerated on change, preserved when unchanged.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + esphome_components_path = src_path / "esphome" / "components" + esphome_components_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create a source file + source_file = tmp_path / "source" / "test.cpp" + source_file.parent.mkdir() + source_file.write_text("// version 1") + + # Create destination file in build tree + dest_file = esphome_components_path / "test.cpp" + + # Create mock FileResource + @dataclass(frozen=True) + class MockFileResource: + package: str + resource: str + _path: Path + + @contextmanager + def path(self): + yield self._path + + mock_resources = [ + MockFileResource( + package="esphome.components", + resource="test.cpp", + _path=source_file, + ), + ] + + mock_component = MagicMock() + mock_component.resources = mock_resources + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [("test", mock_component)] + + build_info_json_path = build_path / "build_info.json" + + # First run: initial setup, should create build_info + mock_walk_files.return_value = [] + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Manually set an old timestamp for testing + old_timestamp = 1700000000 + old_timestamp_str = "2023-11-14 22:13:20 +0000" + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": old_timestamp, + "build_time_str": old_timestamp_str, + "esphome_version": "2025.1.0-dev", + } + ) + ) + + # Second run: no changes, should NOT regenerate build_info + mock_walk_files.return_value = [str(dest_file)] + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + second_json = json.loads(build_info_json_path.read_text()) + second_timestamp = second_json["build_time"] + + # Verify timestamp was NOT changed + assert second_timestamp == old_timestamp, ( + f"build_info should not be regenerated when no files change: " + f"{old_timestamp} != {second_timestamp}" + ) + + # Third run: change source file, should regenerate build_info with new timestamp + source_file.write_text("// version 2") + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + third_json = json.loads(build_info_json_path.read_text()) + third_timestamp = third_json["build_time"] + + # Verify timestamp WAS changed + assert third_timestamp != old_timestamp, ( + f"build_info should be regenerated when source file changes: " + f"{old_timestamp} == {third_timestamp}" + ) + assert third_timestamp > old_timestamp + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_detects_removed_source_file( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree detects when a non-generated source file is removed.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_components_path = src_path / "esphome" / "components" + esphome_components_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create an existing source file in the build tree + existing_file = esphome_components_path / "test.cpp" + existing_file.write_text("// test file") + + # Setup mocks - no components, so the file should be removed + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] # No components = file should be removed + mock_walk_files.return_value = [str(existing_file)] + + # Create existing build_info.json + build_info_json_path = build_path / "build_info.json" + old_timestamp = 1700000000 + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": old_timestamp, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify file was removed + assert not existing_file.exists() + + # Verify build_info was regenerated due to source file removal + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["build_time"] != old_timestamp + + +@patch("esphome.writer.CORE") +@patch("esphome.writer.iter_components") +@patch("esphome.writer.walk_files") +def test_copy_src_tree_ignores_removed_generated_file( + mock_walk_files: MagicMock, + mock_iter_components: MagicMock, + mock_core: MagicMock, + tmp_path: Path, +) -> None: + """Test copy_src_tree doesn't mark sources_changed when only generated file removed.""" + # Setup directory structure + src_path = tmp_path / "src" + src_path.mkdir() + esphome_core_path = src_path / "esphome" / "core" + esphome_core_path.mkdir(parents=True) + build_path = tmp_path / "build" + build_path.mkdir() + + # Create existing build_info_data.h (a generated file) + build_info_h = esphome_core_path / "build_info_data.h" + build_info_h.write_text("// old generated file") + + # Setup mocks + mock_core.relative_src_path.side_effect = lambda *args: src_path.joinpath(*args) + mock_core.relative_build_path.side_effect = lambda *args: build_path.joinpath(*args) + mock_core.defines = [] + mock_core.config_hash = 0xDEADBEEF + mock_core.comment = "" + mock_core.target_platform = "test_platform" + mock_core.config = {} + mock_iter_components.return_value = [] + # walk_files returns the generated file, but it's not in source_files_copy + mock_walk_files.return_value = [str(build_info_h)] + + # Create existing build_info.json with old timestamp + build_info_json_path = build_path / "build_info.json" + old_timestamp = 1700000000 + build_info_json_path.write_text( + json.dumps( + { + "config_hash": 0xDEADBEEF, + "build_time": old_timestamp, + "build_time_str": "2023-11-14 22:13:20 +0000", + "esphome_version": "2025.1.0-dev", + } + ) + ) + + with ( + patch("esphome.writer.__version__", "2025.1.0-dev"), + patch("esphome.writer.importlib.import_module") as mock_import, + ): + mock_import.side_effect = AttributeError + copy_src_tree() + + # Verify build_info_data.h was regenerated (not removed) + assert build_info_h.exists() + + # Note: build_info.json will have a new timestamp because get_build_info() + # always returns current time. The key test is that the old build_info_data.h + # file was removed and regenerated, not that it triggered sources_changed. + new_json = json.loads(build_info_json_path.read_text()) + assert new_json["config_hash"] == 0xDEADBEEF diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index eac0ceabb8..c8cb3e144f 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -278,3 +278,31 @@ def test_secret_values_tracking(fixture_path: Path) -> None: assert yaml_util._SECRET_VALUES["super_secret_wifi"] == "wifi_password" assert "0123456789abcdef" in yaml_util._SECRET_VALUES assert yaml_util._SECRET_VALUES["0123456789abcdef"] == "api_key" + + +def test_dump_sort_keys() -> None: + """Test that dump with sort_keys=True produces sorted output.""" + # Create a dict with unsorted keys + data = { + "zebra": 1, + "alpha": 2, + "nested": { + "z_key": "z_value", + "a_key": "a_value", + }, + } + + # Without sort_keys, keys are in insertion order + unsorted = yaml_util.dump(data, sort_keys=False) + lines_unsorted = unsorted.strip().split("\n") + # First key should be "zebra" (insertion order) + assert lines_unsorted[0].startswith("zebra:") + + # With sort_keys, keys are alphabetically sorted + sorted_dump = yaml_util.dump(data, sort_keys=True) + lines_sorted = sorted_dump.strip().split("\n") + # First key should be "alpha" (alphabetical order) + assert lines_sorted[0].startswith("alpha:") + # nested keys should also be sorted + assert "a_key:" in sorted_dump + assert sorted_dump.index("a_key:") < sorted_dump.index("z_key:")