mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	| @@ -31,7 +31,7 @@ | |||||||
|         "ms-python.python", |         "ms-python.python", | ||||||
|         "ms-python.pylint", |         "ms-python.pylint", | ||||||
|         "ms-python.flake8", |         "ms-python.flake8", | ||||||
|         "ms-python.black-formatter", |         "charliermarsh.ruff", | ||||||
|         "visualstudioexptteam.vscodeintellicode", |         "visualstudioexptteam.vscodeintellicode", | ||||||
|         // yaml |         // yaml | ||||||
|         "redhat.vscode-yaml", |         "redhat.vscode-yaml", | ||||||
| @@ -49,14 +49,11 @@ | |||||||
|         "flake8.args": [ |         "flake8.args": [ | ||||||
|           "--config=${workspaceFolder}/.flake8" |           "--config=${workspaceFolder}/.flake8" | ||||||
|         ], |         ], | ||||||
|         "black-formatter.args": [ |         "ruff.configuration": "${workspaceFolder}/pyproject.toml", | ||||||
|           "--config", |  | ||||||
|           "${workspaceFolder}/pyproject.toml" |  | ||||||
|         ], |  | ||||||
|         "[python]": { |         "[python]": { | ||||||
|           // VS will say "Value is not accepted" before building the devcontainer, but the warning |           // VS will say "Value is not accepted" before building the devcontainer, but the warning | ||||||
|           // should go away after build is completed. |           // should go away after build is completed. | ||||||
|           "editor.defaultFormatter": "ms-python.black-formatter" |           "editor.defaultFormatter": "charliermarsh.ruff" | ||||||
|         }, |         }, | ||||||
|         "editor.formatOnPaste": false, |         "editor.formatOnPaste": false, | ||||||
|         "editor.formatOnSave": true, |         "editor.formatOnSave": true, | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -46,7 +46,7 @@ runs: | |||||||
|  |  | ||||||
|     - name: Build and push to ghcr by digest |     - name: Build and push to ghcr by digest | ||||||
|       id: build-ghcr |       id: build-ghcr | ||||||
|       uses: docker/build-push-action@v6.13.0 |       uses: docker/build-push-action@v6.15.0 | ||||||
|       env: |       env: | ||||||
|         DOCKER_BUILD_SUMMARY: false |         DOCKER_BUILD_SUMMARY: false | ||||||
|         DOCKER_BUILD_RECORD_UPLOAD: false |         DOCKER_BUILD_RECORD_UPLOAD: false | ||||||
| @@ -72,7 +72,7 @@ runs: | |||||||
|  |  | ||||||
|     - name: Build and push to dockerhub by digest |     - name: Build and push to dockerhub by digest | ||||||
|       id: build-dockerhub |       id: build-dockerhub | ||||||
|       uses: docker/build-push-action@v6.13.0 |       uses: docker/build-push-action@v6.15.0 | ||||||
|       env: |       env: | ||||||
|         DOCKER_BUILD_SUMMARY: false |         DOCKER_BUILD_SUMMARY: false | ||||||
|         DOCKER_BUILD_RECORD_UPLOAD: false |         DOCKER_BUILD_RECORD_UPLOAD: false | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							| @@ -22,7 +22,7 @@ runs: | |||||||
|         python-version: ${{ inputs.python-version }} |         python-version: ${{ inputs.python-version }} | ||||||
|     - name: Restore Python virtual environment |     - name: Restore Python virtual environment | ||||||
|       id: cache-venv |       id: cache-venv | ||||||
|       uses: actions/cache/restore@v4.2.0 |       uses: actions/cache/restore@v4.2.2 | ||||||
|       with: |       with: | ||||||
|         path: venv |         path: venv | ||||||
|         # yamllint disable-line rule:line-length |         # yamllint disable-line rule:line-length | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -33,11 +33,11 @@ concurrency: | |||||||
| jobs: | jobs: | ||||||
|   check-docker: |   check-docker: | ||||||
|     name: Build docker containers |     name: Build docker containers | ||||||
|     runs-on: ubuntu-latest |     runs-on: ${{ matrix.os }} | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         arch: [amd64, aarch64] |         os: ["ubuntu-latest", "ubuntu-24.04-arm"] | ||||||
|         build_type: ["ha-addon", "docker", "lint"] |         build_type: ["ha-addon", "docker", "lint"] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4.1.7 |       - uses: actions/checkout@v4.1.7 | ||||||
| @@ -46,9 +46,7 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           python-version: "3.9" |           python-version: "3.9" | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.9.0 |         uses: docker/setup-buildx-action@v3.10.0 | ||||||
|       - name: Set up QEMU |  | ||||||
|         uses: docker/setup-qemu-action@v3.4.0 |  | ||||||
|  |  | ||||||
|       - name: Set TAG |       - name: Set TAG | ||||||
|         run: | |         run: | | ||||||
| @@ -58,6 +56,6 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           docker/build.py \ |           docker/build.py \ | ||||||
|             --tag "${TAG}" \ |             --tag "${TAG}" \ | ||||||
|             --arch "${{ matrix.arch }}" \ |             --arch "${{ matrix.os == 'ubuntu-24.04-arm' && 'aarch64' || 'amd64' }}" \ | ||||||
|             --build-type "${{ matrix.build_type }}" \ |             --build-type "${{ matrix.build_type }}" \ | ||||||
|             build |             build | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -47,7 +47,7 @@ jobs: | |||||||
|           python-version: ${{ env.DEFAULT_PYTHON }} |           python-version: ${{ env.DEFAULT_PYTHON }} | ||||||
|       - name: Restore Python virtual environment |       - name: Restore Python virtual environment | ||||||
|         id: cache-venv |         id: cache-venv | ||||||
|         uses: actions/cache@v4.2.0 |         uses: actions/cache@v4.2.2 | ||||||
|         with: |         with: | ||||||
|           path: venv |           path: venv | ||||||
|           # yamllint disable-line rule:line-length |           # yamllint disable-line rule:line-length | ||||||
| @@ -61,8 +61,8 @@ jobs: | |||||||
|           pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt |           pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt | ||||||
|           pip install -e . |           pip install -e . | ||||||
|  |  | ||||||
|   black: |   ruff: | ||||||
|     name: Check black |     name: Check ruff | ||||||
|     runs-on: ubuntu-24.04 |     runs-on: ubuntu-24.04 | ||||||
|     needs: |     needs: | ||||||
|       - common |       - common | ||||||
| @@ -74,10 +74,10 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           python-version: ${{ env.DEFAULT_PYTHON }} |           python-version: ${{ env.DEFAULT_PYTHON }} | ||||||
|           cache-key: ${{ needs.common.outputs.cache-key }} |           cache-key: ${{ needs.common.outputs.cache-key }} | ||||||
|       - name: Run black |       - name: Run Ruff | ||||||
|         run: | |         run: | | ||||||
|           . venv/bin/activate |           . venv/bin/activate | ||||||
|           black --verbose esphome tests |           ruff format esphome tests | ||||||
|       - name: Suggested changes |       - name: Suggested changes | ||||||
|         run: script/ci-suggest-changes |         run: script/ci-suggest-changes | ||||||
|         if: always() |         if: always() | ||||||
| @@ -255,7 +255,7 @@ jobs: | |||||||
|     runs-on: ubuntu-24.04 |     runs-on: ubuntu-24.04 | ||||||
|     needs: |     needs: | ||||||
|       - common |       - common | ||||||
|       - black |       - ruff | ||||||
|       - ci-custom |       - ci-custom | ||||||
|       - clang-format |       - clang-format | ||||||
|       - flake8 |       - flake8 | ||||||
| @@ -303,14 +303,14 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Cache platformio |       - name: Cache platformio | ||||||
|         if: github.ref == 'refs/heads/dev' |         if: github.ref == 'refs/heads/dev' | ||||||
|         uses: actions/cache@v4.2.0 |         uses: actions/cache@v4.2.2 | ||||||
|         with: |         with: | ||||||
|           path: ~/.platformio |           path: ~/.platformio | ||||||
|           key: platformio-${{ matrix.pio_cache_key }} |           key: platformio-${{ matrix.pio_cache_key }} | ||||||
|  |  | ||||||
|       - name: Cache platformio |       - name: Cache platformio | ||||||
|         if: github.ref != 'refs/heads/dev' |         if: github.ref != 'refs/heads/dev' | ||||||
|         uses: actions/cache/restore@v4.2.0 |         uses: actions/cache/restore@v4.2.2 | ||||||
|         with: |         with: | ||||||
|           path: ~/.platformio |           path: ~/.platformio | ||||||
|           key: platformio-${{ matrix.pio_cache_key }} |           key: platformio-${{ matrix.pio_cache_key }} | ||||||
| @@ -482,7 +482,7 @@ jobs: | |||||||
|     runs-on: ubuntu-24.04 |     runs-on: ubuntu-24.04 | ||||||
|     needs: |     needs: | ||||||
|       - common |       - common | ||||||
|       - black |       - ruff | ||||||
|       - ci-custom |       - ci-custom | ||||||
|       - clang-format |       - clang-format | ||||||
|       - flake8 |       - flake8 | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,11 +1,11 @@ | |||||||
| { | { | ||||||
|   "problemMatcher": [ |   "problemMatcher": [ | ||||||
|     { |     { | ||||||
|       "owner": "black", |       "owner": "ruff", | ||||||
|       "severity": "error", |       "severity": "error", | ||||||
|       "pattern": [ |       "pattern": [ | ||||||
|         { |         { | ||||||
|           "regexp": "^(.*): (Please format this file with the black formatter)", |           "regexp": "^(.*): (Please format this file with the ruff formatter)", | ||||||
|           "file": 1, |           "file": 1, | ||||||
|           "message": 2 |           "message": 2 | ||||||
|         } |         } | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -89,10 +89,10 @@ jobs: | |||||||
|           python-version: "3.9" |           python-version: "3.9" | ||||||
|  |  | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.9.0 |         uses: docker/setup-buildx-action@v3.10.0 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
|         if: matrix.platform != 'linux/amd64' |         if: matrix.platform != 'linux/amd64' | ||||||
|         uses: docker/setup-qemu-action@v3.4.0 |         uses: docker/setup-qemu-action@v3.6.0 | ||||||
|  |  | ||||||
|       - name: Log in to docker hub |       - name: Log in to docker hub | ||||||
|         uses: docker/login-action@v3.3.0 |         uses: docker/login-action@v3.3.0 | ||||||
| @@ -140,7 +140,7 @@ jobs: | |||||||
|           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT |           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT | ||||||
|  |  | ||||||
|       - name: Upload digests |       - name: Upload digests | ||||||
|         uses: actions/upload-artifact@v4.6.0 |         uses: actions/upload-artifact@v4.6.1 | ||||||
|         with: |         with: | ||||||
|           name: digests-${{ steps.sanitize.outputs.name }} |           name: digests-${{ steps.sanitize.outputs.name }} | ||||||
|           path: /tmp/digests |           path: /tmp/digests | ||||||
| @@ -176,14 +176,14 @@ jobs: | |||||||
|       - uses: actions/checkout@v4.1.7 |       - uses: actions/checkout@v4.1.7 | ||||||
|  |  | ||||||
|       - name: Download digests |       - name: Download digests | ||||||
|         uses: actions/download-artifact@v4.1.8 |         uses: actions/download-artifact@v4.1.9 | ||||||
|         with: |         with: | ||||||
|           pattern: digests-* |           pattern: digests-* | ||||||
|           path: /tmp/digests |           path: /tmp/digests | ||||||
|           merge-multiple: true |           merge-multiple: true | ||||||
|  |  | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.9.0 |         uses: docker/setup-buildx-action@v3.10.0 | ||||||
|  |  | ||||||
|       - name: Log in to docker hub |       - name: Log in to docker hub | ||||||
|         if: matrix.registry == 'dockerhub' |         if: matrix.registry == 'dockerhub' | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							| @@ -36,7 +36,7 @@ jobs: | |||||||
|           python ./script/sync-device_class.py |           python ./script/sync-device_class.py | ||||||
|  |  | ||||||
|       - name: Commit changes |       - name: Commit changes | ||||||
|         uses: peter-evans/create-pull-request@v7.0.6 |         uses: peter-evans/create-pull-request@v7.0.7 | ||||||
|         with: |         with: | ||||||
|           commit-message: "Synchronise Device Classes from Home Assistant" |           commit-message: "Synchronise Device Classes from Home Assistant" | ||||||
|           committer: esphomebot <esphome@nabucasa.com> |           committer: esphomebot <esphome@nabucasa.com> | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/astral-sh/ruff-pre-commit |   - repo: https://github.com/astral-sh/ruff-pre-commit | ||||||
|     # Ruff version. |     # Ruff version. | ||||||
|     rev: v0.5.4 |     rev: v0.9.2 | ||||||
|     hooks: |     hooks: | ||||||
|       # Run the linter. |       # Run the linter. | ||||||
|       - id: ruff |       - id: ruff | ||||||
|   | |||||||
| @@ -93,6 +93,7 @@ esphome/components/captive_portal/* @OttoWinter | |||||||
| esphome/components/ccs811/* @habbie | esphome/components/ccs811/* @habbie | ||||||
| esphome/components/cd74hc4067/* @asoehlke | esphome/components/cd74hc4067/* @asoehlke | ||||||
| esphome/components/ch422g/* @clydebarrow @jesterret | esphome/components/ch422g/* @clydebarrow @jesterret | ||||||
|  | esphome/components/chsc6x/* @kkosik20 | ||||||
| esphome/components/climate/* @esphome/core | esphome/components/climate/* @esphome/core | ||||||
| esphome/components/climate_ir/* @glmnet | esphome/components/climate_ir/* @glmnet | ||||||
| esphome/components/color_temperature/* @jesserockz | esphome/components/color_temperature/* @jesserockz | ||||||
| @@ -234,6 +235,7 @@ esphome/components/kuntze/* @ssieb | |||||||
| esphome/components/lcd_menu/* @numo68 | esphome/components/lcd_menu/* @numo68 | ||||||
| esphome/components/ld2410/* @regevbr @sebcaps | esphome/components/ld2410/* @regevbr @sebcaps | ||||||
| esphome/components/ld2420/* @descipher | esphome/components/ld2420/* @descipher | ||||||
|  | esphome/components/ld2450/* @hareeshmu | ||||||
| esphome/components/ledc/* @OttoWinter | esphome/components/ledc/* @OttoWinter | ||||||
| esphome/components/libretiny/* @kuba2k2 | esphome/components/libretiny/* @kuba2k2 | ||||||
| esphome/components/libretiny_pwm/* @kuba2k2 | esphome/components/libretiny_pwm/* @kuba2k2 | ||||||
| @@ -297,6 +299,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt | |||||||
| esphome/components/mpl3115a2/* @kbickar | esphome/components/mpl3115a2/* @kbickar | ||||||
| esphome/components/mpu6886/* @fabaff | esphome/components/mpu6886/* @fabaff | ||||||
| esphome/components/ms8607/* @e28eta | esphome/components/ms8607/* @e28eta | ||||||
|  | esphome/components/msa3xx/* @latonita | ||||||
| esphome/components/nau7802/* @cujomalainey | esphome/components/nau7802/* @cujomalainey | ||||||
| esphome/components/network/* @esphome/core | esphome/components/network/* @esphome/core | ||||||
| esphome/components/nextion/* @edwardtfn @senexcrenshaw | esphome/components/nextion/* @edwardtfn @senexcrenshaw | ||||||
| @@ -445,6 +448,7 @@ esphome/components/tmp102/* @timsavage | |||||||
| esphome/components/tmp1075/* @sybrenstuvel | esphome/components/tmp1075/* @sybrenstuvel | ||||||
| esphome/components/tmp117/* @Azimath | esphome/components/tmp117/* @Azimath | ||||||
| esphome/components/tof10120/* @wstrzalka | esphome/components/tof10120/* @wstrzalka | ||||||
|  | esphome/components/tormatic/* @ti-mo | ||||||
| esphome/components/toshiba/* @kbx81 | esphome/components/toshiba/* @kbx81 | ||||||
| esphome/components/touchscreen/* @jesserockz @nielsnl68 | esphome/components/touchscreen/* @jesserockz @nielsnl68 | ||||||
| esphome/components/tsl2591/* @wjcarpenter | esphome/components/tsl2591/* @wjcarpenter | ||||||
|   | |||||||
| @@ -23,10 +23,6 @@ if bashio::config.true 'streamer_mode'; then | |||||||
|     export ESPHOME_STREAMER_MODE=true |     export ESPHOME_STREAMER_MODE=true | ||||||
| fi | fi | ||||||
|  |  | ||||||
| if bashio::config.true 'status_use_ping'; then |  | ||||||
|     export ESPHOME_DASHBOARD_USE_PING=true |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| if bashio::config.has_value 'relative_url'; then | if bashio::config.has_value 'relative_url'; then | ||||||
|     export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') |     export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') | ||||||
| fi | fi | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| import argparse | import argparse | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| import functools | import functools | ||||||
|  | import importlib | ||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
| @@ -66,7 +67,7 @@ def choose_prompt(options, purpose: str = None): | |||||||
|         return options[0][1] |         return options[0][1] | ||||||
|  |  | ||||||
|     safe_print( |     safe_print( | ||||||
|         f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:' |         f"Found multiple options{f' for {purpose}' if purpose else ''}, please choose one:" | ||||||
|     ) |     ) | ||||||
|     for i, (desc, _) in enumerate(options): |     for i, (desc, _) in enumerate(options): | ||||||
|         safe_print(f"  [{i + 1}] {desc}") |         safe_print(f"  [{i + 1}] {desc}") | ||||||
| @@ -336,6 +337,13 @@ def check_permissions(port): | |||||||
|  |  | ||||||
|  |  | ||||||
| def upload_program(config, args, host): | def upload_program(config, args, host): | ||||||
|  |     try: | ||||||
|  |         module = importlib.import_module("esphome.components." + CORE.target_platform) | ||||||
|  |         if getattr(module, "upload_program")(config, args, host): | ||||||
|  |             return 0 | ||||||
|  |     except AttributeError: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|     if get_port_type(host) == "SERIAL": |     if get_port_type(host) == "SERIAL": | ||||||
|         check_permissions(host) |         check_permissions(host) | ||||||
|         if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): |         if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): | ||||||
|   | |||||||
| @@ -227,6 +227,9 @@ message DeviceInfoResponse { | |||||||
|   uint32 voice_assistant_feature_flags = 17; |   uint32 voice_assistant_feature_flags = 17; | ||||||
|  |  | ||||||
|   string suggested_area = 16; |   string suggested_area = 16; | ||||||
|  |  | ||||||
|  |   // The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA" | ||||||
|  |   string bluetooth_mac_address = 18; | ||||||
| } | } | ||||||
|  |  | ||||||
| message ListEntitiesRequest { | message ListEntitiesRequest { | ||||||
|   | |||||||
| @@ -28,8 +28,38 @@ namespace api { | |||||||
| static const char *const TAG = "api.connection"; | static const char *const TAG = "api.connection"; | ||||||
| static const int ESP32_CAMERA_STOP_STREAM = 5000; | static const int ESP32_CAMERA_STOP_STREAM = 5000; | ||||||
|  |  | ||||||
|  | // helper for allowing only unique entries in the queue | ||||||
|  | void DeferredMessageQueue::dmq_push_back_with_dedup_(void *source, send_message_t *send_message) { | ||||||
|  |   DeferredMessage item(source, send_message); | ||||||
|  |  | ||||||
|  |   auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(), | ||||||
|  |                            [&item](const DeferredMessage &test) -> bool { return test == item; }); | ||||||
|  |  | ||||||
|  |   if (iter != this->deferred_queue_.end()) { | ||||||
|  |     (*iter) = item; | ||||||
|  |   } else { | ||||||
|  |     this->deferred_queue_.push_back(item); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DeferredMessageQueue::process_queue() { | ||||||
|  |   while (!deferred_queue_.empty()) { | ||||||
|  |     DeferredMessage &de = deferred_queue_.front(); | ||||||
|  |     if (de.send_message_(this->api_connection_, de.source_)) { | ||||||
|  |       // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen | ||||||
|  |       deferred_queue_.erase(deferred_queue_.begin()); | ||||||
|  |     } else { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DeferredMessageQueue::defer(void *source, send_message_t *send_message) { | ||||||
|  |   this->dmq_push_back_with_dedup_(source, send_message); | ||||||
|  | } | ||||||
|  |  | ||||||
| APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | ||||||
|     : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { |     : parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) { | ||||||
|   this->proto_write_buffer_.reserve(64); |   this->proto_write_buffer_.reserve(64); | ||||||
|  |  | ||||||
| #if defined(USE_API_PLAINTEXT) | #if defined(USE_API_PLAINTEXT) | ||||||
| @@ -116,7 +146,11 @@ void APIConnection::loop() { | |||||||
|       return; |       return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   this->deferred_message_queue_.process_queue(); | ||||||
|  |  | ||||||
|  |   if (!this->list_entities_iterator_.completed()) | ||||||
|     this->list_entities_iterator_.advance(); |     this->list_entities_iterator_.advance(); | ||||||
|  |   if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed()) | ||||||
|     this->initial_state_iterator_.advance(); |     this->initial_state_iterator_.advance(); | ||||||
|  |  | ||||||
|   static uint32_t keepalive = 60000; |   static uint32_t keepalive = 60000; | ||||||
| @@ -210,13 +244,31 @@ bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_binary_sensor_state(this, binary_sensor, state)) { | ||||||
|  |     this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) { | ||||||
|  |   if (!APIConnection::try_send_binary_sensor_info(this, binary_sensor)) { | ||||||
|  |     this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor) { | ||||||
|  |   binary_sensor::BinarySensor *binary_sensor = reinterpret_cast<binary_sensor::BinarySensor *>(v_binary_sensor); | ||||||
|  |   return APIConnection::try_send_binary_sensor_state(api, binary_sensor, binary_sensor->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, | ||||||
|  |                                                  bool state) { | ||||||
|   BinarySensorStateResponse resp; |   BinarySensorStateResponse resp; | ||||||
|   resp.key = binary_sensor->get_object_id_hash(); |   resp.key = binary_sensor->get_object_id_hash(); | ||||||
|   resp.state = state; |   resp.state = state; | ||||||
|   resp.missing_state = !binary_sensor->has_state(); |   resp.missing_state = !binary_sensor->has_state(); | ||||||
|   return this->send_binary_sensor_state_response(resp); |   return api->send_binary_sensor_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) { | bool APIConnection::try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor) { | ||||||
|  |   binary_sensor::BinarySensor *binary_sensor = reinterpret_cast<binary_sensor::BinarySensor *>(v_binary_sensor); | ||||||
|   ListEntitiesBinarySensorResponse msg; |   ListEntitiesBinarySensorResponse msg; | ||||||
|   msg.object_id = binary_sensor->get_object_id(); |   msg.object_id = binary_sensor->get_object_id(); | ||||||
|   msg.key = binary_sensor->get_object_id_hash(); |   msg.key = binary_sensor->get_object_id_hash(); | ||||||
| @@ -228,7 +280,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ | |||||||
|   msg.disabled_by_default = binary_sensor->is_disabled_by_default(); |   msg.disabled_by_default = binary_sensor->is_disabled_by_default(); | ||||||
|   msg.icon = binary_sensor->get_icon(); |   msg.icon = binary_sensor->get_icon(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category()); | ||||||
|   return this->send_list_entities_binary_sensor_response(msg); |   return api->send_list_entities_binary_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -237,6 +289,19 @@ bool APIConnection::send_cover_state(cover::Cover *cover) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_cover_state(this, cover)) { | ||||||
|  |     this->deferred_message_queue_.defer(cover, try_send_cover_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_cover_info(cover::Cover *cover) { | ||||||
|  |   if (!APIConnection::try_send_cover_info(this, cover)) { | ||||||
|  |     this->deferred_message_queue_.defer(cover, try_send_cover_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_cover_state(APIConnection *api, void *v_cover) { | ||||||
|  |   cover::Cover *cover = reinterpret_cast<cover::Cover *>(v_cover); | ||||||
|   auto traits = cover->get_traits(); |   auto traits = cover->get_traits(); | ||||||
|   CoverStateResponse resp{}; |   CoverStateResponse resp{}; | ||||||
|   resp.key = cover->get_object_id_hash(); |   resp.key = cover->get_object_id_hash(); | ||||||
| @@ -246,9 +311,10 @@ bool APIConnection::send_cover_state(cover::Cover *cover) { | |||||||
|   if (traits.get_supports_tilt()) |   if (traits.get_supports_tilt()) | ||||||
|     resp.tilt = cover->tilt; |     resp.tilt = cover->tilt; | ||||||
|   resp.current_operation = static_cast<enums::CoverOperation>(cover->current_operation); |   resp.current_operation = static_cast<enums::CoverOperation>(cover->current_operation); | ||||||
|   return this->send_cover_state_response(resp); |   return api->send_cover_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_cover_info(cover::Cover *cover) { | bool APIConnection::try_send_cover_info(APIConnection *api, void *v_cover) { | ||||||
|  |   cover::Cover *cover = reinterpret_cast<cover::Cover *>(v_cover); | ||||||
|   auto traits = cover->get_traits(); |   auto traits = cover->get_traits(); | ||||||
|   ListEntitiesCoverResponse msg; |   ListEntitiesCoverResponse msg; | ||||||
|   msg.key = cover->get_object_id_hash(); |   msg.key = cover->get_object_id_hash(); | ||||||
| @@ -264,7 +330,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { | |||||||
|   msg.disabled_by_default = cover->is_disabled_by_default(); |   msg.disabled_by_default = cover->is_disabled_by_default(); | ||||||
|   msg.icon = cover->get_icon(); |   msg.icon = cover->get_icon(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category()); | ||||||
|   return this->send_list_entities_cover_response(msg); |   return api->send_list_entities_cover_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::cover_command(const CoverCommandRequest &msg) { | void APIConnection::cover_command(const CoverCommandRequest &msg) { | ||||||
|   cover::Cover *cover = App.get_cover_by_key(msg.key); |   cover::Cover *cover = App.get_cover_by_key(msg.key); | ||||||
| @@ -300,6 +366,19 @@ bool APIConnection::send_fan_state(fan::Fan *fan) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_fan_state(this, fan)) { | ||||||
|  |     this->deferred_message_queue_.defer(fan, try_send_fan_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_fan_info(fan::Fan *fan) { | ||||||
|  |   if (!APIConnection::try_send_fan_info(this, fan)) { | ||||||
|  |     this->deferred_message_queue_.defer(fan, try_send_fan_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_fan_state(APIConnection *api, void *v_fan) { | ||||||
|  |   fan::Fan *fan = reinterpret_cast<fan::Fan *>(v_fan); | ||||||
|   auto traits = fan->get_traits(); |   auto traits = fan->get_traits(); | ||||||
|   FanStateResponse resp{}; |   FanStateResponse resp{}; | ||||||
|   resp.key = fan->get_object_id_hash(); |   resp.key = fan->get_object_id_hash(); | ||||||
| @@ -313,9 +392,10 @@ bool APIConnection::send_fan_state(fan::Fan *fan) { | |||||||
|     resp.direction = static_cast<enums::FanDirection>(fan->direction); |     resp.direction = static_cast<enums::FanDirection>(fan->direction); | ||||||
|   if (traits.supports_preset_modes()) |   if (traits.supports_preset_modes()) | ||||||
|     resp.preset_mode = fan->preset_mode; |     resp.preset_mode = fan->preset_mode; | ||||||
|   return this->send_fan_state_response(resp); |   return api->send_fan_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_fan_info(fan::Fan *fan) { | bool APIConnection::try_send_fan_info(APIConnection *api, void *v_fan) { | ||||||
|  |   fan::Fan *fan = reinterpret_cast<fan::Fan *>(v_fan); | ||||||
|   auto traits = fan->get_traits(); |   auto traits = fan->get_traits(); | ||||||
|   ListEntitiesFanResponse msg; |   ListEntitiesFanResponse msg; | ||||||
|   msg.key = fan->get_object_id_hash(); |   msg.key = fan->get_object_id_hash(); | ||||||
| @@ -332,7 +412,7 @@ bool APIConnection::send_fan_info(fan::Fan *fan) { | |||||||
|   msg.disabled_by_default = fan->is_disabled_by_default(); |   msg.disabled_by_default = fan->is_disabled_by_default(); | ||||||
|   msg.icon = fan->get_icon(); |   msg.icon = fan->get_icon(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category()); | ||||||
|   return this->send_list_entities_fan_response(msg); |   return api->send_list_entities_fan_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::fan_command(const FanCommandRequest &msg) { | void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||||
|   fan::Fan *fan = App.get_fan_by_key(msg.key); |   fan::Fan *fan = App.get_fan_by_key(msg.key); | ||||||
| @@ -361,6 +441,19 @@ bool APIConnection::send_light_state(light::LightState *light) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_light_state(this, light)) { | ||||||
|  |     this->deferred_message_queue_.defer(light, try_send_light_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_light_info(light::LightState *light) { | ||||||
|  |   if (!APIConnection::try_send_light_info(this, light)) { | ||||||
|  |     this->deferred_message_queue_.defer(light, try_send_light_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_light_state(APIConnection *api, void *v_light) { | ||||||
|  |   light::LightState *light = reinterpret_cast<light::LightState *>(v_light); | ||||||
|   auto traits = light->get_traits(); |   auto traits = light->get_traits(); | ||||||
|   auto values = light->remote_values; |   auto values = light->remote_values; | ||||||
|   auto color_mode = values.get_color_mode(); |   auto color_mode = values.get_color_mode(); | ||||||
| @@ -380,9 +473,10 @@ bool APIConnection::send_light_state(light::LightState *light) { | |||||||
|   resp.warm_white = values.get_warm_white(); |   resp.warm_white = values.get_warm_white(); | ||||||
|   if (light->supports_effects()) |   if (light->supports_effects()) | ||||||
|     resp.effect = light->get_effect_name(); |     resp.effect = light->get_effect_name(); | ||||||
|   return this->send_light_state_response(resp); |   return api->send_light_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_light_info(light::LightState *light) { | bool APIConnection::try_send_light_info(APIConnection *api, void *v_light) { | ||||||
|  |   light::LightState *light = reinterpret_cast<light::LightState *>(v_light); | ||||||
|   auto traits = light->get_traits(); |   auto traits = light->get_traits(); | ||||||
|   ListEntitiesLightResponse msg; |   ListEntitiesLightResponse msg; | ||||||
|   msg.key = light->get_object_id_hash(); |   msg.key = light->get_object_id_hash(); | ||||||
| @@ -415,7 +509,7 @@ bool APIConnection::send_light_info(light::LightState *light) { | |||||||
|     for (auto *effect : light->get_effects()) |     for (auto *effect : light->get_effects()) | ||||||
|       msg.effects.push_back(effect->get_name()); |       msg.effects.push_back(effect->get_name()); | ||||||
|   } |   } | ||||||
|   return this->send_list_entities_light_response(msg); |   return api->send_list_entities_light_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::light_command(const LightCommandRequest &msg) { | void APIConnection::light_command(const LightCommandRequest &msg) { | ||||||
|   light::LightState *light = App.get_light_by_key(msg.key); |   light::LightState *light = App.get_light_by_key(msg.key); | ||||||
| @@ -459,13 +553,30 @@ bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_sensor_state(this, sensor, state)) { | ||||||
|  |     this->deferred_message_queue_.defer(sensor, try_send_sensor_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_sensor_info(sensor::Sensor *sensor) { | ||||||
|  |   if (!APIConnection::try_send_sensor_info(this, sensor)) { | ||||||
|  |     this->deferred_message_queue_.defer(sensor, try_send_sensor_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_sensor_state(APIConnection *api, void *v_sensor) { | ||||||
|  |   sensor::Sensor *sensor = reinterpret_cast<sensor::Sensor *>(v_sensor); | ||||||
|  |   return APIConnection::try_send_sensor_state(api, sensor, sensor->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state) { | ||||||
|   SensorStateResponse resp{}; |   SensorStateResponse resp{}; | ||||||
|   resp.key = sensor->get_object_id_hash(); |   resp.key = sensor->get_object_id_hash(); | ||||||
|   resp.state = state; |   resp.state = state; | ||||||
|   resp.missing_state = !sensor->has_state(); |   resp.missing_state = !sensor->has_state(); | ||||||
|   return this->send_sensor_state_response(resp); |   return api->send_sensor_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { | bool APIConnection::try_send_sensor_info(APIConnection *api, void *v_sensor) { | ||||||
|  |   sensor::Sensor *sensor = reinterpret_cast<sensor::Sensor *>(v_sensor); | ||||||
|   ListEntitiesSensorResponse msg; |   ListEntitiesSensorResponse msg; | ||||||
|   msg.key = sensor->get_object_id_hash(); |   msg.key = sensor->get_object_id_hash(); | ||||||
|   msg.object_id = sensor->get_object_id(); |   msg.object_id = sensor->get_object_id(); | ||||||
| @@ -482,7 +593,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { | |||||||
|   msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); |   msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); | ||||||
|   msg.disabled_by_default = sensor->is_disabled_by_default(); |   msg.disabled_by_default = sensor->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category()); | ||||||
|   return this->send_list_entities_sensor_response(msg); |   return api->send_list_entities_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -491,12 +602,29 @@ bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_switch_state(this, a_switch, state)) { | ||||||
|  |     this->deferred_message_queue_.defer(a_switch, try_send_switch_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_switch_info(switch_::Switch *a_switch) { | ||||||
|  |   if (!APIConnection::try_send_switch_info(this, a_switch)) { | ||||||
|  |     this->deferred_message_queue_.defer(a_switch, try_send_switch_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_switch_state(APIConnection *api, void *v_a_switch) { | ||||||
|  |   switch_::Switch *a_switch = reinterpret_cast<switch_::Switch *>(v_a_switch); | ||||||
|  |   return APIConnection::try_send_switch_state(api, a_switch, a_switch->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state) { | ||||||
|   SwitchStateResponse resp{}; |   SwitchStateResponse resp{}; | ||||||
|   resp.key = a_switch->get_object_id_hash(); |   resp.key = a_switch->get_object_id_hash(); | ||||||
|   resp.state = state; |   resp.state = state; | ||||||
|   return this->send_switch_state_response(resp); |   return api->send_switch_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_switch_info(switch_::Switch *a_switch) { | bool APIConnection::try_send_switch_info(APIConnection *api, void *v_a_switch) { | ||||||
|  |   switch_::Switch *a_switch = reinterpret_cast<switch_::Switch *>(v_a_switch); | ||||||
|   ListEntitiesSwitchResponse msg; |   ListEntitiesSwitchResponse msg; | ||||||
|   msg.key = a_switch->get_object_id_hash(); |   msg.key = a_switch->get_object_id_hash(); | ||||||
|   msg.object_id = a_switch->get_object_id(); |   msg.object_id = a_switch->get_object_id(); | ||||||
| @@ -508,7 +636,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { | |||||||
|   msg.disabled_by_default = a_switch->is_disabled_by_default(); |   msg.disabled_by_default = a_switch->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category()); | ||||||
|   msg.device_class = a_switch->get_device_class(); |   msg.device_class = a_switch->get_device_class(); | ||||||
|   return this->send_list_entities_switch_response(msg); |   return api->send_list_entities_switch_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::switch_command(const SwitchCommandRequest &msg) { | void APIConnection::switch_command(const SwitchCommandRequest &msg) { | ||||||
|   switch_::Switch *a_switch = App.get_switch_by_key(msg.key); |   switch_::Switch *a_switch = App.get_switch_by_key(msg.key); | ||||||
| @@ -528,13 +656,31 @@ bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_text_sensor_state(this, text_sensor, std::move(state))) { | ||||||
|  |     this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) { | ||||||
|  |   if (!APIConnection::try_send_text_sensor_info(this, text_sensor)) { | ||||||
|  |     this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_text_sensor_state(APIConnection *api, void *v_text_sensor) { | ||||||
|  |   text_sensor::TextSensor *text_sensor = reinterpret_cast<text_sensor::TextSensor *>(v_text_sensor); | ||||||
|  |   return APIConnection::try_send_text_sensor_state(api, text_sensor, text_sensor->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, | ||||||
|  |                                                std::string state) { | ||||||
|   TextSensorStateResponse resp{}; |   TextSensorStateResponse resp{}; | ||||||
|   resp.key = text_sensor->get_object_id_hash(); |   resp.key = text_sensor->get_object_id_hash(); | ||||||
|   resp.state = std::move(state); |   resp.state = std::move(state); | ||||||
|   resp.missing_state = !text_sensor->has_state(); |   resp.missing_state = !text_sensor->has_state(); | ||||||
|   return this->send_text_sensor_state_response(resp); |   return api->send_text_sensor_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) { | bool APIConnection::try_send_text_sensor_info(APIConnection *api, void *v_text_sensor) { | ||||||
|  |   text_sensor::TextSensor *text_sensor = reinterpret_cast<text_sensor::TextSensor *>(v_text_sensor); | ||||||
|   ListEntitiesTextSensorResponse msg; |   ListEntitiesTextSensorResponse msg; | ||||||
|   msg.key = text_sensor->get_object_id_hash(); |   msg.key = text_sensor->get_object_id_hash(); | ||||||
|   msg.object_id = text_sensor->get_object_id(); |   msg.object_id = text_sensor->get_object_id(); | ||||||
| @@ -546,7 +692,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) | |||||||
|   msg.disabled_by_default = text_sensor->is_disabled_by_default(); |   msg.disabled_by_default = text_sensor->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category()); | ||||||
|   msg.device_class = text_sensor->get_device_class(); |   msg.device_class = text_sensor->get_device_class(); | ||||||
|   return this->send_list_entities_text_sensor_response(msg); |   return api->send_list_entities_text_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -555,6 +701,19 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_climate_state(this, climate)) { | ||||||
|  |     this->deferred_message_queue_.defer(climate, try_send_climate_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_climate_info(climate::Climate *climate) { | ||||||
|  |   if (!APIConnection::try_send_climate_info(this, climate)) { | ||||||
|  |     this->deferred_message_queue_.defer(climate, try_send_climate_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_climate_state(APIConnection *api, void *v_climate) { | ||||||
|  |   climate::Climate *climate = reinterpret_cast<climate::Climate *>(v_climate); | ||||||
|   auto traits = climate->get_traits(); |   auto traits = climate->get_traits(); | ||||||
|   ClimateStateResponse resp{}; |   ClimateStateResponse resp{}; | ||||||
|   resp.key = climate->get_object_id_hash(); |   resp.key = climate->get_object_id_hash(); | ||||||
| @@ -583,9 +742,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { | |||||||
|     resp.current_humidity = climate->current_humidity; |     resp.current_humidity = climate->current_humidity; | ||||||
|   if (traits.get_supports_target_humidity()) |   if (traits.get_supports_target_humidity()) | ||||||
|     resp.target_humidity = climate->target_humidity; |     resp.target_humidity = climate->target_humidity; | ||||||
|   return this->send_climate_state_response(resp); |   return api->send_climate_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_climate_info(climate::Climate *climate) { | bool APIConnection::try_send_climate_info(APIConnection *api, void *v_climate) { | ||||||
|  |   climate::Climate *climate = reinterpret_cast<climate::Climate *>(v_climate); | ||||||
|   auto traits = climate->get_traits(); |   auto traits = climate->get_traits(); | ||||||
|   ListEntitiesClimateResponse msg; |   ListEntitiesClimateResponse msg; | ||||||
|   msg.key = climate->get_object_id_hash(); |   msg.key = climate->get_object_id_hash(); | ||||||
| @@ -626,7 +786,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | |||||||
|     msg.supported_custom_presets.push_back(custom_preset); |     msg.supported_custom_presets.push_back(custom_preset); | ||||||
|   for (auto swing_mode : traits.get_supported_swing_modes()) |   for (auto swing_mode : traits.get_supported_swing_modes()) | ||||||
|     msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode)); |     msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode)); | ||||||
|   return this->send_list_entities_climate_response(msg); |   return api->send_list_entities_climate_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::climate_command(const ClimateCommandRequest &msg) { | void APIConnection::climate_command(const ClimateCommandRequest &msg) { | ||||||
|   climate::Climate *climate = App.get_climate_by_key(msg.key); |   climate::Climate *climate = App.get_climate_by_key(msg.key); | ||||||
| @@ -663,13 +823,30 @@ bool APIConnection::send_number_state(number::Number *number, float state) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_number_state(this, number, state)) { | ||||||
|  |     this->deferred_message_queue_.defer(number, try_send_number_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_number_info(number::Number *number) { | ||||||
|  |   if (!APIConnection::try_send_number_info(this, number)) { | ||||||
|  |     this->deferred_message_queue_.defer(number, try_send_number_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_number_state(APIConnection *api, void *v_number) { | ||||||
|  |   number::Number *number = reinterpret_cast<number::Number *>(v_number); | ||||||
|  |   return APIConnection::try_send_number_state(api, number, number->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_number_state(APIConnection *api, number::Number *number, float state) { | ||||||
|   NumberStateResponse resp{}; |   NumberStateResponse resp{}; | ||||||
|   resp.key = number->get_object_id_hash(); |   resp.key = number->get_object_id_hash(); | ||||||
|   resp.state = state; |   resp.state = state; | ||||||
|   resp.missing_state = !number->has_state(); |   resp.missing_state = !number->has_state(); | ||||||
|   return this->send_number_state_response(resp); |   return api->send_number_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_number_info(number::Number *number) { | bool APIConnection::try_send_number_info(APIConnection *api, void *v_number) { | ||||||
|  |   number::Number *number = reinterpret_cast<number::Number *>(v_number); | ||||||
|   ListEntitiesNumberResponse msg; |   ListEntitiesNumberResponse msg; | ||||||
|   msg.key = number->get_object_id_hash(); |   msg.key = number->get_object_id_hash(); | ||||||
|   msg.object_id = number->get_object_id(); |   msg.object_id = number->get_object_id(); | ||||||
| @@ -687,7 +864,7 @@ bool APIConnection::send_number_info(number::Number *number) { | |||||||
|   msg.max_value = number->traits.get_max_value(); |   msg.max_value = number->traits.get_max_value(); | ||||||
|   msg.step = number->traits.get_step(); |   msg.step = number->traits.get_step(); | ||||||
|  |  | ||||||
|   return this->send_list_entities_number_response(msg); |   return api->send_list_entities_number_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::number_command(const NumberCommandRequest &msg) { | void APIConnection::number_command(const NumberCommandRequest &msg) { | ||||||
|   number::Number *number = App.get_number_by_key(msg.key); |   number::Number *number = App.get_number_by_key(msg.key); | ||||||
| @@ -705,15 +882,29 @@ bool APIConnection::send_date_state(datetime::DateEntity *date) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_date_state(this, date)) { | ||||||
|  |     this->deferred_message_queue_.defer(date, try_send_date_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_date_info(datetime::DateEntity *date) { | ||||||
|  |   if (!APIConnection::try_send_date_info(this, date)) { | ||||||
|  |     this->deferred_message_queue_.defer(date, try_send_date_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_date_state(APIConnection *api, void *v_date) { | ||||||
|  |   datetime::DateEntity *date = reinterpret_cast<datetime::DateEntity *>(v_date); | ||||||
|   DateStateResponse resp{}; |   DateStateResponse resp{}; | ||||||
|   resp.key = date->get_object_id_hash(); |   resp.key = date->get_object_id_hash(); | ||||||
|   resp.missing_state = !date->has_state(); |   resp.missing_state = !date->has_state(); | ||||||
|   resp.year = date->year; |   resp.year = date->year; | ||||||
|   resp.month = date->month; |   resp.month = date->month; | ||||||
|   resp.day = date->day; |   resp.day = date->day; | ||||||
|   return this->send_date_state_response(resp); |   return api->send_date_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_date_info(datetime::DateEntity *date) { | bool APIConnection::try_send_date_info(APIConnection *api, void *v_date) { | ||||||
|  |   datetime::DateEntity *date = reinterpret_cast<datetime::DateEntity *>(v_date); | ||||||
|   ListEntitiesDateResponse msg; |   ListEntitiesDateResponse msg; | ||||||
|   msg.key = date->get_object_id_hash(); |   msg.key = date->get_object_id_hash(); | ||||||
|   msg.object_id = date->get_object_id(); |   msg.object_id = date->get_object_id(); | ||||||
| @@ -724,7 +915,7 @@ bool APIConnection::send_date_info(datetime::DateEntity *date) { | |||||||
|   msg.disabled_by_default = date->is_disabled_by_default(); |   msg.disabled_by_default = date->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(date->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(date->get_entity_category()); | ||||||
|  |  | ||||||
|   return this->send_list_entities_date_response(msg); |   return api->send_list_entities_date_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::date_command(const DateCommandRequest &msg) { | void APIConnection::date_command(const DateCommandRequest &msg) { | ||||||
|   datetime::DateEntity *date = App.get_date_by_key(msg.key); |   datetime::DateEntity *date = App.get_date_by_key(msg.key); | ||||||
| @@ -742,15 +933,29 @@ bool APIConnection::send_time_state(datetime::TimeEntity *time) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_time_state(this, time)) { | ||||||
|  |     this->deferred_message_queue_.defer(time, try_send_time_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_time_info(datetime::TimeEntity *time) { | ||||||
|  |   if (!APIConnection::try_send_time_info(this, time)) { | ||||||
|  |     this->deferred_message_queue_.defer(time, try_send_time_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_time_state(APIConnection *api, void *v_time) { | ||||||
|  |   datetime::TimeEntity *time = reinterpret_cast<datetime::TimeEntity *>(v_time); | ||||||
|   TimeStateResponse resp{}; |   TimeStateResponse resp{}; | ||||||
|   resp.key = time->get_object_id_hash(); |   resp.key = time->get_object_id_hash(); | ||||||
|   resp.missing_state = !time->has_state(); |   resp.missing_state = !time->has_state(); | ||||||
|   resp.hour = time->hour; |   resp.hour = time->hour; | ||||||
|   resp.minute = time->minute; |   resp.minute = time->minute; | ||||||
|   resp.second = time->second; |   resp.second = time->second; | ||||||
|   return this->send_time_state_response(resp); |   return api->send_time_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_time_info(datetime::TimeEntity *time) { | bool APIConnection::try_send_time_info(APIConnection *api, void *v_time) { | ||||||
|  |   datetime::TimeEntity *time = reinterpret_cast<datetime::TimeEntity *>(v_time); | ||||||
|   ListEntitiesTimeResponse msg; |   ListEntitiesTimeResponse msg; | ||||||
|   msg.key = time->get_object_id_hash(); |   msg.key = time->get_object_id_hash(); | ||||||
|   msg.object_id = time->get_object_id(); |   msg.object_id = time->get_object_id(); | ||||||
| @@ -761,7 +966,7 @@ bool APIConnection::send_time_info(datetime::TimeEntity *time) { | |||||||
|   msg.disabled_by_default = time->is_disabled_by_default(); |   msg.disabled_by_default = time->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(time->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(time->get_entity_category()); | ||||||
|  |  | ||||||
|   return this->send_list_entities_time_response(msg); |   return api->send_list_entities_time_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::time_command(const TimeCommandRequest &msg) { | void APIConnection::time_command(const TimeCommandRequest &msg) { | ||||||
|   datetime::TimeEntity *time = App.get_time_by_key(msg.key); |   datetime::TimeEntity *time = App.get_time_by_key(msg.key); | ||||||
| @@ -779,6 +984,19 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_datetime_state(this, datetime)) { | ||||||
|  |     this->deferred_message_queue_.defer(datetime, try_send_datetime_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | ||||||
|  |   if (!APIConnection::try_send_datetime_info(this, datetime)) { | ||||||
|  |     this->deferred_message_queue_.defer(datetime, try_send_datetime_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_datetime_state(APIConnection *api, void *v_datetime) { | ||||||
|  |   datetime::DateTimeEntity *datetime = reinterpret_cast<datetime::DateTimeEntity *>(v_datetime); | ||||||
|   DateTimeStateResponse resp{}; |   DateTimeStateResponse resp{}; | ||||||
|   resp.key = datetime->get_object_id_hash(); |   resp.key = datetime->get_object_id_hash(); | ||||||
|   resp.missing_state = !datetime->has_state(); |   resp.missing_state = !datetime->has_state(); | ||||||
| @@ -786,9 +1004,10 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { | |||||||
|     ESPTime state = datetime->state_as_esptime(); |     ESPTime state = datetime->state_as_esptime(); | ||||||
|     resp.epoch_seconds = state.timestamp; |     resp.epoch_seconds = state.timestamp; | ||||||
|   } |   } | ||||||
|   return this->send_date_time_state_response(resp); |   return api->send_date_time_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | bool APIConnection::try_send_datetime_info(APIConnection *api, void *v_datetime) { | ||||||
|  |   datetime::DateTimeEntity *datetime = reinterpret_cast<datetime::DateTimeEntity *>(v_datetime); | ||||||
|   ListEntitiesDateTimeResponse msg; |   ListEntitiesDateTimeResponse msg; | ||||||
|   msg.key = datetime->get_object_id_hash(); |   msg.key = datetime->get_object_id_hash(); | ||||||
|   msg.object_id = datetime->get_object_id(); |   msg.object_id = datetime->get_object_id(); | ||||||
| @@ -799,7 +1018,7 @@ bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | |||||||
|   msg.disabled_by_default = datetime->is_disabled_by_default(); |   msg.disabled_by_default = datetime->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category()); | ||||||
|  |  | ||||||
|   return this->send_list_entities_date_time_response(msg); |   return api->send_list_entities_date_time_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { | void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { | ||||||
|   datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key); |   datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key); | ||||||
| @@ -817,13 +1036,30 @@ bool APIConnection::send_text_state(text::Text *text, std::string state) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_text_state(this, text, std::move(state))) { | ||||||
|  |     this->deferred_message_queue_.defer(text, try_send_text_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_text_info(text::Text *text) { | ||||||
|  |   if (!APIConnection::try_send_text_info(this, text)) { | ||||||
|  |     this->deferred_message_queue_.defer(text, try_send_text_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_text_state(APIConnection *api, void *v_text) { | ||||||
|  |   text::Text *text = reinterpret_cast<text::Text *>(v_text); | ||||||
|  |   return APIConnection::try_send_text_state(api, text, text->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_text_state(APIConnection *api, text::Text *text, std::string state) { | ||||||
|   TextStateResponse resp{}; |   TextStateResponse resp{}; | ||||||
|   resp.key = text->get_object_id_hash(); |   resp.key = text->get_object_id_hash(); | ||||||
|   resp.state = std::move(state); |   resp.state = std::move(state); | ||||||
|   resp.missing_state = !text->has_state(); |   resp.missing_state = !text->has_state(); | ||||||
|   return this->send_text_state_response(resp); |   return api->send_text_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_text_info(text::Text *text) { | bool APIConnection::try_send_text_info(APIConnection *api, void *v_text) { | ||||||
|  |   text::Text *text = reinterpret_cast<text::Text *>(v_text); | ||||||
|   ListEntitiesTextResponse msg; |   ListEntitiesTextResponse msg; | ||||||
|   msg.key = text->get_object_id_hash(); |   msg.key = text->get_object_id_hash(); | ||||||
|   msg.object_id = text->get_object_id(); |   msg.object_id = text->get_object_id(); | ||||||
| @@ -837,7 +1073,7 @@ bool APIConnection::send_text_info(text::Text *text) { | |||||||
|   msg.max_length = text->traits.get_max_length(); |   msg.max_length = text->traits.get_max_length(); | ||||||
|   msg.pattern = text->traits.get_pattern(); |   msg.pattern = text->traits.get_pattern(); | ||||||
|  |  | ||||||
|   return this->send_list_entities_text_response(msg); |   return api->send_list_entities_text_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::text_command(const TextCommandRequest &msg) { | void APIConnection::text_command(const TextCommandRequest &msg) { | ||||||
|   text::Text *text = App.get_text_by_key(msg.key); |   text::Text *text = App.get_text_by_key(msg.key); | ||||||
| @@ -855,13 +1091,30 @@ bool APIConnection::send_select_state(select::Select *select, std::string state) | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_select_state(this, select, std::move(state))) { | ||||||
|  |     this->deferred_message_queue_.defer(select, try_send_select_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_select_info(select::Select *select) { | ||||||
|  |   if (!APIConnection::try_send_select_info(this, select)) { | ||||||
|  |     this->deferred_message_queue_.defer(select, try_send_select_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_select_state(APIConnection *api, void *v_select) { | ||||||
|  |   select::Select *select = reinterpret_cast<select::Select *>(v_select); | ||||||
|  |   return APIConnection::try_send_select_state(api, select, select->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_select_state(APIConnection *api, select::Select *select, std::string state) { | ||||||
|   SelectStateResponse resp{}; |   SelectStateResponse resp{}; | ||||||
|   resp.key = select->get_object_id_hash(); |   resp.key = select->get_object_id_hash(); | ||||||
|   resp.state = std::move(state); |   resp.state = std::move(state); | ||||||
|   resp.missing_state = !select->has_state(); |   resp.missing_state = !select->has_state(); | ||||||
|   return this->send_select_state_response(resp); |   return api->send_select_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_select_info(select::Select *select) { | bool APIConnection::try_send_select_info(APIConnection *api, void *v_select) { | ||||||
|  |   select::Select *select = reinterpret_cast<select::Select *>(v_select); | ||||||
|   ListEntitiesSelectResponse msg; |   ListEntitiesSelectResponse msg; | ||||||
|   msg.key = select->get_object_id_hash(); |   msg.key = select->get_object_id_hash(); | ||||||
|   msg.object_id = select->get_object_id(); |   msg.object_id = select->get_object_id(); | ||||||
| @@ -875,7 +1128,7 @@ bool APIConnection::send_select_info(select::Select *select) { | |||||||
|   for (const auto &option : select->traits.get_options()) |   for (const auto &option : select->traits.get_options()) | ||||||
|     msg.options.push_back(option); |     msg.options.push_back(option); | ||||||
|  |  | ||||||
|   return this->send_list_entities_select_response(msg); |   return api->send_list_entities_select_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::select_command(const SelectCommandRequest &msg) { | void APIConnection::select_command(const SelectCommandRequest &msg) { | ||||||
|   select::Select *select = App.get_select_by_key(msg.key); |   select::Select *select = App.get_select_by_key(msg.key); | ||||||
| @@ -889,7 +1142,13 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_BUTTON | #ifdef USE_BUTTON | ||||||
| bool APIConnection::send_button_info(button::Button *button) { | void APIConnection::send_button_info(button::Button *button) { | ||||||
|  |   if (!APIConnection::try_send_button_info(this, button)) { | ||||||
|  |     this->deferred_message_queue_.defer(button, try_send_button_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_button_info(APIConnection *api, void *v_button) { | ||||||
|  |   button::Button *button = reinterpret_cast<button::Button *>(v_button); | ||||||
|   ListEntitiesButtonResponse msg; |   ListEntitiesButtonResponse msg; | ||||||
|   msg.key = button->get_object_id_hash(); |   msg.key = button->get_object_id_hash(); | ||||||
|   msg.object_id = button->get_object_id(); |   msg.object_id = button->get_object_id(); | ||||||
| @@ -900,7 +1159,7 @@ bool APIConnection::send_button_info(button::Button *button) { | |||||||
|   msg.disabled_by_default = button->is_disabled_by_default(); |   msg.disabled_by_default = button->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category()); | ||||||
|   msg.device_class = button->get_device_class(); |   msg.device_class = button->get_device_class(); | ||||||
|   return this->send_list_entities_button_response(msg); |   return api->send_list_entities_button_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::button_command(const ButtonCommandRequest &msg) { | void APIConnection::button_command(const ButtonCommandRequest &msg) { | ||||||
|   button::Button *button = App.get_button_by_key(msg.key); |   button::Button *button = App.get_button_by_key(msg.key); | ||||||
| @@ -916,12 +1175,29 @@ bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_lock_state(this, a_lock, state)) { | ||||||
|  |     this->deferred_message_queue_.defer(a_lock, try_send_lock_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_lock_info(lock::Lock *a_lock) { | ||||||
|  |   if (!APIConnection::try_send_lock_info(this, a_lock)) { | ||||||
|  |     this->deferred_message_queue_.defer(a_lock, try_send_lock_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_lock_state(APIConnection *api, void *v_a_lock) { | ||||||
|  |   lock::Lock *a_lock = reinterpret_cast<lock::Lock *>(v_a_lock); | ||||||
|  |   return APIConnection::try_send_lock_state(api, a_lock, a_lock->state); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state) { | ||||||
|   LockStateResponse resp{}; |   LockStateResponse resp{}; | ||||||
|   resp.key = a_lock->get_object_id_hash(); |   resp.key = a_lock->get_object_id_hash(); | ||||||
|   resp.state = static_cast<enums::LockState>(state); |   resp.state = static_cast<enums::LockState>(state); | ||||||
|   return this->send_lock_state_response(resp); |   return api->send_lock_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_lock_info(lock::Lock *a_lock) { | bool APIConnection::try_send_lock_info(APIConnection *api, void *v_a_lock) { | ||||||
|  |   lock::Lock *a_lock = reinterpret_cast<lock::Lock *>(v_a_lock); | ||||||
|   ListEntitiesLockResponse msg; |   ListEntitiesLockResponse msg; | ||||||
|   msg.key = a_lock->get_object_id_hash(); |   msg.key = a_lock->get_object_id_hash(); | ||||||
|   msg.object_id = a_lock->get_object_id(); |   msg.object_id = a_lock->get_object_id(); | ||||||
| @@ -934,7 +1210,7 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) { | |||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category()); | ||||||
|   msg.supports_open = a_lock->traits.get_supports_open(); |   msg.supports_open = a_lock->traits.get_supports_open(); | ||||||
|   msg.requires_code = a_lock->traits.get_requires_code(); |   msg.requires_code = a_lock->traits.get_requires_code(); | ||||||
|   return this->send_list_entities_lock_response(msg); |   return api->send_list_entities_lock_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::lock_command(const LockCommandRequest &msg) { | void APIConnection::lock_command(const LockCommandRequest &msg) { | ||||||
|   lock::Lock *a_lock = App.get_lock_by_key(msg.key); |   lock::Lock *a_lock = App.get_lock_by_key(msg.key); | ||||||
| @@ -960,13 +1236,27 @@ bool APIConnection::send_valve_state(valve::Valve *valve) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_valve_state(this, valve)) { | ||||||
|  |     this->deferred_message_queue_.defer(valve, try_send_valve_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_valve_info(valve::Valve *valve) { | ||||||
|  |   if (!APIConnection::try_send_valve_info(this, valve)) { | ||||||
|  |     this->deferred_message_queue_.defer(valve, try_send_valve_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_valve_state(APIConnection *api, void *v_valve) { | ||||||
|  |   valve::Valve *valve = reinterpret_cast<valve::Valve *>(v_valve); | ||||||
|   ValveStateResponse resp{}; |   ValveStateResponse resp{}; | ||||||
|   resp.key = valve->get_object_id_hash(); |   resp.key = valve->get_object_id_hash(); | ||||||
|   resp.position = valve->position; |   resp.position = valve->position; | ||||||
|   resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation); |   resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation); | ||||||
|   return this->send_valve_state_response(resp); |   return api->send_valve_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_valve_info(valve::Valve *valve) { | bool APIConnection::try_send_valve_info(APIConnection *api, void *v_valve) { | ||||||
|  |   valve::Valve *valve = reinterpret_cast<valve::Valve *>(v_valve); | ||||||
|   auto traits = valve->get_traits(); |   auto traits = valve->get_traits(); | ||||||
|   ListEntitiesValveResponse msg; |   ListEntitiesValveResponse msg; | ||||||
|   msg.key = valve->get_object_id_hash(); |   msg.key = valve->get_object_id_hash(); | ||||||
| @@ -981,7 +1271,7 @@ bool APIConnection::send_valve_info(valve::Valve *valve) { | |||||||
|   msg.assumed_state = traits.get_is_assumed_state(); |   msg.assumed_state = traits.get_is_assumed_state(); | ||||||
|   msg.supports_position = traits.get_supports_position(); |   msg.supports_position = traits.get_supports_position(); | ||||||
|   msg.supports_stop = traits.get_supports_stop(); |   msg.supports_stop = traits.get_supports_stop(); | ||||||
|   return this->send_list_entities_valve_response(msg); |   return api->send_list_entities_valve_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::valve_command(const ValveCommandRequest &msg) { | void APIConnection::valve_command(const ValveCommandRequest &msg) { | ||||||
|   valve::Valve *valve = App.get_valve_by_key(msg.key); |   valve::Valve *valve = App.get_valve_by_key(msg.key); | ||||||
| @@ -1002,6 +1292,19 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_media_player_state(this, media_player)) { | ||||||
|  |     this->deferred_message_queue_.defer(media_player, try_send_media_player_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { | ||||||
|  |   if (!APIConnection::try_send_media_player_info(this, media_player)) { | ||||||
|  |     this->deferred_message_queue_.defer(media_player, try_send_media_player_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_media_player_state(APIConnection *api, void *v_media_player) { | ||||||
|  |   media_player::MediaPlayer *media_player = reinterpret_cast<media_player::MediaPlayer *>(v_media_player); | ||||||
|   MediaPlayerStateResponse resp{}; |   MediaPlayerStateResponse resp{}; | ||||||
|   resp.key = media_player->get_object_id_hash(); |   resp.key = media_player->get_object_id_hash(); | ||||||
|  |  | ||||||
| @@ -1011,9 +1314,10 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla | |||||||
|   resp.state = static_cast<enums::MediaPlayerState>(report_state); |   resp.state = static_cast<enums::MediaPlayerState>(report_state); | ||||||
|   resp.volume = media_player->volume; |   resp.volume = media_player->volume; | ||||||
|   resp.muted = media_player->is_muted(); |   resp.muted = media_player->is_muted(); | ||||||
|   return this->send_media_player_state_response(resp); |   return api->send_media_player_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { | bool APIConnection::try_send_media_player_info(APIConnection *api, void *v_media_player) { | ||||||
|  |   media_player::MediaPlayer *media_player = reinterpret_cast<media_player::MediaPlayer *>(v_media_player); | ||||||
|   ListEntitiesMediaPlayerResponse msg; |   ListEntitiesMediaPlayerResponse msg; | ||||||
|   msg.key = media_player->get_object_id_hash(); |   msg.key = media_player->get_object_id_hash(); | ||||||
|   msg.object_id = media_player->get_object_id(); |   msg.object_id = media_player->get_object_id(); | ||||||
| @@ -1037,7 +1341,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play | |||||||
|     msg.supported_formats.push_back(media_format); |     msg.supported_formats.push_back(media_format); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return this->send_list_entities_media_player_response(msg); |   return api->send_list_entities_media_player_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | ||||||
|   media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key); |   media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key); | ||||||
| @@ -1062,7 +1366,7 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | ||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return; |     return; | ||||||
|   if (this->image_reader_.available()) |   if (this->image_reader_.available()) | ||||||
| @@ -1071,7 +1375,13 @@ void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> | |||||||
|       image->was_requested_by(esphome::esp32_camera::IDLE)) |       image->was_requested_by(esphome::esp32_camera::IDLE)) | ||||||
|     this->image_reader_.set_image(std::move(image)); |     this->image_reader_.set_image(std::move(image)); | ||||||
| } | } | ||||||
| bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | void APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | ||||||
|  |   if (!APIConnection::try_send_camera_info(this, camera)) { | ||||||
|  |     this->deferred_message_queue_.defer(camera, try_send_camera_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_camera_info(APIConnection *api, void *v_camera) { | ||||||
|  |   esp32_camera::ESP32Camera *camera = reinterpret_cast<esp32_camera::ESP32Camera *>(v_camera); | ||||||
|   ListEntitiesCameraResponse msg; |   ListEntitiesCameraResponse msg; | ||||||
|   msg.key = camera->get_object_id_hash(); |   msg.key = camera->get_object_id_hash(); | ||||||
|   msg.object_id = camera->get_object_id(); |   msg.object_id = camera->get_object_id(); | ||||||
| @@ -1081,7 +1391,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | |||||||
|   msg.disabled_by_default = camera->is_disabled_by_default(); |   msg.disabled_by_default = camera->is_disabled_by_default(); | ||||||
|   msg.icon = camera->get_icon(); |   msg.icon = camera->get_icon(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category()); | ||||||
|   return this->send_list_entities_camera_response(msg); |   return api->send_list_entities_camera_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::camera_image(const CameraImageRequest &msg) { | void APIConnection::camera_image(const CameraImageRequest &msg) { | ||||||
|   if (esp32_camera::global_esp32_camera == nullptr) |   if (esp32_camera::global_esp32_camera == nullptr) | ||||||
| @@ -1268,12 +1578,28 @@ bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmCon | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_alarm_control_panel_state(this, a_alarm_control_panel)) { | ||||||
|  |     this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||||
|  |   if (!APIConnection::try_send_alarm_control_panel_info(this, a_alarm_control_panel)) { | ||||||
|  |     this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel) { | ||||||
|  |   alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = | ||||||
|  |       reinterpret_cast<alarm_control_panel::AlarmControlPanel *>(v_a_alarm_control_panel); | ||||||
|   AlarmControlPanelStateResponse resp{}; |   AlarmControlPanelStateResponse resp{}; | ||||||
|   resp.key = a_alarm_control_panel->get_object_id_hash(); |   resp.key = a_alarm_control_panel->get_object_id_hash(); | ||||||
|   resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state()); |   resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state()); | ||||||
|   return this->send_alarm_control_panel_state_response(resp); |   return api->send_alarm_control_panel_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | bool APIConnection::try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel) { | ||||||
|  |   alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = | ||||||
|  |       reinterpret_cast<alarm_control_panel::AlarmControlPanel *>(v_a_alarm_control_panel); | ||||||
|   ListEntitiesAlarmControlPanelResponse msg; |   ListEntitiesAlarmControlPanelResponse msg; | ||||||
|   msg.key = a_alarm_control_panel->get_object_id_hash(); |   msg.key = a_alarm_control_panel->get_object_id_hash(); | ||||||
|   msg.object_id = a_alarm_control_panel->get_object_id(); |   msg.object_id = a_alarm_control_panel->get_object_id(); | ||||||
| @@ -1285,7 +1611,7 @@ bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmCont | |||||||
|   msg.supported_features = a_alarm_control_panel->get_supported_features(); |   msg.supported_features = a_alarm_control_panel->get_supported_features(); | ||||||
|   msg.requires_code = a_alarm_control_panel->get_requires_code(); |   msg.requires_code = a_alarm_control_panel->get_requires_code(); | ||||||
|   msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm(); |   msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm(); | ||||||
|   return this->send_list_entities_alarm_control_panel_response(msg); |   return api->send_list_entities_alarm_control_panel_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { | void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { | ||||||
|   alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key); |   alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key); | ||||||
| @@ -1322,13 +1648,28 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
| bool APIConnection::send_event(event::Event *event, std::string event_type) { | void APIConnection::send_event(event::Event *event, std::string event_type) { | ||||||
|  |   if (!APIConnection::try_send_event(this, event, std::move(event_type))) { | ||||||
|  |     this->deferred_message_queue_.defer(event, try_send_event); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void APIConnection::send_event_info(event::Event *event) { | ||||||
|  |   if (!APIConnection::try_send_event_info(this, event)) { | ||||||
|  |     this->deferred_message_queue_.defer(event, try_send_event_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_event(APIConnection *api, void *v_event) { | ||||||
|  |   event::Event *event = reinterpret_cast<event::Event *>(v_event); | ||||||
|  |   return APIConnection::try_send_event(api, event, *(event->last_event_type)); | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_event(APIConnection *api, event::Event *event, std::string event_type) { | ||||||
|   EventResponse resp{}; |   EventResponse resp{}; | ||||||
|   resp.key = event->get_object_id_hash(); |   resp.key = event->get_object_id_hash(); | ||||||
|   resp.event_type = std::move(event_type); |   resp.event_type = std::move(event_type); | ||||||
|   return this->send_event_response(resp); |   return api->send_event_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_event_info(event::Event *event) { | bool APIConnection::try_send_event_info(APIConnection *api, void *v_event) { | ||||||
|  |   event::Event *event = reinterpret_cast<event::Event *>(v_event); | ||||||
|   ListEntitiesEventResponse msg; |   ListEntitiesEventResponse msg; | ||||||
|   msg.key = event->get_object_id_hash(); |   msg.key = event->get_object_id_hash(); | ||||||
|   msg.object_id = event->get_object_id(); |   msg.object_id = event->get_object_id(); | ||||||
| @@ -1341,7 +1682,7 @@ bool APIConnection::send_event_info(event::Event *event) { | |||||||
|   msg.device_class = event->get_device_class(); |   msg.device_class = event->get_device_class(); | ||||||
|   for (const auto &event_type : event->get_event_types()) |   for (const auto &event_type : event->get_event_types()) | ||||||
|     msg.event_types.push_back(event_type); |     msg.event_types.push_back(event_type); | ||||||
|   return this->send_list_entities_event_response(msg); |   return api->send_list_entities_event_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -1350,6 +1691,19 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) { | |||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|  |   if (!APIConnection::try_send_update_state(this, update)) { | ||||||
|  |     this->deferred_message_queue_.defer(update, try_send_update_state); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | void APIConnection::send_update_info(update::UpdateEntity *update) { | ||||||
|  |   if (!APIConnection::try_send_update_info(this, update)) { | ||||||
|  |     this->deferred_message_queue_.defer(update, try_send_update_info); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool APIConnection::try_send_update_state(APIConnection *api, void *v_update) { | ||||||
|  |   update::UpdateEntity *update = reinterpret_cast<update::UpdateEntity *>(v_update); | ||||||
|   UpdateStateResponse resp{}; |   UpdateStateResponse resp{}; | ||||||
|   resp.key = update->get_object_id_hash(); |   resp.key = update->get_object_id_hash(); | ||||||
|   resp.missing_state = !update->has_state(); |   resp.missing_state = !update->has_state(); | ||||||
| @@ -1366,9 +1720,10 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) { | |||||||
|     resp.release_url = update->update_info.release_url; |     resp.release_url = update->update_info.release_url; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return this->send_update_state_response(resp); |   return api->send_update_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_update_info(update::UpdateEntity *update) { | bool APIConnection::try_send_update_info(APIConnection *api, void *v_update) { | ||||||
|  |   update::UpdateEntity *update = reinterpret_cast<update::UpdateEntity *>(v_update); | ||||||
|   ListEntitiesUpdateResponse msg; |   ListEntitiesUpdateResponse msg; | ||||||
|   msg.key = update->get_object_id_hash(); |   msg.key = update->get_object_id_hash(); | ||||||
|   msg.object_id = update->get_object_id(); |   msg.object_id = update->get_object_id(); | ||||||
| @@ -1379,7 +1734,7 @@ bool APIConnection::send_update_info(update::UpdateEntity *update) { | |||||||
|   msg.disabled_by_default = update->is_disabled_by_default(); |   msg.disabled_by_default = update->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category()); | ||||||
|   msg.device_class = update->get_device_class(); |   msg.device_class = update->get_device_class(); | ||||||
|   return this->send_list_entities_update_response(msg); |   return api->send_list_entities_update_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::update_command(const UpdateCommandRequest &msg) { | void APIConnection::update_command(const UpdateCommandRequest &msg) { | ||||||
|   update::UpdateEntity *update = App.get_update_by_key(msg.key); |   update::UpdateEntity *update = App.get_update_by_key(msg.key); | ||||||
| @@ -1403,7 +1758,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| bool APIConnection::send_log_message(int level, const char *tag, const char *line) { | bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) { | ||||||
|   if (this->log_subscription_ < level) |   if (this->log_subscription_ < level) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
| @@ -1488,6 +1843,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
|   resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version(); |   resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version(); | ||||||
|   resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); |   resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); | ||||||
|  |   resp.bluetooth_mac_address = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(); | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
|   resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version(); |   resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version(); | ||||||
|   | |||||||
| @@ -14,6 +14,46 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
|  | using send_message_t = bool(APIConnection *, void *); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |   This class holds a pointer to the source component that wants to publish a message, and a pointer to a function that | ||||||
|  |   will lazily publish that message.  The two pointers allow dedup in the deferred queue if multiple publishes for the | ||||||
|  |   same component are backed up, and take up only 8 bytes of memory.  The entry in the deferred queue (a std::vector) is | ||||||
|  |   the DeferredMessage instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per entry.  Even | ||||||
|  |   100 backed up messages (you'd have to have at least 100 sensors publishing because of dedup) would take up only 0.8 | ||||||
|  |   kB. | ||||||
|  | */ | ||||||
|  | class DeferredMessageQueue { | ||||||
|  |   struct DeferredMessage { | ||||||
|  |     friend class DeferredMessageQueue; | ||||||
|  |  | ||||||
|  |    protected: | ||||||
|  |     void *source_; | ||||||
|  |     send_message_t *send_message_; | ||||||
|  |  | ||||||
|  |    public: | ||||||
|  |     DeferredMessage(void *source, send_message_t *send_message) : source_(source), send_message_(send_message) {} | ||||||
|  |     bool operator==(const DeferredMessage &test) const { | ||||||
|  |       return (source_ == test.source_ && send_message_ == test.send_message_); | ||||||
|  |     } | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   // vector is used very specifically for its zero memory overhead even though items are popped from the front (memory | ||||||
|  |   // footprint is more important than speed here) | ||||||
|  |   std::vector<DeferredMessage> deferred_queue_; | ||||||
|  |   APIConnection *api_connection_; | ||||||
|  |  | ||||||
|  |   // helper for allowing only unique entries in the queue | ||||||
|  |   void dmq_push_back_with_dedup_(void *source, send_message_t *send_message); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {} | ||||||
|  |   void process_queue(); | ||||||
|  |   void defer(void *source, send_message_t *send_message); | ||||||
|  | }; | ||||||
|  |  | ||||||
| class APIConnection : public APIServerConnection { | class APIConnection : public APIServerConnection { | ||||||
|  public: |  public: | ||||||
|   APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent); |   APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent); | ||||||
| @@ -28,96 +68,140 @@ class APIConnection : public APIServerConnection { | |||||||
|   } |   } | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state); |   bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state); | ||||||
|   bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); |   void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); | ||||||
|  |   static bool try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor); | ||||||
|  |   static bool try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, bool state); | ||||||
|  |   static bool try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor); | ||||||
| #endif | #endif | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
|   bool send_cover_state(cover::Cover *cover); |   bool send_cover_state(cover::Cover *cover); | ||||||
|   bool send_cover_info(cover::Cover *cover); |   void send_cover_info(cover::Cover *cover); | ||||||
|  |   static bool try_send_cover_state(APIConnection *api, void *v_cover); | ||||||
|  |   static bool try_send_cover_info(APIConnection *api, void *v_cover); | ||||||
|   void cover_command(const CoverCommandRequest &msg) override; |   void cover_command(const CoverCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
|   bool send_fan_state(fan::Fan *fan); |   bool send_fan_state(fan::Fan *fan); | ||||||
|   bool send_fan_info(fan::Fan *fan); |   void send_fan_info(fan::Fan *fan); | ||||||
|  |   static bool try_send_fan_state(APIConnection *api, void *v_fan); | ||||||
|  |   static bool try_send_fan_info(APIConnection *api, void *v_fan); | ||||||
|   void fan_command(const FanCommandRequest &msg) override; |   void fan_command(const FanCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
|   bool send_light_state(light::LightState *light); |   bool send_light_state(light::LightState *light); | ||||||
|   bool send_light_info(light::LightState *light); |   void send_light_info(light::LightState *light); | ||||||
|  |   static bool try_send_light_state(APIConnection *api, void *v_light); | ||||||
|  |   static bool try_send_light_info(APIConnection *api, void *v_light); | ||||||
|   void light_command(const LightCommandRequest &msg) override; |   void light_command(const LightCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|   bool send_sensor_state(sensor::Sensor *sensor, float state); |   bool send_sensor_state(sensor::Sensor *sensor, float state); | ||||||
|   bool send_sensor_info(sensor::Sensor *sensor); |   void send_sensor_info(sensor::Sensor *sensor); | ||||||
|  |   static bool try_send_sensor_state(APIConnection *api, void *v_sensor); | ||||||
|  |   static bool try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state); | ||||||
|  |   static bool try_send_sensor_info(APIConnection *api, void *v_sensor); | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|   bool send_switch_state(switch_::Switch *a_switch, bool state); |   bool send_switch_state(switch_::Switch *a_switch, bool state); | ||||||
|   bool send_switch_info(switch_::Switch *a_switch); |   void send_switch_info(switch_::Switch *a_switch); | ||||||
|  |   static bool try_send_switch_state(APIConnection *api, void *v_a_switch); | ||||||
|  |   static bool try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state); | ||||||
|  |   static bool try_send_switch_info(APIConnection *api, void *v_a_switch); | ||||||
|   void switch_command(const SwitchCommandRequest &msg) override; |   void switch_command(const SwitchCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|   bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state); |   bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state); | ||||||
|   bool send_text_sensor_info(text_sensor::TextSensor *text_sensor); |   void send_text_sensor_info(text_sensor::TextSensor *text_sensor); | ||||||
|  |   static bool try_send_text_sensor_state(APIConnection *api, void *v_text_sensor); | ||||||
|  |   static bool try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, std::string state); | ||||||
|  |   static bool try_send_text_sensor_info(APIConnection *api, void *v_text_sensor); | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
|   void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); |   void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); | ||||||
|   bool send_camera_info(esp32_camera::ESP32Camera *camera); |   void send_camera_info(esp32_camera::ESP32Camera *camera); | ||||||
|  |   static bool try_send_camera_info(APIConnection *api, void *v_camera); | ||||||
|   void camera_image(const CameraImageRequest &msg) override; |   void camera_image(const CameraImageRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
|   bool send_climate_state(climate::Climate *climate); |   bool send_climate_state(climate::Climate *climate); | ||||||
|   bool send_climate_info(climate::Climate *climate); |   void send_climate_info(climate::Climate *climate); | ||||||
|  |   static bool try_send_climate_state(APIConnection *api, void *v_climate); | ||||||
|  |   static bool try_send_climate_info(APIConnection *api, void *v_climate); | ||||||
|   void climate_command(const ClimateCommandRequest &msg) override; |   void climate_command(const ClimateCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|   bool send_number_state(number::Number *number, float state); |   bool send_number_state(number::Number *number, float state); | ||||||
|   bool send_number_info(number::Number *number); |   void send_number_info(number::Number *number); | ||||||
|  |   static bool try_send_number_state(APIConnection *api, void *v_number); | ||||||
|  |   static bool try_send_number_state(APIConnection *api, number::Number *number, float state); | ||||||
|  |   static bool try_send_number_info(APIConnection *api, void *v_number); | ||||||
|   void number_command(const NumberCommandRequest &msg) override; |   void number_command(const NumberCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_DATETIME_DATE | #ifdef USE_DATETIME_DATE | ||||||
|   bool send_date_state(datetime::DateEntity *date); |   bool send_date_state(datetime::DateEntity *date); | ||||||
|   bool send_date_info(datetime::DateEntity *date); |   void send_date_info(datetime::DateEntity *date); | ||||||
|  |   static bool try_send_date_state(APIConnection *api, void *v_date); | ||||||
|  |   static bool try_send_date_info(APIConnection *api, void *v_date); | ||||||
|   void date_command(const DateCommandRequest &msg) override; |   void date_command(const DateCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_DATETIME_TIME | #ifdef USE_DATETIME_TIME | ||||||
|   bool send_time_state(datetime::TimeEntity *time); |   bool send_time_state(datetime::TimeEntity *time); | ||||||
|   bool send_time_info(datetime::TimeEntity *time); |   void send_time_info(datetime::TimeEntity *time); | ||||||
|  |   static bool try_send_time_state(APIConnection *api, void *v_time); | ||||||
|  |   static bool try_send_time_info(APIConnection *api, void *v_time); | ||||||
|   void time_command(const TimeCommandRequest &msg) override; |   void time_command(const TimeCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_DATETIME_DATETIME | #ifdef USE_DATETIME_DATETIME | ||||||
|   bool send_datetime_state(datetime::DateTimeEntity *datetime); |   bool send_datetime_state(datetime::DateTimeEntity *datetime); | ||||||
|   bool send_datetime_info(datetime::DateTimeEntity *datetime); |   void send_datetime_info(datetime::DateTimeEntity *datetime); | ||||||
|  |   static bool try_send_datetime_state(APIConnection *api, void *v_datetime); | ||||||
|  |   static bool try_send_datetime_info(APIConnection *api, void *v_datetime); | ||||||
|   void datetime_command(const DateTimeCommandRequest &msg) override; |   void datetime_command(const DateTimeCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_TEXT | #ifdef USE_TEXT | ||||||
|   bool send_text_state(text::Text *text, std::string state); |   bool send_text_state(text::Text *text, std::string state); | ||||||
|   bool send_text_info(text::Text *text); |   void send_text_info(text::Text *text); | ||||||
|  |   static bool try_send_text_state(APIConnection *api, void *v_text); | ||||||
|  |   static bool try_send_text_state(APIConnection *api, text::Text *text, std::string state); | ||||||
|  |   static bool try_send_text_info(APIConnection *api, void *v_text); | ||||||
|   void text_command(const TextCommandRequest &msg) override; |   void text_command(const TextCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   bool send_select_state(select::Select *select, std::string state); |   bool send_select_state(select::Select *select, std::string state); | ||||||
|   bool send_select_info(select::Select *select); |   void send_select_info(select::Select *select); | ||||||
|  |   static bool try_send_select_state(APIConnection *api, void *v_select); | ||||||
|  |   static bool try_send_select_state(APIConnection *api, select::Select *select, std::string state); | ||||||
|  |   static bool try_send_select_info(APIConnection *api, void *v_select); | ||||||
|   void select_command(const SelectCommandRequest &msg) override; |   void select_command(const SelectCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BUTTON | #ifdef USE_BUTTON | ||||||
|   bool send_button_info(button::Button *button); |   void send_button_info(button::Button *button); | ||||||
|  |   static bool try_send_button_info(APIConnection *api, void *v_button); | ||||||
|   void button_command(const ButtonCommandRequest &msg) override; |   void button_command(const ButtonCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LOCK | #ifdef USE_LOCK | ||||||
|   bool send_lock_state(lock::Lock *a_lock, lock::LockState state); |   bool send_lock_state(lock::Lock *a_lock, lock::LockState state); | ||||||
|   bool send_lock_info(lock::Lock *a_lock); |   void send_lock_info(lock::Lock *a_lock); | ||||||
|  |   static bool try_send_lock_state(APIConnection *api, void *v_a_lock); | ||||||
|  |   static bool try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state); | ||||||
|  |   static bool try_send_lock_info(APIConnection *api, void *v_a_lock); | ||||||
|   void lock_command(const LockCommandRequest &msg) override; |   void lock_command(const LockCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VALVE | #ifdef USE_VALVE | ||||||
|   bool send_valve_state(valve::Valve *valve); |   bool send_valve_state(valve::Valve *valve); | ||||||
|   bool send_valve_info(valve::Valve *valve); |   void send_valve_info(valve::Valve *valve); | ||||||
|  |   static bool try_send_valve_state(APIConnection *api, void *v_valve); | ||||||
|  |   static bool try_send_valve_info(APIConnection *api, void *v_valve); | ||||||
|   void valve_command(const ValveCommandRequest &msg) override; |   void valve_command(const ValveCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
|   bool send_media_player_state(media_player::MediaPlayer *media_player); |   bool send_media_player_state(media_player::MediaPlayer *media_player); | ||||||
|   bool send_media_player_info(media_player::MediaPlayer *media_player); |   void send_media_player_info(media_player::MediaPlayer *media_player); | ||||||
|  |   static bool try_send_media_player_state(APIConnection *api, void *v_media_player); | ||||||
|  |   static bool try_send_media_player_info(APIConnection *api, void *v_media_player); | ||||||
|   void media_player_command(const MediaPlayerCommandRequest &msg) override; |   void media_player_command(const MediaPlayerCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|   bool send_log_message(int level, const char *tag, const char *line); |   bool try_send_log_message(int level, const char *tag, const char *line); | ||||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { |   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||||
|     if (!this->service_call_subscription_) |     if (!this->service_call_subscription_) | ||||||
|       return; |       return; | ||||||
| @@ -160,18 +244,25 @@ class APIConnection : public APIServerConnection { | |||||||
|  |  | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
|   bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); |   bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); | ||||||
|   bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); |   void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); | ||||||
|  |   static bool try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel); | ||||||
|  |   static bool try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel); | ||||||
|   void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; |   void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
|   bool send_event(event::Event *event, std::string event_type); |   void send_event(event::Event *event, std::string event_type); | ||||||
|   bool send_event_info(event::Event *event); |   void send_event_info(event::Event *event); | ||||||
|  |   static bool try_send_event(APIConnection *api, void *v_event); | ||||||
|  |   static bool try_send_event(APIConnection *api, event::Event *event, std::string event_type); | ||||||
|  |   static bool try_send_event_info(APIConnection *api, void *v_event); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
|   bool send_update_state(update::UpdateEntity *update); |   bool send_update_state(update::UpdateEntity *update); | ||||||
|   bool send_update_info(update::UpdateEntity *update); |   void send_update_info(update::UpdateEntity *update); | ||||||
|  |   static bool try_send_update_state(APIConnection *api, void *v_update); | ||||||
|  |   static bool try_send_update_info(APIConnection *api, void *v_update); | ||||||
|   void update_command(const UpdateCommandRequest &msg) override; |   void update_command(const UpdateCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -262,6 +353,7 @@ class APIConnection : public APIServerConnection { | |||||||
|   bool service_call_subscription_{false}; |   bool service_call_subscription_{false}; | ||||||
|   bool next_close_ = false; |   bool next_close_ = false; | ||||||
|   APIServer *parent_; |   APIServer *parent_; | ||||||
|  |   DeferredMessageQueue deferred_message_queue_; | ||||||
|   InitialStateIterator initial_state_iterator_; |   InitialStateIterator initial_state_iterator_; | ||||||
|   ListEntitiesIterator list_entities_iterator_; |   ListEntitiesIterator list_entities_iterator_; | ||||||
|   int state_subs_at_ = -1; |   int state_subs_at_ = -1; | ||||||
|   | |||||||
| @@ -838,6 +838,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v | |||||||
|       this->suggested_area = value.as_string(); |       this->suggested_area = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 18: { | ||||||
|  |       this->bluetooth_mac_address = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -860,6 +864,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_uint32(14, this->legacy_voice_assistant_version); |   buffer.encode_uint32(14, this->legacy_voice_assistant_version); | ||||||
|   buffer.encode_uint32(17, this->voice_assistant_feature_flags); |   buffer.encode_uint32(17, this->voice_assistant_feature_flags); | ||||||
|   buffer.encode_string(16, this->suggested_area); |   buffer.encode_string(16, this->suggested_area); | ||||||
|  |   buffer.encode_string(18, this->bluetooth_mac_address); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | void DeviceInfoResponse::dump_to(std::string &out) const { | ||||||
| @@ -937,6 +942,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  suggested_area: "); |   out.append("  suggested_area: "); | ||||||
|   out.append("'").append(this->suggested_area).append("'"); |   out.append("'").append(this->suggested_area).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  bluetooth_mac_address: "); | ||||||
|  |   out.append("'").append(this->bluetooth_mac_address).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -354,6 +354,7 @@ class DeviceInfoResponse : public ProtoMessage { | |||||||
|   uint32_t legacy_voice_assistant_version{0}; |   uint32_t legacy_voice_assistant_version{0}; | ||||||
|   uint32_t voice_assistant_feature_flags{0}; |   uint32_t voice_assistant_feature_flags{0}; | ||||||
|   std::string suggested_area{}; |   std::string suggested_area{}; | ||||||
|  |   std::string bluetooth_mac_address{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ void APIServer::setup() { | |||||||
|     logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { |     logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { | ||||||
|       for (auto &c : this->clients_) { |       for (auto &c : this->clients_) { | ||||||
|         if (!c->remove_) |         if (!c->remove_) | ||||||
|           c->send_log_message(level, tag, message); |           c->try_send_log_message(level, tag, message); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @@ -86,7 +86,7 @@ void APIServer::setup() { | |||||||
|         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { |         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { | ||||||
|           for (auto &c : this->clients_) { |           for (auto &c : this->clients_) { | ||||||
|             if (!c->remove_) |             if (!c->remove_) | ||||||
|               c->send_camera_state(image); |               c->set_camera_state(image); | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -10,37 +10,63 @@ namespace api { | |||||||
|  |  | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||||
|   return this->client_->send_binary_sensor_info(binary_sensor); |   this->client_->send_binary_sensor_info(binary_sensor); | ||||||
|  |   return true; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); } | bool ListEntitiesIterator::on_cover(cover::Cover *cover) { | ||||||
|  |   this->client_->send_cover_info(cover); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
| bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); } | bool ListEntitiesIterator::on_fan(fan::Fan *fan) { | ||||||
|  |   this->client_->send_fan_info(fan); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
| bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); } | bool ListEntitiesIterator::on_light(light::LightState *light) { | ||||||
|  |   this->client_->send_light_info(light); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); } | bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { | ||||||
|  |   this->client_->send_sensor_info(sensor); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } | bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { | ||||||
|  |   this->client_->send_switch_info(a_switch); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BUTTON | #ifdef USE_BUTTON | ||||||
| bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } | bool ListEntitiesIterator::on_button(button::Button *button) { | ||||||
|  |   this->client_->send_button_info(button); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||||
|   return this->client_->send_text_sensor_info(text_sensor); |   this->client_->send_text_sensor_info(text_sensor); | ||||||
|  |   return true; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LOCK | #ifdef USE_LOCK | ||||||
| bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); } | bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { | ||||||
|  |   this->client_->send_lock_info(a_lock); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VALVE | #ifdef USE_VALVE | ||||||
| bool ListEntitiesIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_info(valve); } | bool ListEntitiesIterator::on_valve(valve::Valve *valve) { | ||||||
|  |   this->client_->send_valve_info(valve); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | ||||||
| @@ -52,55 +78,83 @@ bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { | |||||||
|  |  | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { | bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { | ||||||
|   return this->client_->send_camera_info(camera); |   this->client_->send_camera_info(camera); | ||||||
|  |   return true; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
| bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); } | bool ListEntitiesIterator::on_climate(climate::Climate *climate) { | ||||||
|  |   this->client_->send_climate_info(climate); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); } | bool ListEntitiesIterator::on_number(number::Number *number) { | ||||||
|  |   this->client_->send_number_info(number); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_DATETIME_DATE | #ifdef USE_DATETIME_DATE | ||||||
| bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); } | bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { | ||||||
|  |   this->client_->send_date_info(date); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_DATETIME_TIME | #ifdef USE_DATETIME_TIME | ||||||
| bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); } | bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { | ||||||
|  |   this->client_->send_time_info(time); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_DATETIME_DATETIME | #ifdef USE_DATETIME_DATETIME | ||||||
| bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { | bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||||
|   return this->client_->send_datetime_info(datetime); |   this->client_->send_datetime_info(datetime); | ||||||
|  |   return true; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_TEXT | #ifdef USE_TEXT | ||||||
| bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } | bool ListEntitiesIterator::on_text(text::Text *text) { | ||||||
|  |   this->client_->send_text_info(text); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); } | bool ListEntitiesIterator::on_select(select::Select *select) { | ||||||
|  |   this->client_->send_select_info(select); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
| bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) { | bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) { | ||||||
|   return this->client_->send_media_player_info(media_player); |   this->client_->send_media_player_info(media_player); | ||||||
|  |   return true; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||||
|   return this->client_->send_alarm_control_panel_info(a_alarm_control_panel); |   this->client_->send_alarm_control_panel_info(a_alarm_control_panel); | ||||||
|  |   return true; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
| bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); } | bool ListEntitiesIterator::on_event(event::Event *event) { | ||||||
|  |   this->client_->send_event_info(event); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
| bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); } | bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { | ||||||
|  |   this->client_->send_update_info(update); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
|   | |||||||
| @@ -80,6 +80,7 @@ class ListEntitiesIterator : public ComponentIterator { | |||||||
|   bool on_update(update::UpdateEntity *update) override; |   bool on_update(update::UpdateEntity *update) override; | ||||||
| #endif | #endif | ||||||
|   bool on_end() override; |   bool on_end() override; | ||||||
|  |   bool completed() { return this->state_ == IteratorState::NONE; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   APIConnection *client_; |   APIConnection *client_; | ||||||
|   | |||||||
| @@ -76,6 +76,8 @@ class InitialStateIterator : public ComponentIterator { | |||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
|   bool on_update(update::UpdateEntity *update) override; |   bool on_update(update::UpdateEntity *update) override; | ||||||
| #endif | #endif | ||||||
|  |   bool completed() { return this->state_ == IteratorState::NONE; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   APIConnection *client_; |   APIConnection *client_; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -15,6 +15,9 @@ | |||||||
|  |  | ||||||
| #include "bluetooth_connection.h" | #include "bluetooth_connection.h" | ||||||
|  |  | ||||||
|  | #include <esp_bt.h> | ||||||
|  | #include <esp_bt_device.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace bluetooth_proxy { | namespace bluetooth_proxy { | ||||||
|  |  | ||||||
| @@ -114,6 +117,11 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com | |||||||
|     return flags; |     return flags; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   std::string get_bluetooth_mac_address_pretty() { | ||||||
|  |     const uint8_t *mac = esp_bt_dev_get_address(); | ||||||
|  |     return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); |   void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -95,7 +95,7 @@ void BMP085Component::read_pressure_() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[0]); |   uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[2]); | ||||||
|   if ((value >> 5) == 0) { |   if ((value >> 5) == 0) { | ||||||
|     ESP_LOGW(TAG, "Invalid pressure!"); |     ESP_LOGW(TAG, "Invalid pressure!"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								esphome/components/chsc6x/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								esphome/components/chsc6x/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | CODEOWNERS = ["@kkosik20"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
							
								
								
									
										47
									
								
								esphome/components/chsc6x/chsc6x_touchscreen.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/chsc6x/chsc6x_touchscreen.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | #include "chsc6x_touchscreen.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace chsc6x { | ||||||
|  |  | ||||||
|  | void CHSC6XTouchscreen::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up CHSC6X Touchscreen..."); | ||||||
|  |   if (this->interrupt_pin_ != nullptr) { | ||||||
|  |     this->interrupt_pin_->setup(); | ||||||
|  |     this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); | ||||||
|  |   } | ||||||
|  |   if (this->x_raw_max_ == this->x_raw_min_) { | ||||||
|  |     this->x_raw_max_ = this->display_->get_native_width(); | ||||||
|  |   } | ||||||
|  |   if (this->y_raw_max_ == this->y_raw_min_) { | ||||||
|  |     this->y_raw_max_ = this->display_->get_native_height(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen setup complete"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CHSC6XTouchscreen::update_touches() { | ||||||
|  |   uint8_t data[CHSC6X_REG_STATUS_LEN]; | ||||||
|  |   if (!this->read_bytes(CHSC6X_REG_STATUS, data, sizeof(data))) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t num_of_touches = data[CHSC6X_REG_STATUS_TOUCH]; | ||||||
|  |  | ||||||
|  |   if (num_of_touches == 1) { | ||||||
|  |     uint16_t x = data[CHSC6X_REG_STATUS_X_COR]; | ||||||
|  |     uint16_t y = data[CHSC6X_REG_STATUS_Y_COR]; | ||||||
|  |     this->add_raw_touch_position_(0, x, y); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CHSC6XTouchscreen::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   LOG_PIN("  Interrupt Pin: ", this->interrupt_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Touch timeout: %d", this->touch_timeout_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  x_raw_max_: %d", this->x_raw_max_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  y_raw_max_: %d", this->y_raw_max_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace chsc6x | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										34
									
								
								esphome/components/chsc6x/chsc6x_touchscreen.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/chsc6x/chsc6x_touchscreen.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/components/touchscreen/touchscreen.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace chsc6x { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "chsc6x.touchscreen"; | ||||||
|  |  | ||||||
|  | static const uint8_t CHSC6X_REG_STATUS = 0x00; | ||||||
|  | static const uint8_t CHSC6X_REG_STATUS_TOUCH = 0x00; | ||||||
|  | static const uint8_t CHSC6X_REG_STATUS_X_COR = 0x02; | ||||||
|  | static const uint8_t CHSC6X_REG_STATUS_Y_COR = 0x04; | ||||||
|  | static const uint8_t CHSC6X_REG_STATUS_LEN = 0x05; | ||||||
|  | static const uint8_t CHSC6X_CHIP_ID = 0x2e; | ||||||
|  |  | ||||||
|  | class CHSC6XTouchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void update_touches() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   InternalGPIOPin *interrupt_pin_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace chsc6x | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										33
									
								
								esphome/components/chsc6x/touchscreen.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/chsc6x/touchscreen.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | from esphome import pins | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c, touchscreen | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ID, CONF_INTERRUPT_PIN | ||||||
|  |  | ||||||
|  | chsc6x_ns = cg.esphome_ns.namespace("chsc6x") | ||||||
|  |  | ||||||
|  | CHSC6XTouchscreen = chsc6x_ns.class_( | ||||||
|  |     "CHSC6XTouchscreen", | ||||||
|  |     touchscreen.Touchscreen, | ||||||
|  |     i2c.I2CDevice, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     touchscreen.touchscreen_schema("100ms") | ||||||
|  |     .extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(CHSC6XTouchscreen), | ||||||
|  |             cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x2E)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await touchscreen.register_touchscreen(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     if interrupt_pin := config.get(CONF_INTERRUPT_PIN): | ||||||
|  |         cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) | ||||||
| @@ -128,7 +128,6 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema( | |||||||
|  |  | ||||||
|  |  | ||||||
| def visual_temperature_step(value): | def visual_temperature_step(value): | ||||||
|  |  | ||||||
|     # Allow defining target/current temperature steps separately |     # Allow defining target/current temperature steps separately | ||||||
|     if isinstance(value, dict): |     if isinstance(value, dict): | ||||||
|         return VISUAL_TEMPERATURE_STEP_SCHEMA(value) |         return VISUAL_TEMPERATURE_STEP_SCHEMA(value) | ||||||
|   | |||||||
| @@ -1,28 +1,5 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import binary_sensor |  | ||||||
|  |  | ||||||
| from .. import cst816_ns | CONFIG_SCHEMA = cv.invalid( | ||||||
| from ..touchscreen import CST816Touchscreen, CST816ButtonListener |     "The CST816 binary sensor has been removed. Instead use the touchscreen binary sensor with the 'use_raw' flag set." | ||||||
|  |  | ||||||
| CONF_CST816_ID = "cst816_id" |  | ||||||
|  |  | ||||||
| CST816Button = cst816_ns.class_( |  | ||||||
|     "CST816Button", |  | ||||||
|     binary_sensor.BinarySensor, |  | ||||||
|     cg.Component, |  | ||||||
|     CST816ButtonListener, |  | ||||||
|     cg.Parented.template(CST816Touchscreen), |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST816Button).extend( |  | ||||||
|     { |  | ||||||
|         cv.GenerateID(CONF_CST816_ID): cv.use_id(CST816Touchscreen), |  | ||||||
|     } |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): |  | ||||||
|     var = await binary_sensor.new_binary_sensor(config) |  | ||||||
|     await cg.register_component(var, config) |  | ||||||
|     await cg.register_parented(var, config[CONF_CST816_ID]) |  | ||||||
|   | |||||||
| @@ -1,27 +0,0 @@ | |||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "esphome/components/binary_sensor/binary_sensor.h" |  | ||||||
| #include "esphome/components/cst816/touchscreen/cst816_touchscreen.h" |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/core/helpers.h" |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace cst816 { |  | ||||||
|  |  | ||||||
| class CST816Button : public binary_sensor::BinarySensor, |  | ||||||
|                      public Component, |  | ||||||
|                      public CST816ButtonListener, |  | ||||||
|                      public Parented<CST816Touchscreen> { |  | ||||||
|  public: |  | ||||||
|   void setup() override { |  | ||||||
|     this->parent_->register_button_listener(this); |  | ||||||
|     this->publish_initial_state(false); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void dump_config() override { LOG_BINARY_SENSOR("", "CST816 Button", this); } |  | ||||||
|  |  | ||||||
|   void update_button(bool state) override { this->publish_state(state); } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| }  // namespace cst816 |  | ||||||
| }  // namespace esphome |  | ||||||
| @@ -37,14 +37,6 @@ void CST816Touchscreen::continue_setup_() { | |||||||
|   ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete"); |   ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void CST816Touchscreen::update_button_state_(bool state) { |  | ||||||
|   if (this->button_touched_ == state) |  | ||||||
|     return; |  | ||||||
|   this->button_touched_ = state; |  | ||||||
|   for (auto *listener : this->button_listeners_) |  | ||||||
|     listener->update_button(state); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CST816Touchscreen::setup() { | void CST816Touchscreen::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen..."); |   ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen..."); | ||||||
|   if (this->reset_pin_ != nullptr) { |   if (this->reset_pin_ != nullptr) { | ||||||
| @@ -68,18 +60,13 @@ void CST816Touchscreen::update_touches() { | |||||||
|   } |   } | ||||||
|   uint8_t num_of_touches = data[REG_TOUCH_NUM] & 3; |   uint8_t num_of_touches = data[REG_TOUCH_NUM] & 3; | ||||||
|   if (num_of_touches == 0) { |   if (num_of_touches == 0) { | ||||||
|     this->update_button_state_(false); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]); |   uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]); | ||||||
|   uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]); |   uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]); | ||||||
|   ESP_LOGV(TAG, "Read touch %d/%d", x, y); |   ESP_LOGV(TAG, "Read touch %d/%d", x, y); | ||||||
|   if (x >= this->x_raw_max_) { |  | ||||||
|     this->update_button_state_(true); |  | ||||||
|   } else { |  | ||||||
|   this->add_raw_touch_position_(0, x, y); |   this->add_raw_touch_position_(0, x, y); | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void CST816Touchscreen::dump_config() { | void CST816Touchscreen::dump_config() { | ||||||
| @@ -87,6 +74,8 @@ void CST816Touchscreen::dump_config() { | |||||||
|   LOG_I2C_DEVICE(this); |   LOG_I2C_DEVICE(this); | ||||||
|   LOG_PIN("  Interrupt Pin: ", this->interrupt_pin_); |   LOG_PIN("  Interrupt Pin: ", this->interrupt_pin_); | ||||||
|   LOG_PIN("  Reset Pin: ", this->reset_pin_); |   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  X Raw Min: %d, X Raw Max: %d", this->x_raw_min_, this->x_raw_max_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Y Raw Min: %d, Y Raw Max: %d", this->y_raw_min_, this->y_raw_max_); | ||||||
|   const char *name; |   const char *name; | ||||||
|   switch (this->chip_id_) { |   switch (this->chip_id_) { | ||||||
|     case CST820_CHIP_ID: |     case CST820_CHIP_ID: | ||||||
|   | |||||||
| @@ -40,7 +40,6 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice | |||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void update_touches() override; |   void update_touches() override; | ||||||
|   void register_button_listener(CST816ButtonListener *listener) { this->button_listeners_.push_back(listener); } |  | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } |   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } | ||||||
| @@ -49,14 +48,11 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void continue_setup_(); |   void continue_setup_(); | ||||||
|   void update_button_state_(bool state); |  | ||||||
|  |  | ||||||
|   InternalGPIOPin *interrupt_pin_{}; |   InternalGPIOPin *interrupt_pin_{}; | ||||||
|   GPIOPin *reset_pin_{}; |   GPIOPin *reset_pin_{}; | ||||||
|   uint8_t chip_id_{}; |   uint8_t chip_id_{}; | ||||||
|   bool skip_probe_{};  // if set, do not expect to be able to probe the controller on the i2c bus. |   bool skip_probe_{};  // if set, do not expect to be able to probe the controller on the i2c bus. | ||||||
|   std::vector<CST816ButtonListener *> button_listeners_; |  | ||||||
|   bool button_touched_{}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace cst816 | }  // namespace cst816 | ||||||
|   | |||||||
| @@ -66,7 +66,9 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant | |||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     uuid = config[CONF_UUID].hex |     uuid = config[CONF_UUID].hex | ||||||
|     uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] |     uuid_arr = [ | ||||||
|  |         cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2) | ||||||
|  |     ] | ||||||
|     var = cg.new_Pvariable(config[CONF_ID], uuid_arr) |     var = cg.new_Pvariable(config[CONF_ID], uuid_arr) | ||||||
|  |  | ||||||
|     parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) |     parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) | ||||||
|   | |||||||
| @@ -112,8 +112,7 @@ def validate_supports(value): | |||||||
|         ) |         ) | ||||||
|     if is_pullup and num == 16: |     if is_pullup and num == 16: | ||||||
|         raise cv.Invalid( |         raise cv.Invalid( | ||||||
|             "GPIO Pin 16 does not support pullup pin mode. " |             "GPIO Pin 16 does not support pullup pin mode. Please choose another pin.", | ||||||
|             "Please choose another pin.", |  | ||||||
|             [CONF_MODE, CONF_PULLUP], |             [CONF_MODE, CONF_PULLUP], | ||||||
|         ) |         ) | ||||||
|     if is_pulldown and num != 16: |     if is_pulldown and num != 16: | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | from collections.abc import MutableMapping | ||||||
| import functools | import functools | ||||||
| import hashlib | import hashlib | ||||||
| import logging | import logging | ||||||
| @@ -6,10 +7,10 @@ from pathlib import Path | |||||||
| import re | import re | ||||||
|  |  | ||||||
| import esphome_glyphsets as glyphsets | import esphome_glyphsets as glyphsets | ||||||
| import freetype | from freetype import Face, ft_pixel_mode_grays, ft_pixel_mode_mono | ||||||
| import requests | import requests | ||||||
|  |  | ||||||
| from esphome import core, external_files | from esphome import external_files | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
| @@ -26,7 +27,7 @@ from esphome.const import ( | |||||||
|     CONF_WEIGHT, |     CONF_WEIGHT, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, HexInt | from esphome.core import CORE, HexInt | ||||||
| from esphome.helpers import copy_file_if_changed, cpp_string_escape | from esphome.helpers import cpp_string_escape | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -49,13 +50,42 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs" | |||||||
|  |  | ||||||
|  |  | ||||||
| # Cache loaded freetype fonts | # Cache loaded freetype fonts | ||||||
| class FontCache(dict): | class FontCache(MutableMapping): | ||||||
|     def __missing__(self, key): |     @staticmethod | ||||||
|         try: |     def get_name(value): | ||||||
|             res = self[key] = freetype.Face(key) |         if CONF_FAMILY in value: | ||||||
|             return res |             return ( | ||||||
|         except freetype.FT_Exception as e: |                 f"{value[CONF_FAMILY]}:{int(value[CONF_ITALIC])}:{value[CONF_WEIGHT]}" | ||||||
|             raise cv.Invalid(f"Could not load Font file {key}: {e}") from e |             ) | ||||||
|  |         if CONF_URL in value: | ||||||
|  |             return value[CONF_URL] | ||||||
|  |         return value[CONF_PATH] | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _keytransform(value): | ||||||
|  |         if CONF_FAMILY in value: | ||||||
|  |             return f"gfont:{value[CONF_FAMILY]}:{int(value[CONF_ITALIC])}:{value[CONF_WEIGHT]}" | ||||||
|  |         if CONF_URL in value: | ||||||
|  |             return f"url:{value[CONF_URL]}" | ||||||
|  |         return f"file:{value[CONF_PATH]}" | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         self.store = {} | ||||||
|  |  | ||||||
|  |     def __delitem__(self, key): | ||||||
|  |         del self.store[self._keytransform(key)] | ||||||
|  |  | ||||||
|  |     def __iter__(self): | ||||||
|  |         return iter(self.store) | ||||||
|  |  | ||||||
|  |     def __len__(self): | ||||||
|  |         return len(self.store) | ||||||
|  |  | ||||||
|  |     def __getitem__(self, item): | ||||||
|  |         return self.store[self._keytransform(item)] | ||||||
|  |  | ||||||
|  |     def __setitem__(self, key, value): | ||||||
|  |         self.store[self._keytransform(key)] = Face(str(value)) | ||||||
|  |  | ||||||
|  |  | ||||||
| FONT_CACHE = FontCache() | FONT_CACHE = FontCache() | ||||||
| @@ -109,14 +139,14 @@ def check_missing_glyphs(file, codepoints, warning: bool = False): | |||||||
|         ) |         ) | ||||||
|         if count > 10: |         if count > 10: | ||||||
|             missing_str += f"\n    and {count - 10} more." |             missing_str += f"\n    and {count - 10} more." | ||||||
|         message = f"Font {Path(file).name} is missing {count} glyph{'s' if count != 1 else ''}:\n    {missing_str}" |         message = f"Font {FontCache.get_name(file)} is missing {count} glyph{'s' if count != 1 else ''}:\n    {missing_str}" | ||||||
|         if warning: |         if warning: | ||||||
|             _LOGGER.warning(message) |             _LOGGER.warning(message) | ||||||
|         else: |         else: | ||||||
|             raise cv.Invalid(message) |             raise cv.Invalid(message) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_glyphs(config): | def validate_font_config(config): | ||||||
|     """ |     """ | ||||||
|     Check for duplicate codepoints, then check that all requested codepoints actually |     Check for duplicate codepoints, then check that all requested codepoints actually | ||||||
|     have glyphs defined in the appropriate font file. |     have glyphs defined in the appropriate font file. | ||||||
| @@ -143,8 +173,6 @@ def validate_glyphs(config): | |||||||
|     # Make setpoints and glyphspoints disjoint |     # Make setpoints and glyphspoints disjoint | ||||||
|     setpoints.difference_update(glyphspoints) |     setpoints.difference_update(glyphspoints) | ||||||
|     if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: |     if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP: | ||||||
|         # Pillow only allows 256 glyphs per bitmap font. Not sure if that is a Pillow limitation |  | ||||||
|         # or a file format limitation |  | ||||||
|         if any(x >= 256 for x in setpoints.copy().union(glyphspoints)): |         if any(x >= 256 for x in setpoints.copy().union(glyphspoints)): | ||||||
|             raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255") |             raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255") | ||||||
|     else: |     else: | ||||||
| @@ -154,13 +182,14 @@ def validate_glyphs(config): | |||||||
|             points = {ord(x) for x in flatten(extra[CONF_GLYPHS])} |             points = {ord(x) for x in flatten(extra[CONF_GLYPHS])} | ||||||
|             glyphspoints.difference_update(points) |             glyphspoints.difference_update(points) | ||||||
|             setpoints.difference_update(points) |             setpoints.difference_update(points) | ||||||
|             check_missing_glyphs(extra[CONF_FILE][CONF_PATH], points) |             check_missing_glyphs(extra[CONF_FILE], points) | ||||||
|  |  | ||||||
|         # A named glyph that can't be provided is an error |         # A named glyph that can't be provided is an error | ||||||
|         check_missing_glyphs(fileconf[CONF_PATH], glyphspoints) |  | ||||||
|  |         check_missing_glyphs(fileconf, glyphspoints) | ||||||
|         # A missing glyph from a set is a warning. |         # A missing glyph from a set is a warning. | ||||||
|         if not config[CONF_IGNORE_MISSING_GLYPHS]: |         if not config[CONF_IGNORE_MISSING_GLYPHS]: | ||||||
|             check_missing_glyphs(fileconf[CONF_PATH], setpoints, warning=True) |             check_missing_glyphs(fileconf, setpoints, warning=True) | ||||||
|  |  | ||||||
|     # Populate the default after the above checks so that use of the default doesn't trigger errors |     # Populate the default after the above checks so that use of the default doesn't trigger errors | ||||||
|     if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]: |     if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]: | ||||||
| @@ -168,17 +197,32 @@ def validate_glyphs(config): | |||||||
|             config[CONF_GLYPHS] = [DEFAULT_GLYPHS] |             config[CONF_GLYPHS] = [DEFAULT_GLYPHS] | ||||||
|         else: |         else: | ||||||
|             # set a default glyphset, intersected with what the font actually offers |             # set a default glyphset, intersected with what the font actually offers | ||||||
|             font = FONT_CACHE[fileconf[CONF_PATH]] |             font = FONT_CACHE[fileconf] | ||||||
|             config[CONF_GLYPHS] = [ |             config[CONF_GLYPHS] = [ | ||||||
|                 chr(x) |                 chr(x) | ||||||
|                 for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET) |                 for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET) | ||||||
|                 if font.get_char_index(x) != 0 |                 if font.get_char_index(x) != 0 | ||||||
|             ] |             ] | ||||||
|  |  | ||||||
|  |     if config[CONF_FILE][CONF_TYPE] == TYPE_LOCAL_BITMAP: | ||||||
|  |         if CONF_SIZE in config: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 "Size is not a valid option for bitmap fonts, which are inherently fixed size" | ||||||
|  |             ) | ||||||
|  |     elif CONF_SIZE not in config: | ||||||
|  |         config[CONF_SIZE] = 20 | ||||||
|  |  | ||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| FONT_EXTENSIONS = (".ttf", ".woff", ".otf") | FONT_EXTENSIONS = (".ttf", ".woff", ".otf") | ||||||
|  | BITMAP_EXTENSIONS = (".bdf", ".pcf") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_bitmap_file(value): | ||||||
|  |     if not any(map(value.lower().endswith, BITMAP_EXTENSIONS)): | ||||||
|  |         raise cv.Invalid(f"Only {', '.join(BITMAP_EXTENSIONS)} files are supported.") | ||||||
|  |     return CORE.relative_config_path(cv.file_(value)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_truetype_file(value): | def validate_truetype_file(value): | ||||||
| @@ -187,24 +231,40 @@ def validate_truetype_file(value): | |||||||
|             f"Please unzip the font archive '{value}' first and then use the .ttf files inside." |             f"Please unzip the font archive '{value}' first and then use the .ttf files inside." | ||||||
|         ) |         ) | ||||||
|     if not any(map(value.lower().endswith, FONT_EXTENSIONS)): |     if not any(map(value.lower().endswith, FONT_EXTENSIONS)): | ||||||
|         raise cv.Invalid(f"Only {FONT_EXTENSIONS} files are supported.") |         raise cv.Invalid(f"Only {', '.join(FONT_EXTENSIONS)} files are supported.") | ||||||
|     return CORE.relative_config_path(cv.file_(value)) |     return CORE.relative_config_path(cv.file_(value)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def add_local_file(value): | ||||||
|  |     if value in FONT_CACHE: | ||||||
|  |         return value | ||||||
|  |     path = value[CONF_PATH] | ||||||
|  |     if not os.path.isfile(path): | ||||||
|  |         raise cv.Invalid(f"File '{path}' not found.") | ||||||
|  |     FONT_CACHE[value] = path | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| TYPE_LOCAL = "local" | TYPE_LOCAL = "local" | ||||||
| TYPE_LOCAL_BITMAP = "local_bitmap" | TYPE_LOCAL_BITMAP = "local_bitmap" | ||||||
| TYPE_GFONTS = "gfonts" | TYPE_GFONTS = "gfonts" | ||||||
| TYPE_WEB = "web" | TYPE_WEB = "web" | ||||||
| LOCAL_SCHEMA = cv.Schema( | LOCAL_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_PATH): validate_truetype_file, |             cv.Required(CONF_PATH): validate_truetype_file, | ||||||
|         } |         } | ||||||
|  |     ), | ||||||
|  |     add_local_file, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| LOCAL_BITMAP_SCHEMA = cv.Schema( | LOCAL_BITMAP_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|         { |         { | ||||||
|         cv.Required(CONF_PATH): cv.file_, |             cv.Required(CONF_PATH): validate_bitmap_file, | ||||||
|         } |         } | ||||||
|  |     ), | ||||||
|  |     add_local_file, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| FULLPATH_SCHEMA = cv.maybe_simple_value( | FULLPATH_SCHEMA = cv.maybe_simple_value( | ||||||
| @@ -235,27 +295,23 @@ def _compute_local_font_path(value: dict) -> Path: | |||||||
|     h.update(url.encode()) |     h.update(url.encode()) | ||||||
|     key = h.hexdigest()[:8] |     key = h.hexdigest()[:8] | ||||||
|     base_dir = external_files.compute_local_file_dir(DOMAIN) |     base_dir = external_files.compute_local_file_dir(DOMAIN) | ||||||
|     _LOGGER.debug("_compute_local_font_path: base_dir=%s", base_dir / key) |     _LOGGER.debug("_compute_local_font_path: %s", base_dir / key) | ||||||
|     return base_dir / key |     return base_dir / key | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_font_path(value, font_type) -> Path: |  | ||||||
|     if font_type == TYPE_GFONTS: |  | ||||||
|         name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" |  | ||||||
|         return external_files.compute_local_file_dir(DOMAIN) / f"{name}.ttf" |  | ||||||
|     if font_type == TYPE_WEB: |  | ||||||
|         return _compute_local_font_path(value) / "font.ttf" |  | ||||||
|     assert False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def download_gfont(value): | def download_gfont(value): | ||||||
|  |     if value in FONT_CACHE: | ||||||
|  |         return value | ||||||
|     name = ( |     name = ( | ||||||
|         f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}" |         f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}" | ||||||
|     ) |     ) | ||||||
|     url = f"https://fonts.googleapis.com/css2?family={name}" |     url = f"https://fonts.googleapis.com/css2?family={name}" | ||||||
|     path = get_font_path(value, TYPE_GFONTS) |     path = ( | ||||||
|  |         external_files.compute_local_file_dir(DOMAIN) | ||||||
|  |         / f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1.ttf" | ||||||
|  |     ) | ||||||
|  |     if not external_files.is_file_recent(str(path), value[CONF_REFRESH]): | ||||||
|         _LOGGER.debug("download_gfont: path=%s", path) |         _LOGGER.debug("download_gfont: path=%s", path) | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             req = requests.get(url, timeout=external_files.NETWORK_TIMEOUT) |             req = requests.get(url, timeout=external_files.NETWORK_TIMEOUT) | ||||||
|             req.raise_for_status() |             req.raise_for_status() | ||||||
| @@ -275,16 +331,23 @@ def download_gfont(value): | |||||||
|         _LOGGER.debug("download_gfont: ttf_url=%s", ttf_url) |         _LOGGER.debug("download_gfont: ttf_url=%s", ttf_url) | ||||||
|  |  | ||||||
|         external_files.download_content(ttf_url, path) |         external_files.download_content(ttf_url, path) | ||||||
|     return FULLPATH_SCHEMA(path) |         # In case the remote file is not modified, the download_content function will return the existing file, | ||||||
|  |         # so update the modification time to now. | ||||||
|  |         path.touch() | ||||||
|  |     FONT_CACHE[value] = path | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| def download_web_font(value): | def download_web_font(value): | ||||||
|  |     if value in FONT_CACHE: | ||||||
|  |         return value | ||||||
|     url = value[CONF_URL] |     url = value[CONF_URL] | ||||||
|     path = get_font_path(value, TYPE_WEB) |     path = _compute_local_font_path(value) / "font.ttf" | ||||||
|  |  | ||||||
|     external_files.download_content(url, path) |     external_files.download_content(url, path) | ||||||
|     _LOGGER.debug("download_web_font: path=%s", path) |     _LOGGER.debug("download_web_font: path=%s", path) | ||||||
|     return FULLPATH_SCHEMA(path) |     FONT_CACHE[value] = path | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| EXTERNAL_FONT_SCHEMA = cv.Schema( | EXTERNAL_FONT_SCHEMA = cv.Schema( | ||||||
| @@ -340,14 +403,14 @@ def validate_file_shorthand(value): | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     if value.endswith(".pcf") or value.endswith(".bdf"): |     extension = Path(value).suffix | ||||||
|         value = convert_bitmap_to_pillow_font( |     if extension in BITMAP_EXTENSIONS: | ||||||
|             CORE.relative_config_path(cv.file_(value)) |         return font_file_schema( | ||||||
|         ) |             { | ||||||
|         return { |  | ||||||
|                 CONF_TYPE: TYPE_LOCAL_BITMAP, |                 CONF_TYPE: TYPE_LOCAL_BITMAP, | ||||||
|                 CONF_PATH: value, |                 CONF_PATH: value, | ||||||
|             } |             } | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     return font_file_schema( |     return font_file_schema( | ||||||
|         { |         { | ||||||
| @@ -391,7 +454,7 @@ FONT_SCHEMA = cv.Schema( | |||||||
|             cv.one_of(*glyphsets.defined_glyphsets()) |             cv.one_of(*glyphsets.defined_glyphsets()) | ||||||
|         ), |         ), | ||||||
|         cv.Optional(CONF_IGNORE_MISSING_GLYPHS, default=False): cv.boolean, |         cv.Optional(CONF_IGNORE_MISSING_GLYPHS, default=False): cv.boolean, | ||||||
|         cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), |         cv.Optional(CONF_SIZE): cv.int_range(min=1), | ||||||
|         cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8), |         cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8), | ||||||
|         cv.Optional(CONF_EXTRAS, default=[]): cv.ensure_list( |         cv.Optional(CONF_EXTRAS, default=[]): cv.ensure_list( | ||||||
|             cv.Schema( |             cv.Schema( | ||||||
| @@ -406,114 +469,19 @@ FONT_SCHEMA = cv.Schema( | |||||||
|     }, |     }, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs) | CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_font_config) | ||||||
|  |  | ||||||
|  |  | ||||||
| # PIL doesn't provide a consistent interface for both TrueType and bitmap |  | ||||||
| # fonts. So, we use our own wrappers to give us the consistency that we need. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TrueTypeFontWrapper: |  | ||||||
|     def __init__(self, font): |  | ||||||
|         self.font = font |  | ||||||
|  |  | ||||||
|     def getoffset(self, glyph): |  | ||||||
|         _, (offset_x, offset_y) = self.font.font.getsize(glyph) |  | ||||||
|         return offset_x, offset_y |  | ||||||
|  |  | ||||||
|     def getmask(self, glyph, **kwargs): |  | ||||||
|         return self.font.getmask(str(glyph), **kwargs) |  | ||||||
|  |  | ||||||
|     def getmetrics(self, glyphs): |  | ||||||
|         return self.font.getmetrics() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BitmapFontWrapper: |  | ||||||
|     def __init__(self, font): |  | ||||||
|         self.font = font |  | ||||||
|         self.max_height = 0 |  | ||||||
|  |  | ||||||
|     def getoffset(self, glyph): |  | ||||||
|         return 0, 0 |  | ||||||
|  |  | ||||||
|     def getmask(self, glyph, **kwargs): |  | ||||||
|         return self.font.getmask(str(glyph), **kwargs) |  | ||||||
|  |  | ||||||
|     def getmetrics(self, glyphs): |  | ||||||
|         max_height = 0 |  | ||||||
|         for glyph in glyphs: |  | ||||||
|             mask = self.getmask(glyph, mode="1") |  | ||||||
|             _, height = mask.size |  | ||||||
|             max_height = max(max_height, height) |  | ||||||
|         return max_height, 0 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EFont: | class EFont: | ||||||
|     def __init__(self, file, size, codepoints): |     def __init__(self, file, codepoints): | ||||||
|         self.codepoints = codepoints |         self.codepoints = codepoints | ||||||
|         path = file[CONF_PATH] |         self.font: Face = FONT_CACHE[file] | ||||||
|         self.name = Path(path).name |  | ||||||
|         ftype = file[CONF_TYPE] |  | ||||||
|         if ftype == TYPE_LOCAL_BITMAP: |  | ||||||
|             self.font = load_bitmap_font(path) |  | ||||||
|         else: |  | ||||||
|             self.font = load_ttf_font(path, size) |  | ||||||
|         self.ascent, self.descent = self.font.getmetrics(codepoints) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def convert_bitmap_to_pillow_font(filepath): |  | ||||||
|     from PIL import BdfFontFile, PcfFontFile |  | ||||||
|  |  | ||||||
|     local_bitmap_font_file = external_files.compute_local_file_dir( |  | ||||||
|         DOMAIN, |  | ||||||
|     ) / os.path.basename(filepath) |  | ||||||
|  |  | ||||||
|     copy_file_if_changed(filepath, local_bitmap_font_file) |  | ||||||
|  |  | ||||||
|     local_pil_font_file = local_bitmap_font_file.with_suffix(".pil") |  | ||||||
|     with open(local_bitmap_font_file, "rb") as fp: |  | ||||||
|         try: |  | ||||||
|             try: |  | ||||||
|                 p = PcfFontFile.PcfFontFile(fp) |  | ||||||
|             except SyntaxError: |  | ||||||
|                 fp.seek(0) |  | ||||||
|                 p = BdfFontFile.BdfFontFile(fp) |  | ||||||
|  |  | ||||||
|             # Convert to pillow-formatted fonts, which have a .pil and .pbm extension. |  | ||||||
|             p.save(local_pil_font_file) |  | ||||||
|         except (SyntaxError, OSError) as err: |  | ||||||
|             raise core.EsphomeError( |  | ||||||
|                 f"Failed to parse as bitmap font: '{filepath}': {err}" |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|     return str(local_pil_font_file) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def load_bitmap_font(filepath): |  | ||||||
|     from PIL import ImageFont |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         font = ImageFont.load(str(filepath)) |  | ||||||
|     except Exception as e: |  | ||||||
|         raise core.EsphomeError(f"Failed to load bitmap font file: {filepath}: {e}") |  | ||||||
|  |  | ||||||
|     return BitmapFontWrapper(font) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def load_ttf_font(path, size): |  | ||||||
|     from PIL import ImageFont |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         font = ImageFont.truetype(str(path), size) |  | ||||||
|     except Exception as e: |  | ||||||
|         raise core.EsphomeError(f"Could not load TrueType file {path}: {e}") |  | ||||||
|  |  | ||||||
|     return TrueTypeFontWrapper(font) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GlyphInfo: | class GlyphInfo: | ||||||
|     def __init__(self, data_len, offset_x, offset_y, width, height): |     def __init__(self, data_len, advance, offset_x, offset_y, width, height): | ||||||
|         self.data_len = data_len |         self.data_len = data_len | ||||||
|  |         self.advance = advance | ||||||
|         self.offset_x = offset_x |         self.offset_x = offset_x | ||||||
|         self.offset_y = offset_y |         self.offset_y = offset_y | ||||||
|         self.width = width |         self.width = width | ||||||
| @@ -537,15 +505,14 @@ async def to_code(config): | |||||||
|     } |     } | ||||||
|     # get the codepoints from the glyphs key, flatten to a list of chrs and combine with the points from glyphsets |     # get the codepoints from the glyphs key, flatten to a list of chrs and combine with the points from glyphsets | ||||||
|     point_set.update(flatten(config[CONF_GLYPHS])) |     point_set.update(flatten(config[CONF_GLYPHS])) | ||||||
|     size = config[CONF_SIZE] |  | ||||||
|     # Create the codepoint to font file map |     # Create the codepoint to font file map | ||||||
|     base_font = EFont(config[CONF_FILE], size, point_set) |     base_font = FONT_CACHE[config[CONF_FILE]] | ||||||
|     point_font_map: dict[str, EFont] = {c: base_font for c in point_set} |     point_font_map: dict[str, Face] = {c: base_font for c in point_set} | ||||||
|     # process extras, updating the map and extending the codepoint list |     # process extras, updating the map and extending the codepoint list | ||||||
|     for extra in config[CONF_EXTRAS]: |     for extra in config[CONF_EXTRAS]: | ||||||
|         extra_points = flatten(extra[CONF_GLYPHS]) |         extra_points = flatten(extra[CONF_GLYPHS]) | ||||||
|         point_set.update(extra_points) |         point_set.update(extra_points) | ||||||
|         extra_font = EFont(extra[CONF_FILE], size, extra_points) |         extra_font = FONT_CACHE[extra[CONF_FILE]] | ||||||
|         point_font_map.update({c: extra_font for c in extra_points}) |         point_font_map.update({c: extra_font for c in extra_points}) | ||||||
|  |  | ||||||
|     codepoints = list(point_set) |     codepoints = list(point_set) | ||||||
| @@ -553,28 +520,52 @@ async def to_code(config): | |||||||
|     glyph_args = {} |     glyph_args = {} | ||||||
|     data = [] |     data = [] | ||||||
|     bpp = config[CONF_BPP] |     bpp = config[CONF_BPP] | ||||||
|     if bpp == 1: |     mode = ft_pixel_mode_grays | ||||||
|         mode = "1" |  | ||||||
|         scale = 1 |  | ||||||
|     else: |  | ||||||
|         mode = "L" |  | ||||||
|     scale = 256 // (1 << bpp) |     scale = 256 // (1 << bpp) | ||||||
|     # create the data array for all glyphs |     # create the data array for all glyphs | ||||||
|     for codepoint in codepoints: |     for codepoint in codepoints: | ||||||
|         font = point_font_map[codepoint] |         font = point_font_map[codepoint] | ||||||
|         mask = font.font.getmask(codepoint, mode=mode) |         if not font.has_fixed_sizes: | ||||||
|         offset_x, offset_y = font.font.getoffset(codepoint) |             font.set_pixel_sizes(config[CONF_SIZE], 0) | ||||||
|         width, height = mask.size |         font.load_char(codepoint) | ||||||
|  |         font.glyph.render(mode) | ||||||
|  |         width = font.glyph.bitmap.width | ||||||
|  |         height = font.glyph.bitmap.rows | ||||||
|  |         buffer = font.glyph.bitmap.buffer | ||||||
|  |         pitch = font.glyph.bitmap.pitch | ||||||
|         glyph_data = [0] * ((height * width * bpp + 7) // 8) |         glyph_data = [0] * ((height * width * bpp + 7) // 8) | ||||||
|  |         src_mode = font.glyph.bitmap.pixel_mode | ||||||
|         pos = 0 |         pos = 0 | ||||||
|         for y in range(height): |         for y in range(height): | ||||||
|             for x in range(width): |             for x in range(width): | ||||||
|                 pixel = mask.getpixel((x, y)) // scale |                 if src_mode == ft_pixel_mode_mono: | ||||||
|  |                     pixel = ( | ||||||
|  |                         (1 << bpp) - 1 | ||||||
|  |                         if buffer[y * pitch + x // 8] & (1 << (7 - x % 8)) | ||||||
|  |                         else 0 | ||||||
|  |                     ) | ||||||
|  |                 else: | ||||||
|  |                     pixel = buffer[y * pitch + x] // scale | ||||||
|                 for bit_num in range(bpp): |                 for bit_num in range(bpp): | ||||||
|                     if pixel & (1 << (bpp - bit_num - 1)): |                     if pixel & (1 << (bpp - bit_num - 1)): | ||||||
|                         glyph_data[pos // 8] |= 0x80 >> (pos % 8) |                         glyph_data[pos // 8] |= 0x80 >> (pos % 8) | ||||||
|                     pos += 1 |                     pos += 1 | ||||||
|         glyph_args[codepoint] = GlyphInfo(len(data), offset_x, offset_y, width, height) |         ascender = font.size.ascender // 64 | ||||||
|  |         if ascender == 0: | ||||||
|  |             if font.has_fixed_sizes: | ||||||
|  |                 ascender = font.available_sizes[0].height | ||||||
|  |             else: | ||||||
|  |                 _LOGGER.error( | ||||||
|  |                     "Unable to determine ascender of font %s", config[CONF_FILE] | ||||||
|  |                 ) | ||||||
|  |         glyph_args[codepoint] = GlyphInfo( | ||||||
|  |             len(data), | ||||||
|  |             font.glyph.metrics.horiAdvance // 64, | ||||||
|  |             font.glyph.bitmap_left, | ||||||
|  |             ascender - font.glyph.bitmap_top, | ||||||
|  |             width, | ||||||
|  |             height, | ||||||
|  |         ) | ||||||
|         data += glyph_data |         data += glyph_data | ||||||
|  |  | ||||||
|     rhs = [HexInt(x) for x in data] |     rhs = [HexInt(x) for x in data] | ||||||
| @@ -598,6 +589,7 @@ async def to_code(config): | |||||||
|                         f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}" |                         f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}" | ||||||
|                     ), |                     ), | ||||||
|                 ), |                 ), | ||||||
|  |                 ("advance", glyph_args[codepoint].advance), | ||||||
|                 ("offset_x", glyph_args[codepoint].offset_x), |                 ("offset_x", glyph_args[codepoint].offset_x), | ||||||
|                 ("offset_y", glyph_args[codepoint].offset_y), |                 ("offset_y", glyph_args[codepoint].offset_y), | ||||||
|                 ("width", glyph_args[codepoint].width), |                 ("width", glyph_args[codepoint].width), | ||||||
| @@ -607,11 +599,19 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) |     glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) | ||||||
|  |  | ||||||
|  |     font_height = base_font.size.height // 64 | ||||||
|  |     ascender = base_font.size.ascender // 64 | ||||||
|  |     if font_height == 0: | ||||||
|  |         if base_font.has_fixed_sizes: | ||||||
|  |             font_height = base_font.available_sizes[0].height | ||||||
|  |             ascender = font_height | ||||||
|  |         else: | ||||||
|  |             _LOGGER.error("Unable to determine height of font %s", config[CONF_FILE]) | ||||||
|     cg.new_Pvariable( |     cg.new_Pvariable( | ||||||
|         config[CONF_ID], |         config[CONF_ID], | ||||||
|         glyphs, |         glyphs, | ||||||
|         len(glyph_initializer), |         len(glyph_initializer), | ||||||
|         base_font.ascent, |         ascender, | ||||||
|         base_font.ascent + base_font.descent, |         font_height, | ||||||
|         bpp, |         bpp, | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -81,7 +81,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in | |||||||
|     if (glyph_n < 0) { |     if (glyph_n < 0) { | ||||||
|       // Unknown char, skip |       // Unknown char, skip | ||||||
|       if (!this->get_glyphs().empty()) |       if (!this->get_glyphs().empty()) | ||||||
|         x += this->get_glyphs()[0].glyph_data_->width; |         x += this->get_glyphs()[0].glyph_data_->advance; | ||||||
|       i++; |       i++; | ||||||
|       continue; |       continue; | ||||||
|     } |     } | ||||||
| @@ -92,7 +92,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in | |||||||
|     } else { |     } else { | ||||||
|       min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); |       min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); | ||||||
|     } |     } | ||||||
|     x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; |     x += glyph.glyph_data_->advance; | ||||||
|  |  | ||||||
|     i += match_length; |     i += match_length; | ||||||
|     has_char = true; |     has_char = true; | ||||||
| @@ -111,7 +111,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo | |||||||
|       // Unknown char, skip |       // Unknown char, skip | ||||||
|       ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); |       ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); | ||||||
|       if (!this->get_glyphs().empty()) { |       if (!this->get_glyphs().empty()) { | ||||||
|         uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->width; |         uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->advance; | ||||||
|         display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color); |         display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color); | ||||||
|         x_at += glyph_width; |         x_at += glyph_width; | ||||||
|       } |       } | ||||||
| @@ -161,7 +161,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; |     x_at += glyph.glyph_data_->advance; | ||||||
|  |  | ||||||
|     i += match_length; |     i += match_length; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ class Font; | |||||||
| struct GlyphData { | struct GlyphData { | ||||||
|   const uint8_t *a_char; |   const uint8_t *a_char; | ||||||
|   const uint8_t *data; |   const uint8_t *data; | ||||||
|  |   int advance; | ||||||
|   int offset_x; |   int offset_x; | ||||||
|   int offset_y; |   int offset_y; | ||||||
|   int width; |   int width; | ||||||
|   | |||||||
| @@ -1,9 +1,15 @@ | |||||||
| import logging | import logging | ||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| import esphome.final_validate as fv |  | ||||||
| from esphome.components import uart, climate, logger |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import climate, logger, uart | ||||||
|  | from esphome.components.climate import ( | ||||||
|  |     CONF_CURRENT_TEMPERATURE, | ||||||
|  |     ClimateMode, | ||||||
|  |     ClimatePreset, | ||||||
|  |     ClimateSwingMode, | ||||||
|  | ) | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_BEEPER, |     CONF_BEEPER, | ||||||
|     CONF_DISPLAY, |     CONF_DISPLAY, | ||||||
| @@ -24,12 +30,7 @@ from esphome.const import ( | |||||||
|     CONF_VISUAL, |     CONF_VISUAL, | ||||||
|     CONF_WIFI, |     CONF_WIFI, | ||||||
| ) | ) | ||||||
| from esphome.components.climate import ( | import esphome.final_validate as fv | ||||||
|     ClimateMode, |  | ||||||
|     ClimatePreset, |  | ||||||
|     ClimateSwingMode, |  | ||||||
|     CONF_CURRENT_TEMPERATURE, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWAR | |||||||
| void HBridgeSwitch::setup() { | void HBridgeSwitch::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str()); |   ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str()); | ||||||
|  |  | ||||||
|   optional<bool> initial_state = this->get_initial_state_with_restore_mode().value_or(false); |   optional<bool> initial_state = this->get_initial_state_with_restore_mode(); | ||||||
|  |  | ||||||
|   // Like GPIOSwitch does, set the pin state both before and after pin setup() |   // Like GPIOSwitch does, set the pin state both before and after pin setup() | ||||||
|   this->on_pin_->digital_write(false); |   this->on_pin_->digital_write(false); | ||||||
| @@ -24,7 +24,7 @@ void HBridgeSwitch::setup() { | |||||||
|   this->off_pin_->digital_write(false); |   this->off_pin_->digital_write(false); | ||||||
|  |  | ||||||
|   if (initial_state.has_value()) |   if (initial_state.has_value()) | ||||||
|     this->write_state(initial_state); |     this->write_state(initial_state.value()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HBridgeSwitch::dump_config() { | void HBridgeSwitch::dump_config() { | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ PROTOCOLS = { | |||||||
|     "mitsubishi_sez": Protocol.PROTOCOL_MITSUBISHI_SEZ, |     "mitsubishi_sez": Protocol.PROTOCOL_MITSUBISHI_SEZ, | ||||||
|     "panasonic_ckp": Protocol.PROTOCOL_PANASONIC_CKP, |     "panasonic_ckp": Protocol.PROTOCOL_PANASONIC_CKP, | ||||||
|     "panasonic_dke": Protocol.PROTOCOL_PANASONIC_DKE, |     "panasonic_dke": Protocol.PROTOCOL_PANASONIC_DKE, | ||||||
|  |     "panasonic_eke": Protocol.PROTOCOL_PANASONIC_EKE, | ||||||
|     "panasonic_jke": Protocol.PROTOCOL_PANASONIC_JKE, |     "panasonic_jke": Protocol.PROTOCOL_PANASONIC_JKE, | ||||||
|     "panasonic_lke": Protocol.PROTOCOL_PANASONIC_LKE, |     "panasonic_lke": Protocol.PROTOCOL_PANASONIC_LKE, | ||||||
|     "panasonic_nke": Protocol.PROTOCOL_PANASONIC_NKE, |     "panasonic_nke": Protocol.PROTOCOL_PANASONIC_NKE, | ||||||
| @@ -127,6 +128,6 @@ def to_code(config): | |||||||
|     cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) |     cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) | ||||||
|     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) |     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) | ||||||
|  |  | ||||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.27") |     cg.add_library("tonia/HeatpumpIR", "1.0.32") | ||||||
|     if CORE.is_libretiny: |     if CORE.is_libretiny: | ||||||
|         CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") |         CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP | |||||||
|     {PROTOCOL_MITSUBISHI_SEZ, []() { return new MitsubishiSEZKDXXHeatpumpIR(); }},           // NOLINT |     {PROTOCOL_MITSUBISHI_SEZ, []() { return new MitsubishiSEZKDXXHeatpumpIR(); }},           // NOLINT | ||||||
|     {PROTOCOL_PANASONIC_CKP, []() { return new PanasonicCKPHeatpumpIR(); }},                 // NOLINT |     {PROTOCOL_PANASONIC_CKP, []() { return new PanasonicCKPHeatpumpIR(); }},                 // NOLINT | ||||||
|     {PROTOCOL_PANASONIC_DKE, []() { return new PanasonicDKEHeatpumpIR(); }},                 // NOLINT |     {PROTOCOL_PANASONIC_DKE, []() { return new PanasonicDKEHeatpumpIR(); }},                 // NOLINT | ||||||
|  |     {PROTOCOL_PANASONIC_EKE, []() { return new PanasonicEKEHeatpumpIR(); }},                 // NOLINT | ||||||
|     {PROTOCOL_PANASONIC_JKE, []() { return new PanasonicJKEHeatpumpIR(); }},                 // NOLINT |     {PROTOCOL_PANASONIC_JKE, []() { return new PanasonicJKEHeatpumpIR(); }},                 // NOLINT | ||||||
|     {PROTOCOL_PANASONIC_LKE, []() { return new PanasonicLKEHeatpumpIR(); }},                 // NOLINT |     {PROTOCOL_PANASONIC_LKE, []() { return new PanasonicLKEHeatpumpIR(); }},                 // NOLINT | ||||||
|     {PROTOCOL_PANASONIC_NKE, []() { return new PanasonicNKEHeatpumpIR(); }},                 // NOLINT |     {PROTOCOL_PANASONIC_NKE, []() { return new PanasonicNKEHeatpumpIR(); }},                 // NOLINT | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ enum Protocol { | |||||||
|   PROTOCOL_MITSUBISHI_SEZ, |   PROTOCOL_MITSUBISHI_SEZ, | ||||||
|   PROTOCOL_PANASONIC_CKP, |   PROTOCOL_PANASONIC_CKP, | ||||||
|   PROTOCOL_PANASONIC_DKE, |   PROTOCOL_PANASONIC_DKE, | ||||||
|  |   PROTOCOL_PANASONIC_EKE, | ||||||
|   PROTOCOL_PANASONIC_JKE, |   PROTOCOL_PANASONIC_JKE, | ||||||
|   PROTOCOL_PANASONIC_LKE, |   PROTOCOL_PANASONIC_LKE, | ||||||
|   PROTOCOL_PANASONIC_NKE, |   PROTOCOL_PANASONIC_NKE, | ||||||
|   | |||||||
| @@ -1,23 +1,23 @@ | |||||||
|  | from esphome import pins | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.final_validate as fv |  | ||||||
| from esphome import pins |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_ADDRESS, | ||||||
|     CONF_FREQUENCY, |     CONF_FREQUENCY, | ||||||
|     CONF_TIMEOUT, |     CONF_I2C_ID, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_INPUT, |     CONF_INPUT, | ||||||
|     CONF_OUTPUT, |     CONF_OUTPUT, | ||||||
|     CONF_SCAN, |     CONF_SCAN, | ||||||
|     CONF_SCL, |     CONF_SCL, | ||||||
|     CONF_SDA, |     CONF_SDA, | ||||||
|     CONF_ADDRESS, |     CONF_TIMEOUT, | ||||||
|     CONF_I2C_ID, |  | ||||||
|     PLATFORM_ESP32, |     PLATFORM_ESP32, | ||||||
|     PLATFORM_ESP8266, |     PLATFORM_ESP8266, | ||||||
|     PLATFORM_RP2040, |     PLATFORM_RP2040, | ||||||
| ) | ) | ||||||
| from esphome.core import coroutine_with_priority, CORE | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | import esphome.final_validate as fv | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| i2c_ns = cg.esphome_ns.namespace("i2c") | i2c_ns = cg.esphome_ns.namespace("i2c") | ||||||
|   | |||||||
| @@ -8,6 +8,10 @@ | |||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) | ||||||
|  | #define SOC_HP_I2C_NUM SOC_I2C_NUM | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace i2c { | namespace i2c { | ||||||
|  |  | ||||||
| @@ -17,14 +21,14 @@ void IDFI2CBus::setup() { | |||||||
|   ESP_LOGCONFIG(TAG, "Setting up I2C bus..."); |   ESP_LOGCONFIG(TAG, "Setting up I2C bus..."); | ||||||
|   static i2c_port_t next_port = I2C_NUM_0; |   static i2c_port_t next_port = I2C_NUM_0; | ||||||
|   port_ = next_port; |   port_ = next_port; | ||||||
| #if SOC_I2C_NUM > 1 | #if SOC_HP_I2C_NUM > 1 | ||||||
|   next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX; |   next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX; | ||||||
| #else | #else | ||||||
|   next_port = I2C_NUM_MAX; |   next_port = I2C_NUM_MAX; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   if (port_ == I2C_NUM_MAX) { |   if (port_ == I2C_NUM_MAX) { | ||||||
|     ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_I2C_NUM); |     ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_HP_I2C_NUM); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -203,7 +203,7 @@ size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t tick | |||||||
|     this->start(); |     this->start(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if ((this->state_ != speaker::STATE_RUNNING) || (this->audio_ring_buffer_.use_count() == 1)) { |   if ((this->state_ != speaker::STATE_RUNNING) || (this->audio_ring_buffer_.use_count() != 1)) { | ||||||
|     // Unable to write data to a running speaker, so delay the max amount of time so it can get ready |     // Unable to write data to a running speaker, so delay the max amount of time so it can get ready | ||||||
|     vTaskDelay(ticks_to_wait); |     vTaskDelay(ticks_to_wait); | ||||||
|     ticks_to_wait = 0; |     ticks_to_wait = 0; | ||||||
|   | |||||||
| @@ -57,6 +57,7 @@ ColorOrder = display.display_ns.enum("ColorMode") | |||||||
|  |  | ||||||
| MODELS = { | MODELS = { | ||||||
|     "GC9A01A": ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay), |     "GC9A01A": ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay), | ||||||
|  |     "GC9D01N": ili9xxx_ns.class_("ILI9XXXGC9D01N", ILI9XXXDisplay), | ||||||
|     "M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), |     "M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay), | ||||||
|     "M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), |     "M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay), | ||||||
|     "TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), |     "TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay), | ||||||
|   | |||||||
| @@ -272,6 +272,11 @@ class ILI9XXXGC9A01A : public ILI9XXXDisplay { | |||||||
|   ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240) {} |   ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class ILI9XXXGC9D01N : public ILI9XXXDisplay { | ||||||
|  |  public: | ||||||
|  |   ILI9XXXGC9D01N() : ILI9XXXDisplay(INITCMD_GC9D01N, 160, 160) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
| //-----------   ILI9XXX_24_TFT display -------------- | //-----------   ILI9XXX_24_TFT display -------------- | ||||||
| class ILI9XXXST7735 : public ILI9XXXDisplay { | class ILI9XXXST7735 : public ILI9XXXDisplay { | ||||||
|  public: |  public: | ||||||
|   | |||||||
| @@ -367,6 +367,65 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = { | |||||||
|   0x00                  // End of list |   0x00                  // End of list | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | static const uint8_t PROGMEM INITCMD_GC9D01N[] = { | ||||||
|  |   // Enable Inter_command | ||||||
|  |   0xFE, 0,          // Inter Register Enable 1 (FEh) | ||||||
|  |   0xEF, 0,          // Inter Register Enable 2 (EFh) | ||||||
|  |   // Inter_command is now enabled | ||||||
|  |   0x80, 1, 0xFF, | ||||||
|  |   0x81, 1, 0xFF, | ||||||
|  |   0x82, 1, 0xFF, | ||||||
|  |   0x83, 1, 0xFF, | ||||||
|  |   0x84, 1, 0xFF, | ||||||
|  |   0x85, 1, 0xFF, | ||||||
|  |   0x86, 1, 0xFF, | ||||||
|  |   0x87, 1, 0xFF, | ||||||
|  |   0x88, 1, 0xFF, | ||||||
|  |   0x89, 1, 0xFF, | ||||||
|  |   0x8A, 1, 0xFF, | ||||||
|  |   0x8B, 1, 0xFF, | ||||||
|  |   0x8C, 1, 0xFF, | ||||||
|  |   0x8D, 1, 0xFF, | ||||||
|  |   0x8E, 1, 0xFF, | ||||||
|  |   0x8F, 1, 0xFF, | ||||||
|  |   0X3A, 1, 0x05,    // COLMOD: Pixel Format Set (3Ah) MCU interface, 16 bits / pixel | ||||||
|  |   0xEC, 1, 0x01,    // Inversion (ECh) DINV=1+2H1V column for Dual Gate (BFh=0) | ||||||
|  |                     // According to datasheet Inversion (ECh) value 0x01 isn't valid, but Lilygo uses it everywhere | ||||||
|  |   0x74, 7, 0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |   0x98, 1, 0x3e, | ||||||
|  |   0x99, 1, 0x3e, | ||||||
|  |   0xB5, 2, 0x0D, 0x0D,    // Blanking Porch Control (B5h) VFP=14 VBP=14 HBP=Off | ||||||
|  |   0x60, 4, 0x38, 0x0F, 0x79, 0x67, | ||||||
|  |   0x61, 4, 0x38, 0x11, 0x79, 0x67, | ||||||
|  |   0x64, 6, 0x38, 0x17, 0x71, 0x5F, 0x79, 0x67, | ||||||
|  |   0x65, 6, 0x38, 0x13, 0x71, 0x5B, 0x79, 0x67, | ||||||
|  |   0x6A, 2, 0x00, 0x00, | ||||||
|  |   0x6C, 7, 0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50, | ||||||
|  |   0x6E, 32, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x0F, 0x0F, | ||||||
|  |             0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x00, 0x00, | ||||||
|  |             0x00, 0x00, 0x0A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, | ||||||
|  |             0x10, 0x10, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, | ||||||
|  |   0xBF, 1, 0x01,    // Dual-Single gate select (BFh) 01h = dual gate mode | ||||||
|  |   0xF9, 1, 0x40, | ||||||
|  |   0x9B, 5, 0x3B, 0x93, 0x33, 0x7F, 0x00, | ||||||
|  |   0x7E, 1, 0x30, | ||||||
|  |   0x70, 6, 0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08, | ||||||
|  |   0x71, 3, 0x0D, 0x02, 0x08, | ||||||
|  |   0x91, 2, 0x0E, 0x09, | ||||||
|  |   // Set VREG1A, VREG1B, VREG2A, VREG2B voltage | ||||||
|  |   // According to datasheet set either 0xC3/0xC4 or 0xC9 only, but Lilygo sets both of them | ||||||
|  |   0xC3, 5, 0x19, 0xC4, 0x19, 0xC9, 0x3C, | ||||||
|  |   0xF0, 6, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E,    // SET_GAMMA1 (F0h) | ||||||
|  |   0xF1, 6, 0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F,    // SET_GAMMA2 (F1h) | ||||||
|  |   0xF2, 6, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A,    // SET_GAMMA3 (F2h) | ||||||
|  |   0xF3, 6, 0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF,    // SET_GAMMA4 (F3h) | ||||||
|  |   ILI9XXX_SLPOUT, 0,      // Sleep Out Mode (11h) | ||||||
|  |   ILI9XXX_DELAY(10), | ||||||
|  |   ILI9XXX_DISPON, 0,      // Display ON (29h) | ||||||
|  |   ILI9XXX_DELAY(20), | ||||||
|  |   0x00                    // End of list | ||||||
|  | }; | ||||||
|  |  | ||||||
| static const uint8_t PROGMEM INITCMD_ST7735[] = { | static const uint8_t PROGMEM INITCMD_ST7735[] = { | ||||||
|     ILI9XXX_SWRESET, 0,         // Soft reset, then delay 10ms |     ILI9XXX_SWRESET, 0,         // Soft reset, then delay 10ms | ||||||
|     ILI9XXX_DELAY(10), |     ILI9XXX_DELAY(10), | ||||||
|   | |||||||
							
								
								
									
										51
									
								
								esphome/components/ld2450/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								esphome/components/ld2450/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import uart | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_THROTTLE, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["uart"] | ||||||
|  | CODEOWNERS = ["@hareeshmu"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  | ld2450_ns = cg.esphome_ns.namespace("ld2450") | ||||||
|  | LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice) | ||||||
|  |  | ||||||
|  | CONF_LD2450_ID = "ld2450_id" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(LD2450Component), | ||||||
|  |             cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All( | ||||||
|  |                 cv.positive_time_period_milliseconds, | ||||||
|  |                 cv.Range(min=cv.TimePeriod(milliseconds=1)), | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(uart.UART_DEVICE_SCHEMA) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | LD2450BaseSchema = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |     }, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||||
|  |     "ld2450", | ||||||
|  |     require_tx=True, | ||||||
|  |     require_rx=True, | ||||||
|  |     parity="NONE", | ||||||
|  |     stop_bits=1, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  |     cg.add(var.set_throttle(config[CONF_THROTTLE])) | ||||||
							
								
								
									
										47
									
								
								esphome/components/ld2450/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/ld2450/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import binary_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_HAS_MOVING_TARGET, | ||||||
|  |     CONF_HAS_STILL_TARGET, | ||||||
|  |     CONF_HAS_TARGET, | ||||||
|  |     DEVICE_CLASS_MOTION, | ||||||
|  |     DEVICE_CLASS_OCCUPANCY, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import CONF_LD2450_ID, LD2450Component | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["ld2450"] | ||||||
|  |  | ||||||
|  | ICON_MEDITATION = "mdi:meditation" | ||||||
|  | ICON_SHIELD_ACCOUNT = "mdi:shield-account" | ||||||
|  | ICON_TARGET_ACCOUNT = "mdi:target-account" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = { | ||||||
|  |     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |     cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( | ||||||
|  |         device_class=DEVICE_CLASS_OCCUPANCY, | ||||||
|  |         icon=ICON_SHIELD_ACCOUNT, | ||||||
|  |     ), | ||||||
|  |     cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema( | ||||||
|  |         device_class=DEVICE_CLASS_MOTION, | ||||||
|  |         icon=ICON_TARGET_ACCOUNT, | ||||||
|  |     ), | ||||||
|  |     cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema( | ||||||
|  |         device_class=DEVICE_CLASS_OCCUPANCY, | ||||||
|  |         icon=ICON_MEDITATION, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||||
|  |     if has_target_config := config.get(CONF_HAS_TARGET): | ||||||
|  |         sens = await binary_sensor.new_binary_sensor(has_target_config) | ||||||
|  |         cg.add(ld2450_component.set_target_binary_sensor(sens)) | ||||||
|  |     if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET): | ||||||
|  |         sens = await binary_sensor.new_binary_sensor(has_moving_target_config) | ||||||
|  |         cg.add(ld2450_component.set_moving_target_binary_sensor(sens)) | ||||||
|  |     if has_still_target_config := config.get(CONF_HAS_STILL_TARGET): | ||||||
|  |         sens = await binary_sensor.new_binary_sensor(has_still_target_config) | ||||||
|  |         cg.add(ld2450_component.set_still_target_binary_sensor(sens)) | ||||||
							
								
								
									
										45
									
								
								esphome/components/ld2450/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/ld2450/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import button | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_FACTORY_RESET, | ||||||
|  |     CONF_RESTART, | ||||||
|  |     DEVICE_CLASS_RESTART, | ||||||
|  |     ENTITY_CATEGORY_CONFIG, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ICON_RESTART, | ||||||
|  |     ICON_RESTART_ALERT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||||
|  |  | ||||||
|  | ResetButton = ld2450_ns.class_("ResetButton", button.Button) | ||||||
|  | RestartButton = ld2450_ns.class_("RestartButton", button.Button) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = { | ||||||
|  |     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |     cv.Optional(CONF_FACTORY_RESET): button.button_schema( | ||||||
|  |         ResetButton, | ||||||
|  |         device_class=DEVICE_CLASS_RESTART, | ||||||
|  |         entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |         icon=ICON_RESTART_ALERT, | ||||||
|  |     ), | ||||||
|  |     cv.Optional(CONF_RESTART): button.button_schema( | ||||||
|  |         RestartButton, | ||||||
|  |         device_class=DEVICE_CLASS_RESTART, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |         icon=ICON_RESTART, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||||
|  |     if factory_reset_config := config.get(CONF_FACTORY_RESET): | ||||||
|  |         b = await button.new_button(factory_reset_config) | ||||||
|  |         await cg.register_parented(b, config[CONF_LD2450_ID]) | ||||||
|  |         cg.add(ld2450_component.set_reset_button(b)) | ||||||
|  |     if restart_config := config.get(CONF_RESTART): | ||||||
|  |         b = await button.new_button(restart_config) | ||||||
|  |         await cg.register_parented(b, config[CONF_LD2450_ID]) | ||||||
|  |         cg.add(ld2450_component.set_restart_button(b)) | ||||||
							
								
								
									
										9
									
								
								esphome/components/ld2450/button/reset_button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								esphome/components/ld2450/button/reset_button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | #include "reset_button.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | void ResetButton::press_action() { this->parent_->factory_reset(); } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/button/reset_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/button/reset_button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/button/button.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class ResetButton : public button::Button, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   ResetButton() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void press_action() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										9
									
								
								esphome/components/ld2450/button/restart_button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								esphome/components/ld2450/button/restart_button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | #include "restart_button.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/button/restart_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/button/restart_button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/button/button.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class RestartButton : public button::Button, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   RestartButton() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void press_action() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										876
									
								
								esphome/components/ld2450/ld2450.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										876
									
								
								esphome/components/ld2450/ld2450.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,876 @@ | |||||||
|  | #include "ld2450.h" | ||||||
|  | #include <utility> | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  | #include "esphome/components/number/number.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #endif | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | #define highbyte(val) (uint8_t)((val) >> 8) | ||||||
|  | #define lowbyte(val) (uint8_t)((val) &0xff) | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "ld2450"; | ||||||
|  | static const char *const UNKNOWN_MAC("unknown"); | ||||||
|  |  | ||||||
|  | // LD2450 UART Serial Commands | ||||||
|  | static const uint8_t CMD_ENABLE_CONF = 0x00FF; | ||||||
|  | static const uint8_t CMD_DISABLE_CONF = 0x00FE; | ||||||
|  | static const uint8_t CMD_VERSION = 0x00A0; | ||||||
|  | static const uint8_t CMD_MAC = 0x00A5; | ||||||
|  | static const uint8_t CMD_RESET = 0x00A2; | ||||||
|  | static const uint8_t CMD_RESTART = 0x00A3; | ||||||
|  | static const uint8_t CMD_BLUETOOTH = 0x00A4; | ||||||
|  | static const uint8_t CMD_SINGLE_TARGET_MODE = 0x0080; | ||||||
|  | static const uint8_t CMD_MULTI_TARGET_MODE = 0x0090; | ||||||
|  | static const uint8_t CMD_QUERY_TARGET_MODE = 0x0091; | ||||||
|  | static const uint8_t CMD_SET_BAUD_RATE = 0x00A1; | ||||||
|  | static const uint8_t CMD_QUERY_ZONE = 0x00C1; | ||||||
|  | static const uint8_t CMD_SET_ZONE = 0x00C2; | ||||||
|  |  | ||||||
|  | static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; }; | ||||||
|  |  | ||||||
|  | static inline std::string convert_signed_int_to_hex(int value) { | ||||||
|  |   auto value_as_str = str_snprintf("%04x", 4, value & 0xFFFF); | ||||||
|  |   return value_as_str; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) { | ||||||
|  |   for (int i = 0; i < 4; i++) { | ||||||
|  |     std::string temp_hex = convert_signed_int_to_hex(values[i]); | ||||||
|  |     bytes[i * 2] = std::stoi(temp_hex.substr(2, 2), nullptr, 16);      // Store high byte | ||||||
|  |     bytes[i * 2 + 1] = std::stoi(temp_hex.substr(0, 2), nullptr, 16);  // Store low byte | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline int16_t decode_coordinate(uint8_t low_byte, uint8_t high_byte) { | ||||||
|  |   int16_t coordinate = (high_byte & 0x7F) << 8 | low_byte; | ||||||
|  |   if ((high_byte & 0x80) == 0) { | ||||||
|  |     coordinate = -coordinate; | ||||||
|  |   } | ||||||
|  |   return coordinate;  // mm | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline int16_t decode_speed(uint8_t low_byte, uint8_t high_byte) { | ||||||
|  |   int16_t speed = (high_byte & 0x7F) << 8 | low_byte; | ||||||
|  |   if ((high_byte & 0x80) == 0) { | ||||||
|  |     speed = -speed; | ||||||
|  |   } | ||||||
|  |   return speed * 10;  // mm/s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline int16_t hex_to_signed_int(const uint8_t *buffer, uint8_t offset) { | ||||||
|  |   uint16_t hex_val = (buffer[offset + 1] << 8) | buffer[offset]; | ||||||
|  |   int16_t dec_val = static_cast<int16_t>(hex_val); | ||||||
|  |   if (dec_val & 0x8000) { | ||||||
|  |     dec_val -= 65536; | ||||||
|  |   } | ||||||
|  |   return dec_val; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline float calculate_angle(float base, float hypotenuse) { | ||||||
|  |   if (base < 0.0 || hypotenuse <= 0.0) { | ||||||
|  |     return 0.0; | ||||||
|  |   } | ||||||
|  |   float angle_radians = std::acos(base / hypotenuse); | ||||||
|  |   float angle_degrees = angle_radians * (180.0 / M_PI); | ||||||
|  |   return angle_degrees; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline std::string get_direction(int16_t speed) { | ||||||
|  |   static const char *const APPROACHING = "Approaching"; | ||||||
|  |   static const char *const MOVING_AWAY = "Moving away"; | ||||||
|  |   static const char *const STATIONARY = "Stationary"; | ||||||
|  |  | ||||||
|  |   if (speed > 0) { | ||||||
|  |     return MOVING_AWAY; | ||||||
|  |   } | ||||||
|  |   if (speed < 0) { | ||||||
|  |     return APPROACHING; | ||||||
|  |   } | ||||||
|  |   return STATIONARY; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline std::string format_mac(uint8_t *buffer) { | ||||||
|  |   return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], | ||||||
|  |                       buffer[15]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static inline std::string format_version(uint8_t *buffer) { | ||||||
|  |   return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], | ||||||
|  |                      buffer[14]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | LD2450Component::LD2450Component() {} | ||||||
|  |  | ||||||
|  | void LD2450Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up HLK-LD2450..."); | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  |   if (this->presence_timeout_number_ != nullptr) { | ||||||
|  |     this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_object_id_hash()); | ||||||
|  |     this->set_presence_timeout(); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |   this->restart_and_read_all_info(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void LD2450Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:"); | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   LOG_BINARY_SENSOR("  ", "TargetBinarySensor", this->target_binary_sensor_); | ||||||
|  |   LOG_BINARY_SENSOR("  ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); | ||||||
|  |   LOG_BINARY_SENSOR("  ", "StillTargetBinarySensor", this->still_target_binary_sensor_); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |   LOG_SWITCH("  ", "BluetoothSwitch", this->bluetooth_switch_); | ||||||
|  |   LOG_SWITCH("  ", "MultiTargetSwitch", this->multi_target_switch_); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   LOG_BUTTON("  ", "ResetButton", this->reset_button_); | ||||||
|  |   LOG_BUTTON("  ", "RestartButton", this->restart_button_); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   LOG_SENSOR("  ", "TargetCountSensor", this->target_count_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "StillTargetCountSensor", this->still_target_count_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "MovingTargetCountSensor", this->moving_target_count_sensor_); | ||||||
|  |   for (sensor::Sensor *s : this->move_x_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthTargetXSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->move_y_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthTargetYSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->move_speed_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthTargetSpeedSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->move_angle_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthTargetAngleSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->move_distance_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthTargetDistanceSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->move_resolution_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthTargetResolutionSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->zone_target_count_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthZoneTargetCountSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->zone_still_target_count_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthZoneStillTargetCountSensor", s); | ||||||
|  |   } | ||||||
|  |   for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) { | ||||||
|  |     LOG_SENSOR("  ", "NthZoneMovingTargetCountSensor", s); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   LOG_TEXT_SENSOR("  ", "VersionTextSensor", this->version_text_sensor_); | ||||||
|  |   LOG_TEXT_SENSOR("  ", "MacTextSensor", this->mac_text_sensor_); | ||||||
|  |   for (text_sensor::TextSensor *s : this->direction_text_sensors_) { | ||||||
|  |     LOG_TEXT_SENSOR("  ", "NthDirectionTextSensor", s); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  |   for (auto n : this->zone_numbers_) { | ||||||
|  |     LOG_NUMBER("  ", "ZoneX1Number", n.x1); | ||||||
|  |     LOG_NUMBER("  ", "ZoneY1Number", n.y1); | ||||||
|  |     LOG_NUMBER("  ", "ZoneX2Number", n.x2); | ||||||
|  |     LOG_NUMBER("  ", "ZoneY2Number", n.y2); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SELECT | ||||||
|  |   LOG_SELECT("  ", "BaudRateSelect", this->baud_rate_select_); | ||||||
|  |   LOG_SELECT("  ", "ZoneTypeSelect", this->zone_type_select_); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  |   LOG_NUMBER("  ", "PresenceTimeoutNumber", this->presence_timeout_number_); | ||||||
|  | #endif | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Throttle : %ums", this->throttle_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  MAC Address : %s", const_cast<char *>(this->mac_.c_str())); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Firmware version : %s", const_cast<char *>(this->version_.c_str())); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void LD2450Component::loop() { | ||||||
|  |   while (this->available()) { | ||||||
|  |     this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Count targets in zone | ||||||
|  | uint8_t LD2450Component::count_targets_in_zone_(const Zone &zone, bool is_moving) { | ||||||
|  |   uint8_t count = 0; | ||||||
|  |   for (auto &index : this->target_info_) { | ||||||
|  |     if (index.x > zone.x1 && index.x < zone.x2 && index.y > zone.y1 && index.y < zone.y2 && | ||||||
|  |         index.is_moving == is_moving) { | ||||||
|  |       count++; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return count; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Service reset_radar_zone | ||||||
|  | void LD2450Component::reset_radar_zone() { | ||||||
|  |   this->zone_type_ = 0; | ||||||
|  |   for (auto &i : this->zone_config_) { | ||||||
|  |     i.x1 = 0; | ||||||
|  |     i.y1 = 0; | ||||||
|  |     i.x2 = 0; | ||||||
|  |     i.y2 = 0; | ||||||
|  |   } | ||||||
|  |   this->send_set_zone_command_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2, | ||||||
|  |                                      int32_t zone1_y2, int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, | ||||||
|  |                                      int32_t zone2_y2, int32_t zone3_x1, int32_t zone3_y1, int32_t zone3_x2, | ||||||
|  |                                      int32_t zone3_y2) { | ||||||
|  |   this->zone_type_ = zone_type; | ||||||
|  |   int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1, | ||||||
|  |                              zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2}; | ||||||
|  |   for (int i = 0; i < MAX_ZONES; i++) { | ||||||
|  |     this->zone_config_[i].x1 = zone_parameters[i * 4]; | ||||||
|  |     this->zone_config_[i].y1 = zone_parameters[i * 4 + 1]; | ||||||
|  |     this->zone_config_[i].x2 = zone_parameters[i * 4 + 2]; | ||||||
|  |     this->zone_config_[i].y2 = zone_parameters[i * 4 + 3]; | ||||||
|  |   } | ||||||
|  |   this->send_set_zone_command_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set Zone on LD2450 Sensor | ||||||
|  | void LD2450Component::send_set_zone_command_() { | ||||||
|  |   uint8_t cmd_value[26] = {}; | ||||||
|  |   uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00}; | ||||||
|  |   uint8_t area_config[24] = {}; | ||||||
|  |   for (int i = 0; i < MAX_ZONES; i++) { | ||||||
|  |     int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2, | ||||||
|  |                      this->zone_config_[i].y2}; | ||||||
|  |     ld2450::convert_int_values_to_hex(values, area_config + (i * 8)); | ||||||
|  |   } | ||||||
|  |   std::memcpy(cmd_value, zone_type_bytes, 2); | ||||||
|  |   std::memcpy(cmd_value + 2, area_config, 24); | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   this->send_command_(CMD_SET_ZONE, cmd_value, 26); | ||||||
|  |   this->set_config_mode_(false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Check presense timeout to reset presence status | ||||||
|  | bool LD2450Component::get_timeout_status_(uint32_t check_millis) { | ||||||
|  |   if (check_millis == 0) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (this->timeout_ == 0) { | ||||||
|  |     this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT); | ||||||
|  |   } | ||||||
|  |   auto current_millis = millis(); | ||||||
|  |   return current_millis - check_millis >= this->timeout_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Extract, store and publish zone details LD2450 buffer | ||||||
|  | void LD2450Component::process_zone_(uint8_t *buffer) { | ||||||
|  |   uint8_t index, start; | ||||||
|  |   for (index = 0; index < MAX_ZONES; index++) { | ||||||
|  |     start = 12 + index * 8; | ||||||
|  |     this->zone_config_[index].x1 = ld2450::hex_to_signed_int(buffer, start); | ||||||
|  |     this->zone_config_[index].y1 = ld2450::hex_to_signed_int(buffer, start + 2); | ||||||
|  |     this->zone_config_[index].x2 = ld2450::hex_to_signed_int(buffer, start + 4); | ||||||
|  |     this->zone_config_[index].y2 = ld2450::hex_to_signed_int(buffer, start + 6); | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  |     // only one null check as all coordinates are required for a single zone | ||||||
|  |     if (this->zone_numbers_[index].x1 != nullptr) { | ||||||
|  |       this->zone_numbers_[index].x1->publish_state(this->zone_config_[index].x1); | ||||||
|  |       this->zone_numbers_[index].y1->publish_state(this->zone_config_[index].y1); | ||||||
|  |       this->zone_numbers_[index].x2->publish_state(this->zone_config_[index].x2); | ||||||
|  |       this->zone_numbers_[index].y2->publish_state(this->zone_config_[index].y2); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Read all info from LD2450 buffer | ||||||
|  | void LD2450Component::read_all_info() { | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   this->get_version_(); | ||||||
|  |   this->get_mac_(); | ||||||
|  |   this->query_target_tracking_mode_(); | ||||||
|  |   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 && this->baud_rate_select_->state != baud_rate) { | ||||||
|  |     this->baud_rate_select_->publish_state(baud_rate); | ||||||
|  |   } | ||||||
|  |   this->publish_zone_type(); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Read zone info from LD2450 buffer | ||||||
|  | void LD2450Component::query_zone_info() { | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   this->query_zone_(); | ||||||
|  |   this->set_config_mode_(false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Restart LD2450 and read all info from buffer | ||||||
|  | void LD2450Component::restart_and_read_all_info() { | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   this->restart_(); | ||||||
|  |   this->set_timeout(1500, [this]() { this->read_all_info(); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Send command with values to LD2450 | ||||||
|  | void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) { | ||||||
|  |   ESP_LOGV(TAG, "Sending command %02X", command); | ||||||
|  |   // frame header | ||||||
|  |   this->write_array(CMD_FRAME_HEADER, 4); | ||||||
|  |   // length bytes | ||||||
|  |   int len = 2; | ||||||
|  |   if (command_value != nullptr) { | ||||||
|  |     len += command_value_len; | ||||||
|  |   } | ||||||
|  |   this->write_byte(lowbyte(len)); | ||||||
|  |   this->write_byte(highbyte(len)); | ||||||
|  |   // command | ||||||
|  |   this->write_byte(lowbyte(command)); | ||||||
|  |   this->write_byte(highbyte(command)); | ||||||
|  |   // command value bytes | ||||||
|  |   if (command_value != nullptr) { | ||||||
|  |     for (int i = 0; i < command_value_len; i++) { | ||||||
|  |       this->write_byte(command_value[i]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // footer | ||||||
|  |   this->write_array(CMD_FRAME_END, 4); | ||||||
|  |   // FIXME to remove | ||||||
|  |   delay(50);  // NOLINT | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LD2450 Radar data message: | ||||||
|  | //  [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC] | ||||||
|  | //   Header       Target 1                  Target 2                  Target 3                  End | ||||||
|  | void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | ||||||
|  |   if (len < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes) | ||||||
|  |     ESP_LOGE(TAG, "Periodic data: invalid message length"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) {  // header | ||||||
|  |     ESP_LOGE(TAG, "Periodic data: invalid message header"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) {  // footer | ||||||
|  |     ESP_LOGE(TAG, "Periodic data: invalid message footer"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto current_millis = millis(); | ||||||
|  |   if (current_millis - this->last_periodic_millis_ < this->throttle_) { | ||||||
|  |     ESP_LOGV(TAG, "Throttling: %d", this->throttle_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->last_periodic_millis_ = current_millis; | ||||||
|  |  | ||||||
|  |   int16_t target_count = 0; | ||||||
|  |   int16_t still_target_count = 0; | ||||||
|  |   int16_t moving_target_count = 0; | ||||||
|  |   int16_t start = 0; | ||||||
|  |   int16_t val = 0; | ||||||
|  |   uint8_t index = 0; | ||||||
|  |   int16_t tx = 0; | ||||||
|  |   int16_t ty = 0; | ||||||
|  |   int16_t td = 0; | ||||||
|  |   int16_t ts = 0; | ||||||
|  |   int16_t angle = 0; | ||||||
|  |   std::string direction{}; | ||||||
|  |   bool is_moving = false; | ||||||
|  |  | ||||||
|  | #if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR) | ||||||
|  |   // Loop thru targets | ||||||
|  |   for (index = 0; index < MAX_TARGETS; index++) { | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |     // X | ||||||
|  |     start = TARGET_X + index * 8; | ||||||
|  |     is_moving = false; | ||||||
|  |     sensor::Sensor *sx = this->move_x_sensors_[index]; | ||||||
|  |     if (sx != nullptr) { | ||||||
|  |       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); | ||||||
|  |       tx = val; | ||||||
|  |       sx->publish_state(val); | ||||||
|  |     } | ||||||
|  |     // Y | ||||||
|  |     start = TARGET_Y + index * 8; | ||||||
|  |     sensor::Sensor *sy = this->move_y_sensors_[index]; | ||||||
|  |     if (sy != nullptr) { | ||||||
|  |       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); | ||||||
|  |       ty = val; | ||||||
|  |       sy->publish_state(val); | ||||||
|  |     } | ||||||
|  |     // RESOLUTION | ||||||
|  |     start = TARGET_RESOLUTION + index * 8; | ||||||
|  |     sensor::Sensor *sr = this->move_resolution_sensors_[index]; | ||||||
|  |     if (sr != nullptr) { | ||||||
|  |       val = (buffer[start + 1] << 8) | buffer[start]; | ||||||
|  |       sr->publish_state(val); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |     // SPEED | ||||||
|  |     start = TARGET_SPEED + index * 8; | ||||||
|  |     val = ld2450::decode_speed(buffer[start], buffer[start + 1]); | ||||||
|  |     ts = val; | ||||||
|  |     if (val) { | ||||||
|  |       is_moving = true; | ||||||
|  |       moving_target_count++; | ||||||
|  |     } | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |     sensor::Sensor *ss = this->move_speed_sensors_[index]; | ||||||
|  |     if (ss != nullptr) { | ||||||
|  |       ss->publish_state(val); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |     // DISTANCE | ||||||
|  |     val = (uint16_t) sqrt( | ||||||
|  |         pow(ld2450::decode_coordinate(buffer[TARGET_X + index * 8], buffer[(TARGET_X + index * 8) + 1]), 2) + | ||||||
|  |         pow(ld2450::decode_coordinate(buffer[TARGET_Y + index * 8], buffer[(TARGET_Y + index * 8) + 1]), 2)); | ||||||
|  |     td = val; | ||||||
|  |     if (val > 0) { | ||||||
|  |       target_count++; | ||||||
|  |     } | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |     sensor::Sensor *sd = this->move_distance_sensors_[index]; | ||||||
|  |     if (sd != nullptr) { | ||||||
|  |       sd->publish_state(val); | ||||||
|  |     } | ||||||
|  |     // ANGLE | ||||||
|  |     angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td)); | ||||||
|  |     if (tx > 0) { | ||||||
|  |       angle = angle * -1; | ||||||
|  |     } | ||||||
|  |     sensor::Sensor *sa = this->move_angle_sensors_[index]; | ||||||
|  |     if (sa != nullptr) { | ||||||
|  |       sa->publish_state(angle); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |     // DIRECTION | ||||||
|  |     direction = get_direction(ts); | ||||||
|  |     if (td == 0) { | ||||||
|  |       direction = "NA"; | ||||||
|  |     } | ||||||
|  |     text_sensor::TextSensor *tsd = this->direction_text_sensors_[index]; | ||||||
|  |     if (tsd != nullptr) { | ||||||
|  |       tsd->publish_state(direction); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     // Store target info for zone target count | ||||||
|  |     this->target_info_[index].x = tx; | ||||||
|  |     this->target_info_[index].y = ty; | ||||||
|  |     this->target_info_[index].is_moving = is_moving; | ||||||
|  |  | ||||||
|  |   }  // End loop thru targets | ||||||
|  |  | ||||||
|  |   still_target_count = target_count - moving_target_count; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   // Loop thru zones | ||||||
|  |   uint8_t zone_still_targets = 0; | ||||||
|  |   uint8_t zone_moving_targets = 0; | ||||||
|  |   uint8_t zone_all_targets = 0; | ||||||
|  |   for (index = 0; index < MAX_ZONES; index++) { | ||||||
|  |     zone_still_targets = this->count_targets_in_zone_(this->zone_config_[index], false); | ||||||
|  |     zone_moving_targets = this->count_targets_in_zone_(this->zone_config_[index], true); | ||||||
|  |     zone_all_targets = zone_still_targets + zone_moving_targets; | ||||||
|  |  | ||||||
|  |     // Publish Still Target Count in Zones | ||||||
|  |     sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index]; | ||||||
|  |     if (szstc != nullptr) { | ||||||
|  |       szstc->publish_state(zone_still_targets); | ||||||
|  |     } | ||||||
|  |     // Publish Moving Target Count in Zones | ||||||
|  |     sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index]; | ||||||
|  |     if (szmtc != nullptr) { | ||||||
|  |       szmtc->publish_state(zone_moving_targets); | ||||||
|  |     } | ||||||
|  |     // Publish All Target Count in Zones | ||||||
|  |     sensor::Sensor *sztc = this->zone_target_count_sensors_[index]; | ||||||
|  |     if (sztc != nullptr) { | ||||||
|  |       sztc->publish_state(zone_all_targets); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |   }  // End loop thru zones | ||||||
|  |  | ||||||
|  |   // Target Count | ||||||
|  |   if (this->target_count_sensor_ != nullptr) { | ||||||
|  |     this->target_count_sensor_->publish_state(target_count); | ||||||
|  |   } | ||||||
|  |   // Still Target Count | ||||||
|  |   if (this->still_target_count_sensor_ != nullptr) { | ||||||
|  |     this->still_target_count_sensor_->publish_state(still_target_count); | ||||||
|  |   } | ||||||
|  |   // Moving Target Count | ||||||
|  |   if (this->moving_target_count_sensor_ != nullptr) { | ||||||
|  |     this->moving_target_count_sensor_->publish_state(moving_target_count); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   // Target Presence | ||||||
|  |   if (this->target_binary_sensor_ != nullptr) { | ||||||
|  |     if (target_count > 0) { | ||||||
|  |       this->target_binary_sensor_->publish_state(true); | ||||||
|  |     } else { | ||||||
|  |       if (this->get_timeout_status_(this->presence_millis_)) { | ||||||
|  |         this->target_binary_sensor_->publish_state(false); | ||||||
|  |       } else { | ||||||
|  |         ESP_LOGV(TAG, "Clear presence waiting timeout: %d", this->timeout_); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Moving Target Presence | ||||||
|  |   if (this->moving_target_binary_sensor_ != nullptr) { | ||||||
|  |     if (moving_target_count > 0) { | ||||||
|  |       this->moving_target_binary_sensor_->publish_state(true); | ||||||
|  |     } else { | ||||||
|  |       if (this->get_timeout_status_(this->moving_presence_millis_)) { | ||||||
|  |         this->moving_target_binary_sensor_->publish_state(false); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Still Target Presence | ||||||
|  |   if (this->still_target_binary_sensor_ != nullptr) { | ||||||
|  |     if (still_target_count > 0) { | ||||||
|  |       this->still_target_binary_sensor_->publish_state(true); | ||||||
|  |     } else { | ||||||
|  |       if (this->get_timeout_status_(this->still_presence_millis_)) { | ||||||
|  |         this->still_target_binary_sensor_->publish_state(false); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   // For presence timeout check | ||||||
|  |   if (target_count > 0) { | ||||||
|  |     this->presence_millis_ = millis(); | ||||||
|  |   } | ||||||
|  |   if (moving_target_count > 0) { | ||||||
|  |     this->moving_presence_millis_ = millis(); | ||||||
|  |   } | ||||||
|  |   if (still_target_count > 0) { | ||||||
|  |     this->still_presence_millis_ = millis(); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | ||||||
|  |   ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); | ||||||
|  |   if (len < 10) { | ||||||
|  |     ESP_LOGE(TAG, "Ack data: invalid length"); | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // frame header | ||||||
|  |     ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]); | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (buffer[COMMAND_STATUS] != 0x01) { | ||||||
|  |     ESP_LOGE(TAG, "Ack data: invalid status"); | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (buffer[8] || buffer[9]) { | ||||||
|  |     ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]); | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   switch (buffer[COMMAND]) { | ||||||
|  |     case lowbyte(CMD_ENABLE_CONF): | ||||||
|  |       ESP_LOGV(TAG, "Got enable conf command"); | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_DISABLE_CONF): | ||||||
|  |       ESP_LOGV(TAG, "Got disable conf command"); | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_SET_BAUD_RATE): | ||||||
|  |       ESP_LOGV(TAG, "Got baud rate change command"); | ||||||
|  | #ifdef USE_SELECT | ||||||
|  |       if (this->baud_rate_select_ != nullptr) { | ||||||
|  |         ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_VERSION): | ||||||
|  |       this->version_ = ld2450::format_version(buffer); | ||||||
|  |       ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str()); | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |       if (this->version_text_sensor_ != nullptr) { | ||||||
|  |         this->version_text_sensor_->publish_state(this->version_); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_MAC): | ||||||
|  |       if (len < 20) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |       this->mac_ = ld2450::format_mac(buffer); | ||||||
|  |       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |       if (this->mac_text_sensor_ != nullptr) { | ||||||
|  |         this->mac_text_sensor_->publish_state(this->mac_); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |       if (this->bluetooth_switch_ != nullptr) { | ||||||
|  |         this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_BLUETOOTH): | ||||||
|  |       ESP_LOGV(TAG, "Got Bluetooth command"); | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_SINGLE_TARGET_MODE): | ||||||
|  |       ESP_LOGV(TAG, "Got single target conf command"); | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |       if (this->multi_target_switch_ != nullptr) { | ||||||
|  |         this->multi_target_switch_->publish_state(false); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_MULTI_TARGET_MODE): | ||||||
|  |       ESP_LOGV(TAG, "Got multi target conf command"); | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |       if (this->multi_target_switch_ != nullptr) { | ||||||
|  |         this->multi_target_switch_->publish_state(true); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_QUERY_TARGET_MODE): | ||||||
|  |       ESP_LOGV(TAG, "Got query target tracking mode command"); | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |       if (this->multi_target_switch_ != nullptr) { | ||||||
|  |         this->multi_target_switch_->publish_state(buffer[10] == 0x02); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_QUERY_ZONE): | ||||||
|  |       ESP_LOGV(TAG, "Got query zone conf command"); | ||||||
|  |       this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); | ||||||
|  |       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_->state.c_str()); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |       if (buffer[10] == 0x00) { | ||||||
|  |         ESP_LOGV(TAG, "Zone: Disabled"); | ||||||
|  |       } | ||||||
|  |       if (buffer[10] == 0x01) { | ||||||
|  |         ESP_LOGV(TAG, "Zone: Area detection"); | ||||||
|  |       } | ||||||
|  |       if (buffer[10] == 0x02) { | ||||||
|  |         ESP_LOGV(TAG, "Zone: Area filter"); | ||||||
|  |       } | ||||||
|  |       this->process_zone_(buffer); | ||||||
|  |       break; | ||||||
|  |     case lowbyte(CMD_SET_ZONE): | ||||||
|  |       ESP_LOGV(TAG, "Got set zone conf command"); | ||||||
|  |       this->query_zone_info(); | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Read LD2450 buffer data | ||||||
|  | void LD2450Component::readline_(int readch, uint8_t *buffer, uint8_t len) { | ||||||
|  |   if (readch < 0) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->buffer_pos_ < len - 1) { | ||||||
|  |     buffer[this->buffer_pos_++] = readch; | ||||||
|  |     buffer[this->buffer_pos_] = 0; | ||||||
|  |   } else { | ||||||
|  |     this->buffer_pos_ = 0; | ||||||
|  |   } | ||||||
|  |   if (this->buffer_pos_ < 4) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (buffer[this->buffer_pos_ - 2] == 0x55 && buffer[this->buffer_pos_ - 1] == 0xCC) { | ||||||
|  |     ESP_LOGV(TAG, "Handle periodic radar data"); | ||||||
|  |     this->handle_periodic_data_(buffer, this->buffer_pos_); | ||||||
|  |     this->buffer_pos_ = 0;  // Reset position index for next frame | ||||||
|  |   } else if (buffer[this->buffer_pos_ - 4] == 0x04 && buffer[this->buffer_pos_ - 3] == 0x03 && | ||||||
|  |              buffer[this->buffer_pos_ - 2] == 0x02 && buffer[this->buffer_pos_ - 1] == 0x01) { | ||||||
|  |     ESP_LOGV(TAG, "Handle command ack data"); | ||||||
|  |     if (this->handle_ack_data_(buffer, this->buffer_pos_)) { | ||||||
|  |       this->buffer_pos_ = 0;  // Reset position index for next frame | ||||||
|  |     } else { | ||||||
|  |       ESP_LOGV(TAG, "Command ack data invalid"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set Config Mode - Pre-requisite sending commands | ||||||
|  | void LD2450Component::set_config_mode_(bool enable) { | ||||||
|  |   uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; | ||||||
|  |   uint8_t cmd_value[2] = {0x01, 0x00}; | ||||||
|  |   this->send_command_(cmd, enable ? cmd_value : nullptr, 2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set Bluetooth Enable/Disable | ||||||
|  | void LD2450Component::set_bluetooth(bool enable) { | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   uint8_t enable_cmd_value[2] = {0x01, 0x00}; | ||||||
|  |   uint8_t disable_cmd_value[2] = {0x00, 0x00}; | ||||||
|  |   this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2); | ||||||
|  |   this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set Baud rate | ||||||
|  | void LD2450Component::set_baud_rate(const std::string &state) { | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00}; | ||||||
|  |   this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); | ||||||
|  |   this->set_timeout(200, [this]() { this->restart_(); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set Zone Type - one of: Disabled, Detection, Filter | ||||||
|  | void LD2450Component::set_zone_type(const std::string &state) { | ||||||
|  |   ESP_LOGV(TAG, "Set zone type: %s", state.c_str()); | ||||||
|  |   uint8_t zone_type = ZONE_TYPE_ENUM_TO_INT.at(state); | ||||||
|  |   this->zone_type_ = zone_type; | ||||||
|  |   this->send_set_zone_command_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Publish Zone Type to Select component | ||||||
|  | void LD2450Component::publish_zone_type() { | ||||||
|  | #ifdef USE_SELECT | ||||||
|  |   std::string zone_type = ZONE_TYPE_INT_TO_ENUM.at(static_cast<ZoneTypeStructure>(this->zone_type_)); | ||||||
|  |   if (this->zone_type_select_ != nullptr) { | ||||||
|  |     this->zone_type_select_->publish_state(zone_type); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set Single/Multiplayer target detection | ||||||
|  | void LD2450Component::set_multi_target(bool enable) { | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   uint8_t cmd = enable ? CMD_MULTI_TARGET_MODE : CMD_SINGLE_TARGET_MODE; | ||||||
|  |   this->send_command_(cmd, nullptr, 0); | ||||||
|  |   this->set_config_mode_(false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LD2450 factory reset | ||||||
|  | void LD2450Component::factory_reset() { | ||||||
|  |   this->set_config_mode_(true); | ||||||
|  |   this->send_command_(CMD_RESET, nullptr, 0); | ||||||
|  |   this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Restart LD2450 module | ||||||
|  | void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); } | ||||||
|  |  | ||||||
|  | // Get LD2450 firmware version | ||||||
|  | void LD2450Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); } | ||||||
|  |  | ||||||
|  | // Get LD2450 mac address | ||||||
|  | void LD2450Component::get_mac_() { | ||||||
|  |   uint8_t cmd_value[2] = {0x01, 0x00}; | ||||||
|  |   this->send_command_(CMD_MAC, cmd_value, 2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Query for target tracking mode | ||||||
|  | void LD2450Component::query_target_tracking_mode_() { this->send_command_(CMD_QUERY_TARGET_MODE, nullptr, 0); } | ||||||
|  |  | ||||||
|  | // Query for zone info | ||||||
|  | void LD2450Component::query_zone_() { this->send_command_(CMD_QUERY_ZONE, nullptr, 0); } | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | void LD2450Component::set_move_x_sensor(uint8_t target, sensor::Sensor *s) { this->move_x_sensors_[target] = s; } | ||||||
|  | void LD2450Component::set_move_y_sensor(uint8_t target, sensor::Sensor *s) { this->move_y_sensors_[target] = s; } | ||||||
|  | void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) { | ||||||
|  |   this->move_speed_sensors_[target] = s; | ||||||
|  | } | ||||||
|  | void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) { | ||||||
|  |   this->move_angle_sensors_[target] = s; | ||||||
|  | } | ||||||
|  | void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) { | ||||||
|  |   this->move_distance_sensors_[target] = s; | ||||||
|  | } | ||||||
|  | void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) { | ||||||
|  |   this->move_resolution_sensors_[target] = s; | ||||||
|  | } | ||||||
|  | void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) { | ||||||
|  |   this->zone_target_count_sensors_[zone] = s; | ||||||
|  | } | ||||||
|  | void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) { | ||||||
|  |   this->zone_still_target_count_sensors_[zone] = s; | ||||||
|  | } | ||||||
|  | void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s) { | ||||||
|  |   this->zone_moving_target_count_sensors_[zone] = s; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  | void LD2450Component::set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s) { | ||||||
|  |   this->direction_text_sensors_[target] = s; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | // Send Zone coordinates data to LD2450 | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  | void LD2450Component::set_zone_coordinate(uint8_t zone) { | ||||||
|  |   number::Number *x1sens = this->zone_numbers_[zone].x1; | ||||||
|  |   number::Number *y1sens = this->zone_numbers_[zone].y1; | ||||||
|  |   number::Number *x2sens = this->zone_numbers_[zone].x2; | ||||||
|  |   number::Number *y2sens = this->zone_numbers_[zone].y2; | ||||||
|  |   if (!x1sens->has_state() || !y1sens->has_state() || !x2sens->has_state() || !y2sens->has_state()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->zone_config_[zone].x1 = static_cast<int>(x1sens->state); | ||||||
|  |   this->zone_config_[zone].y1 = static_cast<int>(y1sens->state); | ||||||
|  |   this->zone_config_[zone].x2 = static_cast<int>(x2sens->state); | ||||||
|  |   this->zone_config_[zone].y2 = static_cast<int>(y2sens->state); | ||||||
|  |   this->send_set_zone_command_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void LD2450Component::set_zone_numbers(uint8_t zone, number::Number *x1, number::Number *y1, number::Number *x2, | ||||||
|  |                                        number::Number *y2) { | ||||||
|  |   if (zone < MAX_ZONES) { | ||||||
|  |     this->zone_numbers_[zone].x1 = x1; | ||||||
|  |     this->zone_numbers_[zone].y1 = y1; | ||||||
|  |     this->zone_numbers_[zone].x2 = x2; | ||||||
|  |     this->zone_numbers_[zone].y2 = y2; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | // Set Presence Timeout load and save from flash | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  | void LD2450Component::set_presence_timeout() { | ||||||
|  |   if (this->presence_timeout_number_ != nullptr) { | ||||||
|  |     if (this->presence_timeout_number_->state == 0) { | ||||||
|  |       float timeout = this->restore_from_flash_(); | ||||||
|  |       this->presence_timeout_number_->publish_state(timeout); | ||||||
|  |       this->timeout_ = ld2450::convert_seconds_to_ms(timeout); | ||||||
|  |     } | ||||||
|  |     if (this->presence_timeout_number_->has_state()) { | ||||||
|  |       this->save_to_flash_(this->presence_timeout_number_->state); | ||||||
|  |       this->timeout_ = ld2450::convert_seconds_to_ms(this->presence_timeout_number_->state); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Save Presence Timeout to flash | ||||||
|  | void LD2450Component::save_to_flash_(float value) { this->pref_.save(&value); } | ||||||
|  |  | ||||||
|  | // Load Presence Timeout from flash | ||||||
|  | float LD2450Component::restore_from_flash_() { | ||||||
|  |   float value; | ||||||
|  |   if (!this->pref_.load(&value)) { | ||||||
|  |     value = DEFAULT_PRESENCE_TIMEOUT; | ||||||
|  |   } | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										234
									
								
								esphome/components/ld2450/ld2450.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								esphome/components/ld2450/ld2450.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,234 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <iomanip> | ||||||
|  | #include <map> | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/preferences.h" | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  | #include "esphome/components/number/number.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | #include "esphome/components/button/button.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SELECT | ||||||
|  | #include "esphome/components/select/select.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  | #include "esphome/components/text_sensor/text_sensor.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifndef M_PI | ||||||
|  | #define M_PI 3.14 | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | // Constants | ||||||
|  | static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5;  // Timeout to reset presense status 5 sec. | ||||||
|  | static const uint8_t MAX_LINE_LENGTH = 60;          // Max characters for serial buffer | ||||||
|  | static const uint8_t MAX_TARGETS = 3;               // Max 3 Targets in LD2450 | ||||||
|  | static const uint8_t MAX_ZONES = 3;                 // Max 3 Zones in LD2450 | ||||||
|  |  | ||||||
|  | // Target coordinate struct | ||||||
|  | struct Target { | ||||||
|  |   int16_t x; | ||||||
|  |   int16_t y; | ||||||
|  |   bool is_moving; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Zone coordinate struct | ||||||
|  | struct Zone { | ||||||
|  |   int16_t x1 = 0; | ||||||
|  |   int16_t y1 = 0; | ||||||
|  |   int16_t x2 = 0; | ||||||
|  |   int16_t y2 = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  | struct ZoneOfNumbers { | ||||||
|  |   number::Number *x1 = nullptr; | ||||||
|  |   number::Number *y1 = nullptr; | ||||||
|  |   number::Number *x2 = nullptr; | ||||||
|  |   number::Number *y2 = nullptr; | ||||||
|  | }; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | enum BaudRateStructure : uint8_t { | ||||||
|  |   BAUD_RATE_9600 = 1, | ||||||
|  |   BAUD_RATE_19200 = 2, | ||||||
|  |   BAUD_RATE_38400 = 3, | ||||||
|  |   BAUD_RATE_57600 = 4, | ||||||
|  |   BAUD_RATE_115200 = 5, | ||||||
|  |   BAUD_RATE_230400 = 6, | ||||||
|  |   BAUD_RATE_256000 = 7, | ||||||
|  |   BAUD_RATE_460800 = 8 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Convert baud rate enum to int | ||||||
|  | static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{ | ||||||
|  |     {"9600", BAUD_RATE_9600},     {"19200", BAUD_RATE_19200},   {"38400", BAUD_RATE_38400}, | ||||||
|  |     {"57600", BAUD_RATE_57600},   {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400}, | ||||||
|  |     {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}}; | ||||||
|  |  | ||||||
|  | // Zone type struct | ||||||
|  | enum ZoneTypeStructure : uint8_t { ZONE_DISABLED = 0, ZONE_DETECTION = 1, ZONE_FILTER = 2 }; | ||||||
|  |  | ||||||
|  | // Convert zone type int to enum | ||||||
|  | static const std::map<ZoneTypeStructure, std::string> ZONE_TYPE_INT_TO_ENUM{ | ||||||
|  |     {ZONE_DISABLED, "Disabled"}, {ZONE_DETECTION, "Detection"}, {ZONE_FILTER, "Filter"}}; | ||||||
|  |  | ||||||
|  | // Convert zone type enum to int | ||||||
|  | static const std::map<std::string, uint8_t> ZONE_TYPE_ENUM_TO_INT{ | ||||||
|  |     {"Disabled", ZONE_DISABLED}, {"Detection", ZONE_DETECTION}, {"Filter", ZONE_FILTER}}; | ||||||
|  |  | ||||||
|  | // LD2450 serial command header & footer | ||||||
|  | static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA}; | ||||||
|  | static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01}; | ||||||
|  |  | ||||||
|  | enum PeriodicDataStructure : uint8_t { | ||||||
|  |   TARGET_X = 4, | ||||||
|  |   TARGET_Y = 6, | ||||||
|  |   TARGET_SPEED = 8, | ||||||
|  |   TARGET_RESOLUTION = 10, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 }; | ||||||
|  |  | ||||||
|  | enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 }; | ||||||
|  |  | ||||||
|  | class LD2450Component : public Component, public uart::UARTDevice { | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   SUB_SENSOR(target_count) | ||||||
|  |   SUB_SENSOR(still_target_count) | ||||||
|  |   SUB_SENSOR(moving_target_count) | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   SUB_BINARY_SENSOR(target) | ||||||
|  |   SUB_BINARY_SENSOR(moving_target) | ||||||
|  |   SUB_BINARY_SENSOR(still_target) | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   SUB_TEXT_SENSOR(version) | ||||||
|  |   SUB_TEXT_SENSOR(mac) | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SELECT | ||||||
|  |   SUB_SELECT(baud_rate) | ||||||
|  |   SUB_SELECT(zone_type) | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |   SUB_SWITCH(bluetooth) | ||||||
|  |   SUB_SWITCH(multi_target) | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   SUB_BUTTON(reset) | ||||||
|  |   SUB_BUTTON(restart) | ||||||
|  | #endif | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  |   SUB_NUMBER(presence_timeout) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   LD2450Component(); | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   void loop() override; | ||||||
|  |   void set_presence_timeout(); | ||||||
|  |   void set_throttle(uint16_t value) { this->throttle_ = value; }; | ||||||
|  |   void read_all_info(); | ||||||
|  |   void query_zone_info(); | ||||||
|  |   void restart_and_read_all_info(); | ||||||
|  |   void set_bluetooth(bool enable); | ||||||
|  |   void set_multi_target(bool enable); | ||||||
|  |   void set_baud_rate(const std::string &state); | ||||||
|  |   void set_zone_type(const std::string &state); | ||||||
|  |   void publish_zone_type(); | ||||||
|  |   void factory_reset(); | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   void set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  |   void set_zone_coordinate(uint8_t zone); | ||||||
|  |   void set_zone_numbers(uint8_t zone, number::Number *x1, number::Number *y1, number::Number *x2, number::Number *y2); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   void set_move_x_sensor(uint8_t target, sensor::Sensor *s); | ||||||
|  |   void set_move_y_sensor(uint8_t target, sensor::Sensor *s); | ||||||
|  |   void set_move_speed_sensor(uint8_t target, sensor::Sensor *s); | ||||||
|  |   void set_move_angle_sensor(uint8_t target, sensor::Sensor *s); | ||||||
|  |   void set_move_distance_sensor(uint8_t target, sensor::Sensor *s); | ||||||
|  |   void set_move_resolution_sensor(uint8_t target, sensor::Sensor *s); | ||||||
|  |   void set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s); | ||||||
|  |   void set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s); | ||||||
|  |   void set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s); | ||||||
|  | #endif | ||||||
|  |   void reset_radar_zone(); | ||||||
|  |   void set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2, int32_t zone1_y2, | ||||||
|  |                       int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, int32_t zone2_y2, int32_t zone3_x1, | ||||||
|  |                       int32_t zone3_y1, int32_t zone3_x2, int32_t zone3_y2); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len); | ||||||
|  |   void set_config_mode_(bool enable); | ||||||
|  |   void handle_periodic_data_(uint8_t *buffer, uint8_t len); | ||||||
|  |   bool handle_ack_data_(uint8_t *buffer, uint8_t len); | ||||||
|  |   void process_zone_(uint8_t *buffer); | ||||||
|  |   void readline_(int readch, uint8_t *buffer, uint8_t len); | ||||||
|  |   void get_version_(); | ||||||
|  |   void get_mac_(); | ||||||
|  |   void query_target_tracking_mode_(); | ||||||
|  |   void query_zone_(); | ||||||
|  |   void restart_(); | ||||||
|  |   void send_set_zone_command_(); | ||||||
|  |   void save_to_flash_(float value); | ||||||
|  |   float restore_from_flash_(); | ||||||
|  |   bool get_timeout_status_(uint32_t check_millis); | ||||||
|  |   uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving); | ||||||
|  |  | ||||||
|  |   Target target_info_[MAX_TARGETS]; | ||||||
|  |   Zone zone_config_[MAX_ZONES]; | ||||||
|  |   uint8_t buffer_pos_ = 0;  // where to resume processing/populating buffer | ||||||
|  |   uint8_t buffer_data_[MAX_LINE_LENGTH]; | ||||||
|  |   uint32_t last_periodic_millis_ = 0; | ||||||
|  |   uint32_t presence_millis_ = 0; | ||||||
|  |   uint32_t still_presence_millis_ = 0; | ||||||
|  |   uint32_t moving_presence_millis_ = 0; | ||||||
|  |   uint16_t throttle_ = 0; | ||||||
|  |   uint16_t timeout_ = 5; | ||||||
|  |   uint8_t zone_type_ = 0; | ||||||
|  |   std::string version_{}; | ||||||
|  |   std::string mac_{}; | ||||||
|  | #ifdef USE_NUMBER | ||||||
|  |   ESPPreferenceObject pref_;  // only used when numbers are in use | ||||||
|  |   ZoneOfNumbers zone_numbers_[MAX_ZONES]; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   std::vector<sensor::Sensor *> move_x_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||||
|  |   std::vector<sensor::Sensor *> move_y_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||||
|  |   std::vector<sensor::Sensor *> move_speed_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||||
|  |   std::vector<sensor::Sensor *> move_angle_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||||
|  |   std::vector<sensor::Sensor *> move_distance_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||||
|  |   std::vector<sensor::Sensor *> move_resolution_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||||
|  |   std::vector<sensor::Sensor *> zone_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES); | ||||||
|  |   std::vector<sensor::Sensor *> zone_still_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES); | ||||||
|  |   std::vector<sensor::Sensor *> zone_moving_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   std::vector<text_sensor::TextSensor *> direction_text_sensors_ = std::vector<text_sensor::TextSensor *>(3); | ||||||
|  | #endif | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										121
									
								
								esphome/components/ld2450/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								esphome/components/ld2450/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import number | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_DISTANCE, | ||||||
|  |     ENTITY_CATEGORY_CONFIG, | ||||||
|  |     ICON_TIMELAPSE, | ||||||
|  |     UNIT_MILLIMETER, | ||||||
|  |     UNIT_SECOND, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||||
|  |  | ||||||
|  | CONF_PRESENCE_TIMEOUT = "presence_timeout" | ||||||
|  | CONF_X1 = "x1" | ||||||
|  | CONF_X2 = "x2" | ||||||
|  | CONF_Y1 = "y1" | ||||||
|  | CONF_Y2 = "y2" | ||||||
|  | ICON_ARROW_BOTTOM_RIGHT = "mdi:arrow-bottom-right" | ||||||
|  | ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE = "mdi:arrow-bottom-right-bold-box-outline" | ||||||
|  | ICON_ARROW_TOP_LEFT = "mdi:arrow-top-left" | ||||||
|  | ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE = "mdi:arrow-top-left-bold-box-outline" | ||||||
|  | MAX_ZONES = 3 | ||||||
|  |  | ||||||
|  | PresenceTimeoutNumber = ld2450_ns.class_("PresenceTimeoutNumber", number.Number) | ||||||
|  | ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |         cv.Required(CONF_PRESENCE_TIMEOUT): number.number_schema( | ||||||
|  |             PresenceTimeoutNumber, | ||||||
|  |             unit_of_measurement=UNIT_SECOND, | ||||||
|  |             entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |             icon=ICON_TIMELAPSE, | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = CONFIG_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.Optional(f"zone_{n + 1}"): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_X1): number.number_schema( | ||||||
|  |                     ZoneCoordinateNumber, | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |                     icon=ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE, | ||||||
|  |                 ), | ||||||
|  |                 cv.Required(CONF_Y1): number.number_schema( | ||||||
|  |                     ZoneCoordinateNumber, | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |                     icon=ICON_ARROW_TOP_LEFT, | ||||||
|  |                 ), | ||||||
|  |                 cv.Required(CONF_X2): number.number_schema( | ||||||
|  |                     ZoneCoordinateNumber, | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |                     icon=ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE, | ||||||
|  |                 ), | ||||||
|  |                 cv.Required(CONF_Y2): number.number_schema( | ||||||
|  |                     ZoneCoordinateNumber, | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |                     icon=ICON_ARROW_BOTTOM_RIGHT, | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         for n in range(MAX_ZONES) | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||||
|  |     if presence_timeout_config := config.get(CONF_PRESENCE_TIMEOUT): | ||||||
|  |         n = await number.new_number( | ||||||
|  |             presence_timeout_config, | ||||||
|  |             min_value=0, | ||||||
|  |             max_value=3600, | ||||||
|  |             step=1, | ||||||
|  |         ) | ||||||
|  |         await cg.register_parented(n, config[CONF_LD2450_ID]) | ||||||
|  |         cg.add(ld2450_component.set_presence_timeout_number(n)) | ||||||
|  |     for zone_num in range(MAX_ZONES): | ||||||
|  |         if zone_conf := config.get(f"zone_{zone_num + 1}"): | ||||||
|  |             zone_x1_config = zone_conf.get(CONF_X1) | ||||||
|  |             x1 = cg.new_Pvariable(zone_x1_config[CONF_ID], zone_num) | ||||||
|  |             await number.register_number( | ||||||
|  |                 x1, zone_x1_config, min_value=-4860, max_value=4860, step=1 | ||||||
|  |             ) | ||||||
|  |             await cg.register_parented(x1, config[CONF_LD2450_ID]) | ||||||
|  |  | ||||||
|  |             zone_y1_config = zone_conf.get(CONF_Y1) | ||||||
|  |             y1 = cg.new_Pvariable(zone_y1_config[CONF_ID], zone_num) | ||||||
|  |             await number.register_number( | ||||||
|  |                 y1, zone_y1_config, min_value=0, max_value=7560, step=1 | ||||||
|  |             ) | ||||||
|  |             await cg.register_parented(y1, config[CONF_LD2450_ID]) | ||||||
|  |  | ||||||
|  |             zone_x2_config = zone_conf.get(CONF_X2) | ||||||
|  |             x2 = cg.new_Pvariable(zone_x2_config[CONF_ID], zone_num) | ||||||
|  |             await number.register_number( | ||||||
|  |                 x2, zone_x2_config, min_value=-4860, max_value=4860, step=1 | ||||||
|  |             ) | ||||||
|  |             await cg.register_parented(x2, config[CONF_LD2450_ID]) | ||||||
|  |  | ||||||
|  |             zone_y2_config = zone_conf.get(CONF_Y2) | ||||||
|  |             y2 = cg.new_Pvariable(zone_y2_config[CONF_ID], zone_num) | ||||||
|  |             await number.register_number( | ||||||
|  |                 y2, zone_y2_config, min_value=0, max_value=7560, step=1 | ||||||
|  |             ) | ||||||
|  |             await cg.register_parented(y2, config[CONF_LD2450_ID]) | ||||||
|  |  | ||||||
|  |             cg.add(ld2450_component.set_zone_numbers(zone_num, x1, y1, x2, y2)) | ||||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/number/presence_timeout_number.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/number/presence_timeout_number.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #include "presence_timeout_number.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | void PresenceTimeoutNumber::control(float value) { | ||||||
|  |   this->publish_state(value); | ||||||
|  |   this->parent_->set_presence_timeout(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/number/presence_timeout_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/number/presence_timeout_number.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/number/number.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class PresenceTimeoutNumber : public number::Number, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   PresenceTimeoutNumber() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void control(float value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										14
									
								
								esphome/components/ld2450/number/zone_coordinate_number.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/ld2450/number/zone_coordinate_number.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #include "zone_coordinate_number.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | ZoneCoordinateNumber::ZoneCoordinateNumber(uint8_t zone) : zone_(zone) {} | ||||||
|  |  | ||||||
|  | void ZoneCoordinateNumber::control(float value) { | ||||||
|  |   this->publish_state(value); | ||||||
|  |   this->parent_->set_zone_coordinate(this->zone_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										19
									
								
								esphome/components/ld2450/number/zone_coordinate_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/ld2450/number/zone_coordinate_number.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/number/number.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class ZoneCoordinateNumber : public number::Number, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   ZoneCoordinateNumber(uint8_t zone); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t zone_; | ||||||
|  |   void control(float value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										56
									
								
								esphome/components/ld2450/select/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/ld2450/select/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | 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 .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||||
|  |  | ||||||
|  | CONF_ZONE_TYPE = "zone_type" | ||||||
|  |  | ||||||
|  | BaudRateSelect = ld2450_ns.class_("BaudRateSelect", select.Select) | ||||||
|  | ZoneTypeSelect = ld2450_ns.class_("ZoneTypeSelect", select.Select) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = { | ||||||
|  |     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |     cv.Optional(CONF_BAUD_RATE): select.select_schema( | ||||||
|  |         BaudRateSelect, | ||||||
|  |         entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |         icon=ICON_THERMOMETER, | ||||||
|  |     ), | ||||||
|  |     cv.Optional(CONF_ZONE_TYPE): select.select_schema( | ||||||
|  |         ZoneTypeSelect, | ||||||
|  |         entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |         icon=ICON_THERMOMETER, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||||
|  |     if baud_rate_config := config.get(CONF_BAUD_RATE): | ||||||
|  |         s = await select.new_select( | ||||||
|  |             baud_rate_config, | ||||||
|  |             options=[ | ||||||
|  |                 "9600", | ||||||
|  |                 "19200", | ||||||
|  |                 "38400", | ||||||
|  |                 "57600", | ||||||
|  |                 "115200", | ||||||
|  |                 "230400", | ||||||
|  |                 "256000", | ||||||
|  |                 "460800", | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|  |         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||||
|  |         cg.add(ld2450_component.set_baud_rate_select(s)) | ||||||
|  |     if zone_type_config := config.get(CONF_ZONE_TYPE): | ||||||
|  |         s = await select.new_select( | ||||||
|  |             zone_type_config, | ||||||
|  |             options=[ | ||||||
|  |                 "Disabled", | ||||||
|  |                 "Detection", | ||||||
|  |                 "Filter", | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|  |         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||||
|  |         cg.add(ld2450_component.set_zone_type_select(s)) | ||||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/select/baud_rate_select.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/select/baud_rate_select.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #include "baud_rate_select.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | void BaudRateSelect::control(const std::string &value) { | ||||||
|  |   this->publish_state(value); | ||||||
|  |   this->parent_->set_baud_rate(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/select/baud_rate_select.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/select/baud_rate_select.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/select/select.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class BaudRateSelect : public select::Select, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   BaudRateSelect() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void control(const std::string &value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/select/zone_type_select.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/select/zone_type_select.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #include "zone_type_select.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | void ZoneTypeSelect::control(const std::string &value) { | ||||||
|  |   this->publish_state(value); | ||||||
|  |   this->parent_->set_zone_type(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/select/zone_type_select.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/select/zone_type_select.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/select/select.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class ZoneTypeSelect : public select::Select, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   ZoneTypeSelect() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void control(const std::string &value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										156
									
								
								esphome/components/ld2450/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								esphome/components/ld2450/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | 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, | ||||||
|  |     DEVICE_CLASS_DISTANCE, | ||||||
|  |     DEVICE_CLASS_SPEED, | ||||||
|  |     UNIT_DEGREES, | ||||||
|  |     UNIT_MILLIMETER, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import CONF_LD2450_ID, LD2450Component | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["ld2450"] | ||||||
|  |  | ||||||
|  | CONF_MOVING_TARGET_COUNT = "moving_target_count" | ||||||
|  | CONF_STILL_TARGET_COUNT = "still_target_count" | ||||||
|  | CONF_TARGET_COUNT = "target_count" | ||||||
|  | CONF_X = "x" | ||||||
|  | CONF_Y = "y" | ||||||
|  |  | ||||||
|  | ICON_ACCOUNT_GROUP = "mdi:account-group" | ||||||
|  | ICON_ACCOUNT_SWITCH = "mdi:account-switch" | ||||||
|  | ICON_ALPHA_X_BOX_OUTLINE = "mdi:alpha-x-box-outline" | ||||||
|  | ICON_ALPHA_Y_BOX_OUTLINE = "mdi:alpha-y-box-outline" | ||||||
|  | ICON_FORMAT_TEXT_ROTATION_ANGLE_UP = "mdi:format-text-rotation-angle-up" | ||||||
|  | ICON_HUMAN_GREETING_PROXIMITY = "mdi:human-greeting-proximity" | ||||||
|  | ICON_MAP_MARKER_ACCOUNT = "mdi:map-marker-account" | ||||||
|  | ICON_MAP_MARKER_DISTANCE = "mdi:map-marker-distance" | ||||||
|  | ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE = "mdi:relation-zero-or-one-to-zero-or-one" | ||||||
|  | ICON_SPEEDOMETER_SLOW = "mdi:speedometer-slow" | ||||||
|  |  | ||||||
|  | MAX_TARGETS = 3 | ||||||
|  | MAX_ZONES = 3 | ||||||
|  |  | ||||||
|  | UNIT_MILLIMETER_PER_SECOND = "mm/s" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |         cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( | ||||||
|  |             icon=ICON_ACCOUNT_GROUP, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema( | ||||||
|  |             icon=ICON_HUMAN_GREETING_PROXIMITY, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema( | ||||||
|  |             icon=ICON_ACCOUNT_SWITCH, | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = CONFIG_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.Optional(f"target_{n + 1}"): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_X): sensor.sensor_schema( | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     icon=ICON_ALPHA_X_BOX_OUTLINE, | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_Y): sensor.sensor_schema( | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     icon=ICON_ALPHA_Y_BOX_OUTLINE, | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_SPEED): sensor.sensor_schema( | ||||||
|  |                     device_class=DEVICE_CLASS_SPEED, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER_PER_SECOND, | ||||||
|  |                     icon=ICON_SPEEDOMETER_SLOW, | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_ANGLE): sensor.sensor_schema( | ||||||
|  |                     unit_of_measurement=UNIT_DEGREES, | ||||||
|  |                     icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP, | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_DISTANCE): sensor.sensor_schema( | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     icon=ICON_MAP_MARKER_DISTANCE, | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_RESOLUTION): sensor.sensor_schema( | ||||||
|  |                     device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |                     unit_of_measurement=UNIT_MILLIMETER, | ||||||
|  |                     icon=ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE, | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         for n in range(MAX_TARGETS) | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         cv.Optional(f"zone_{n + 1}"): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( | ||||||
|  |                     icon=ICON_MAP_MARKER_ACCOUNT, | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema( | ||||||
|  |                     icon=ICON_MAP_MARKER_ACCOUNT, | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema( | ||||||
|  |                     icon=ICON_MAP_MARKER_ACCOUNT, | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         for n in range(MAX_ZONES) | ||||||
|  |     }, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||||
|  |  | ||||||
|  |     if target_count_config := config.get(CONF_TARGET_COUNT): | ||||||
|  |         sens = await sensor.new_sensor(target_count_config) | ||||||
|  |         cg.add(ld2450_component.set_target_count_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if still_target_count_config := config.get(CONF_STILL_TARGET_COUNT): | ||||||
|  |         sens = await sensor.new_sensor(still_target_count_config) | ||||||
|  |         cg.add(ld2450_component.set_still_target_count_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if moving_target_count_config := config.get(CONF_MOVING_TARGET_COUNT): | ||||||
|  |         sens = await sensor.new_sensor(moving_target_count_config) | ||||||
|  |         cg.add(ld2450_component.set_moving_target_count_sensor(sens)) | ||||||
|  |     for n in range(MAX_TARGETS): | ||||||
|  |         if target_conf := config.get(f"target_{n + 1}"): | ||||||
|  |             if x_config := target_conf.get(CONF_X): | ||||||
|  |                 sens = await sensor.new_sensor(x_config) | ||||||
|  |                 cg.add(ld2450_component.set_move_x_sensor(n, sens)) | ||||||
|  |             if y_config := target_conf.get(CONF_Y): | ||||||
|  |                 sens = await sensor.new_sensor(y_config) | ||||||
|  |                 cg.add(ld2450_component.set_move_y_sensor(n, sens)) | ||||||
|  |             if speed_config := target_conf.get(CONF_SPEED): | ||||||
|  |                 sens = await sensor.new_sensor(speed_config) | ||||||
|  |                 cg.add(ld2450_component.set_move_speed_sensor(n, sens)) | ||||||
|  |             if angle_config := target_conf.get(CONF_ANGLE): | ||||||
|  |                 sens = await sensor.new_sensor(angle_config) | ||||||
|  |                 cg.add(ld2450_component.set_move_angle_sensor(n, sens)) | ||||||
|  |             if distance_config := target_conf.get(CONF_DISTANCE): | ||||||
|  |                 sens = await sensor.new_sensor(distance_config) | ||||||
|  |                 cg.add(ld2450_component.set_move_distance_sensor(n, sens)) | ||||||
|  |             if resolution_config := target_conf.get(CONF_RESOLUTION): | ||||||
|  |                 sens = await sensor.new_sensor(resolution_config) | ||||||
|  |                 cg.add(ld2450_component.set_move_resolution_sensor(n, sens)) | ||||||
|  |     for n in range(MAX_ZONES): | ||||||
|  |         if zone_config := config.get(f"zone_{n + 1}"): | ||||||
|  |             if target_count_config := zone_config.get(CONF_TARGET_COUNT): | ||||||
|  |                 sens = await sensor.new_sensor(target_count_config) | ||||||
|  |                 cg.add(ld2450_component.set_zone_target_count_sensor(n, sens)) | ||||||
|  |             if still_target_count_config := zone_config.get(CONF_STILL_TARGET_COUNT): | ||||||
|  |                 sens = await sensor.new_sensor(still_target_count_config) | ||||||
|  |                 cg.add(ld2450_component.set_zone_still_target_count_sensor(n, sens)) | ||||||
|  |             if moving_target_count_config := zone_config.get(CONF_MOVING_TARGET_COUNT): | ||||||
|  |                 sens = await sensor.new_sensor(moving_target_count_config) | ||||||
|  |                 cg.add(ld2450_component.set_zone_moving_target_count_sensor(n, sens)) | ||||||
							
								
								
									
										45
									
								
								esphome/components/ld2450/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/ld2450/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import switch | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     DEVICE_CLASS_SWITCH, | ||||||
|  |     ENTITY_CATEGORY_CONFIG, | ||||||
|  |     ICON_BLUETOOTH, | ||||||
|  |     ICON_PULSE, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||||
|  |  | ||||||
|  | BluetoothSwitch = ld2450_ns.class_("BluetoothSwitch", switch.Switch) | ||||||
|  | MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch) | ||||||
|  |  | ||||||
|  | CONF_BLUETOOTH = "bluetooth" | ||||||
|  | CONF_MULTI_TARGET = "multi_target" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = { | ||||||
|  |     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |     cv.Optional(CONF_BLUETOOTH): switch.switch_schema( | ||||||
|  |         BluetoothSwitch, | ||||||
|  |         device_class=DEVICE_CLASS_SWITCH, | ||||||
|  |         entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |         icon=ICON_BLUETOOTH, | ||||||
|  |     ), | ||||||
|  |     cv.Optional(CONF_MULTI_TARGET): switch.switch_schema( | ||||||
|  |         MultiTargetSwitch, | ||||||
|  |         device_class=DEVICE_CLASS_SWITCH, | ||||||
|  |         entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |         icon=ICON_PULSE, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||||
|  |     if bluetooth_config := config.get(CONF_BLUETOOTH): | ||||||
|  |         s = await switch.new_switch(bluetooth_config) | ||||||
|  |         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||||
|  |         cg.add(ld2450_component.set_bluetooth_switch(s)) | ||||||
|  |     if multi_target_config := config.get(CONF_MULTI_TARGET): | ||||||
|  |         s = await switch.new_switch(multi_target_config) | ||||||
|  |         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||||
|  |         cg.add(ld2450_component.set_multi_target_switch(s)) | ||||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/switch/bluetooth_switch.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/switch/bluetooth_switch.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #include "bluetooth_switch.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | void BluetoothSwitch::write_state(bool state) { | ||||||
|  |   this->publish_state(state); | ||||||
|  |   this->parent_->set_bluetooth(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/switch/bluetooth_switch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/switch/bluetooth_switch.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class BluetoothSwitch : public switch_::Switch, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   BluetoothSwitch() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/switch/multi_target_switch.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/switch/multi_target_switch.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #include "multi_target_switch.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | void MultiTargetSwitch::write_state(bool state) { | ||||||
|  |   this->publish_state(state); | ||||||
|  |   this->parent_->set_multi_target(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/switch/multi_target_switch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/switch/multi_target_switch.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #include "../ld2450.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ld2450 { | ||||||
|  |  | ||||||
|  | class MultiTargetSwitch : public switch_::Switch, public Parented<LD2450Component> { | ||||||
|  |  public: | ||||||
|  |   MultiTargetSwitch() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ld2450 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										62
									
								
								esphome/components/ld2450/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/ld2450/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import text_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DIRECTION, | ||||||
|  |     CONF_MAC_ADDRESS, | ||||||
|  |     CONF_VERSION, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ENTITY_CATEGORY_NONE, | ||||||
|  |     ICON_BLUETOOTH, | ||||||
|  |     ICON_CHIP, | ||||||
|  |     ICON_SIGN_DIRECTION, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import CONF_LD2450_ID, LD2450Component | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["ld2450"] | ||||||
|  |  | ||||||
|  | MAX_TARGETS = 3 | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||||
|  |         cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( | ||||||
|  |             entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |             icon=ICON_CHIP, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( | ||||||
|  |             entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |             icon=ICON_BLUETOOTH, | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = CONFIG_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.Optional(f"target_{n + 1}"): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_DIRECTION): text_sensor.text_sensor_schema( | ||||||
|  |                     entity_category=ENTITY_CATEGORY_NONE, | ||||||
|  |                     icon=ICON_SIGN_DIRECTION, | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         for n in range(MAX_TARGETS) | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||||
|  |     if version_config := config.get(CONF_VERSION): | ||||||
|  |         sens = await text_sensor.new_text_sensor(version_config) | ||||||
|  |         cg.add(ld2450_component.set_version_text_sensor(sens)) | ||||||
|  |     if mac_address_config := config.get(CONF_MAC_ADDRESS): | ||||||
|  |         sens = await text_sensor.new_text_sensor(mac_address_config) | ||||||
|  |         cg.add(ld2450_component.set_mac_text_sensor(sens)) | ||||||
|  |     for n in range(MAX_TARGETS): | ||||||
|  |         if direction_conf := config.get(f"target_{n + 1}"): | ||||||
|  |             if direction_config := direction_conf.get(CONF_DIRECTION): | ||||||
|  |                 sens = await text_sensor.new_text_sensor(direction_config) | ||||||
|  |                 cg.add(ld2450_component.set_direction_text_sensor(n, sens)) | ||||||
| @@ -462,8 +462,6 @@ CONF_LVGL_ID = "lvgl_id" | |||||||
| CONF_LONG_MODE = "long_mode" | CONF_LONG_MODE = "long_mode" | ||||||
| CONF_MSGBOXES = "msgboxes" | CONF_MSGBOXES = "msgboxes" | ||||||
| CONF_OBJ = "obj" | CONF_OBJ = "obj" | ||||||
| CONF_OFFSET_X = "offset_x" |  | ||||||
| CONF_OFFSET_Y = "offset_y" |  | ||||||
| CONF_ONE_CHECKED = "one_checked" | CONF_ONE_CHECKED = "one_checked" | ||||||
| CONF_ONE_LINE = "one_line" | CONF_ONE_LINE = "one_line" | ||||||
| CONF_ON_PAUSE = "on_pause" | CONF_ON_PAUSE = "on_pause" | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, ui | |||||||
|   const auto *gd = fe->get_glyph_data(unicode_letter); |   const auto *gd = fe->get_glyph_data(unicode_letter); | ||||||
|   if (gd == nullptr) |   if (gd == nullptr) | ||||||
|     return false; |     return false; | ||||||
|   dsc->adv_w = gd->offset_x + gd->width; |   dsc->adv_w = gd->advance; | ||||||
|   dsc->ofs_x = gd->offset_x; |   dsc->ofs_x = gd->offset_x; | ||||||
|   dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline; |   dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline; | ||||||
|   dsc->box_w = gd->width; |   dsc->box_w = gd->width; | ||||||
|   | |||||||
| @@ -416,37 +416,45 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf | |||||||
|       buffer_frac_(buffer_frac), |       buffer_frac_(buffer_frac), | ||||||
|       full_refresh_(full_refresh), |       full_refresh_(full_refresh), | ||||||
|       resume_on_input_(resume_on_input) { |       resume_on_input_(resume_on_input) { | ||||||
|   auto *display = this->displays_[0]; |   lv_disp_draw_buf_init(&this->draw_buf_, nullptr, nullptr, 0); | ||||||
|   size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_; |  | ||||||
|   auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8; |  | ||||||
|   this->rotation = display->get_rotation(); |  | ||||||
|   if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { |  | ||||||
|     this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes));  // NOLINT |  | ||||||
|     if (this->rotate_buf_ == nullptr) |  | ||||||
|       return; |  | ||||||
|   } |  | ||||||
|   auto *buf = lv_custom_mem_alloc(buf_bytes);  // NOLINT |  | ||||||
|   if (buf == nullptr) |  | ||||||
|     return; |  | ||||||
|   lv_disp_draw_buf_init(&this->draw_buf_, buf, nullptr, buffer_pixels); |  | ||||||
|   lv_disp_drv_init(&this->disp_drv_); |   lv_disp_drv_init(&this->disp_drv_); | ||||||
|   this->disp_drv_.draw_buf = &this->draw_buf_; |   this->disp_drv_.draw_buf = &this->draw_buf_; | ||||||
|   this->disp_drv_.user_data = this; |   this->disp_drv_.user_data = this; | ||||||
|   this->disp_drv_.full_refresh = this->full_refresh_; |   this->disp_drv_.full_refresh = this->full_refresh_; | ||||||
|   this->disp_drv_.flush_cb = static_flush_cb; |   this->disp_drv_.flush_cb = static_flush_cb; | ||||||
|   this->disp_drv_.rounder_cb = rounder_cb; |   this->disp_drv_.rounder_cb = rounder_cb; | ||||||
|   this->disp_drv_.hor_res = (lv_coord_t) display->get_width(); |   this->disp_drv_.hor_res = 0; | ||||||
|   this->disp_drv_.ver_res = (lv_coord_t) display->get_height(); |   this->disp_drv_.ver_res = 0; | ||||||
|   this->disp_ = lv_disp_drv_register(&this->disp_drv_); |   this->disp_ = lv_disp_drv_register(&this->disp_drv_); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LvglComponent::setup() { | void LvglComponent::setup() { | ||||||
|   if (this->draw_buf_.buf1 == nullptr) { |   ESP_LOGCONFIG(TAG, "LVGL Setup starts"); | ||||||
|  |   auto *display = this->displays_[0]; | ||||||
|  |   auto width = display->get_width(); | ||||||
|  |   auto height = display->get_height(); | ||||||
|  |   size_t buffer_pixels = width * height / this->buffer_frac_; | ||||||
|  |   auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8; | ||||||
|  |   auto *buffer = lv_custom_mem_alloc(buf_bytes);  // NOLINT | ||||||
|  |   if (buffer == nullptr) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     this->status_set_error("Memory allocation failure"); |     this->status_set_error("Memory allocation failure"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   ESP_LOGCONFIG(TAG, "LVGL Setup starts"); |   lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buf_bytes); | ||||||
|  |   this->disp_drv_.hor_res = width; | ||||||
|  |   this->disp_drv_.ver_res = height; | ||||||
|  |   // this->setup_driver_(display->get_width(), display->get_height()); | ||||||
|  |   lv_disp_drv_update(this->disp_, &this->disp_drv_); | ||||||
|  |   this->rotation = display->get_rotation(); | ||||||
|  |   if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) { | ||||||
|  |     this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(this->draw_buf_.size));  // NOLINT | ||||||
|  |     if (this->rotate_buf_ == nullptr) { | ||||||
|  |       this->mark_failed(); | ||||||
|  |       this->status_set_error("Memory allocation failure"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| #if LV_USE_LOG | #if LV_USE_LOG | ||||||
|   lv_log_register_print_cb([](const char *buf) { |   lv_log_register_print_cb([](const char *buf) { | ||||||
|     auto next = strchr(buf, ')'); |     auto next = strchr(buf, ')'); | ||||||
| @@ -458,8 +466,8 @@ void LvglComponent::setup() { | |||||||
|   }); |   }); | ||||||
| #endif | #endif | ||||||
|   // Rotation will be handled by our drawing function, so reset the display rotation. |   // Rotation will be handled by our drawing function, so reset the display rotation. | ||||||
|   for (auto *display : this->displays_) |   for (auto *disp : this->displays_) | ||||||
|     display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES); |     disp->set_rotation(display::DISPLAY_ROTATION_0_DEGREES); | ||||||
|   this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0); |   this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0); | ||||||
|   lv_disp_trig_activity(this->disp_); |   lv_disp_trig_activity(this->disp_); | ||||||
|   ESP_LOGCONFIG(TAG, "LVGL Setup complete"); |   ESP_LOGCONFIG(TAG, "LVGL Setup complete"); | ||||||
|   | |||||||
| @@ -1,11 +1,9 @@ | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ANGLE, CONF_MODE | from esphome.const import CONF_ANGLE, CONF_MODE, CONF_OFFSET_X, CONF_OFFSET_Y | ||||||
|  |  | ||||||
| from ..defines import ( | from ..defines import ( | ||||||
|     CONF_ANTIALIAS, |     CONF_ANTIALIAS, | ||||||
|     CONF_MAIN, |     CONF_MAIN, | ||||||
|     CONF_OFFSET_X, |  | ||||||
|     CONF_OFFSET_Y, |  | ||||||
|     CONF_PIVOT_X, |     CONF_PIVOT_X, | ||||||
|     CONF_PIVOT_Y, |     CONF_PIVOT_Y, | ||||||
|     CONF_SRC, |     CONF_SRC, | ||||||
|   | |||||||
| @@ -550,6 +550,7 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo | |||||||
|           cfg3 = MCP_16MHZ_40KBPS_CFG3; |           cfg3 = MCP_16MHZ_40KBPS_CFG3; | ||||||
|           break; |           break; | ||||||
|         case (canbus::CAN_50KBPS):  //  50Kbps |         case (canbus::CAN_50KBPS):  //  50Kbps | ||||||
|  |           cfg1 = MCP_16MHZ_50KBPS_CFG1; | ||||||
|           cfg2 = MCP_16MHZ_50KBPS_CFG2; |           cfg2 = MCP_16MHZ_50KBPS_CFG2; | ||||||
|           cfg3 = MCP_16MHZ_50KBPS_CFG3; |           cfg3 = MCP_16MHZ_50KBPS_CFG3; | ||||||
|           break; |           break; | ||||||
|   | |||||||
| @@ -1,20 +1,21 @@ | |||||||
|  | from esphome import pins | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_FILTER, | ||||||
|  |     CONF_GAIN, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     UNIT_MICROTESLA, |     CONF_OVERSAMPLING, | ||||||
|     UNIT_CELSIUS, |     CONF_RESOLUTION, | ||||||
|     STATE_CLASS_MEASUREMENT, |     CONF_TEMPERATURE, | ||||||
|  |     CONF_TEMPERATURE_COMPENSATION, | ||||||
|     ICON_MAGNET, |     ICON_MAGNET, | ||||||
|     ICON_THERMOMETER, |     ICON_THERMOMETER, | ||||||
|     CONF_GAIN, |     STATE_CLASS_MEASUREMENT, | ||||||
|     CONF_RESOLUTION, |     UNIT_CELSIUS, | ||||||
|     CONF_OVERSAMPLING, |     UNIT_MICROTESLA, | ||||||
|     CONF_FILTER, |  | ||||||
|     CONF_TEMPERATURE, |  | ||||||
| ) | ) | ||||||
| from esphome import pins |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@functionpointer"] | CODEOWNERS = ["@functionpointer"] | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
| @@ -26,30 +27,46 @@ MLX90393Component = mlx90393_ns.class_( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| GAIN = { | GAIN = { | ||||||
|     "1X": 7, |     "1X": 0, | ||||||
|     "1_33X": 6, |     "1_25X": 1, | ||||||
|     "1_67X": 5, |     "1_67X": 2, | ||||||
|     "2X": 4, |     "2X": 3, | ||||||
|     "2_5X": 3, |     "2_5X": 4, | ||||||
|     "3X": 2, |     "3X": 5, | ||||||
|     "4X": 1, |     "3_75X": 6, | ||||||
|     "5X": 0, |     "5X": 7, | ||||||
| } | } | ||||||
|  |  | ||||||
| RESOLUTION = { | RESOLUTION = { | ||||||
|     "16BIT": 0, |     "DIV_8": 3, | ||||||
|     "17BIT": 1, |     "DIV_4": 2, | ||||||
|     "18BIT": 2, |     "DIV_2": 1, | ||||||
|     "19BIT": 3, |     "DIV_1": 0, | ||||||
| } | } | ||||||
|  |  | ||||||
| CONF_X_AXIS = "x_axis" | CONF_X_AXIS = "x_axis" | ||||||
| CONF_Y_AXIS = "y_axis" | CONF_Y_AXIS = "y_axis" | ||||||
| CONF_Z_AXIS = "z_axis" | CONF_Z_AXIS = "z_axis" | ||||||
| CONF_DRDY_PIN = "drdy_pin" | CONF_DRDY_PIN = "drdy_pin" | ||||||
|  | CONF_HALLCONF = "hallconf" | ||||||
|  |  | ||||||
|  |  | ||||||
| def mlx90393_axis_schema(default_resolution: str): | def _validate(config): | ||||||
|  |     if config[CONF_TEMPERATURE_COMPENSATION]: | ||||||
|  |         for axis in [CONF_X_AXIS, CONF_Y_AXIS, CONF_Z_AXIS]: | ||||||
|  |             if axis not in config: | ||||||
|  |                 continue | ||||||
|  |             if (res := config[axis][CONF_RESOLUTION]) in [ | ||||||
|  |                 "DIV_8", | ||||||
|  |                 "DIV_4", | ||||||
|  |             ]: | ||||||
|  |                 raise cv.Invalid( | ||||||
|  |                     f"{axis}: {CONF_RESOLUTION} cannot be {res} with {CONF_TEMPERATURE_COMPENSATION} enabled" | ||||||
|  |                 ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def mlx90393_axis_schema(): | ||||||
|     return sensor.sensor_schema( |     return sensor.sensor_schema( | ||||||
|         unit_of_measurement=UNIT_MICROTESLA, |         unit_of_measurement=UNIT_MICROTESLA, | ||||||
|         accuracy_decimals=0, |         accuracy_decimals=0, | ||||||
| @@ -58,7 +75,7 @@ def mlx90393_axis_schema(default_resolution: str): | |||||||
|     ).extend( |     ).extend( | ||||||
|         cv.Schema( |         cv.Schema( | ||||||
|             { |             { | ||||||
|                 cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum( |                 cv.Optional(CONF_RESOLUTION, default="DIV_4"): cv.enum( | ||||||
|                     RESOLUTION, upper=True, space="_" |                     RESOLUTION, upper=True, space="_" | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -66,19 +83,19 @@ def mlx90393_axis_schema(default_resolution: str): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(MLX90393Component), |             cv.GenerateID(): cv.declare_id(MLX90393Component), | ||||||
|             cv.Optional(CONF_GAIN, default="2_5X"): cv.enum( |             cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAIN, upper=True, space="_"), | ||||||
|                 GAIN, upper=True, space="_" |  | ||||||
|             ), |  | ||||||
|             cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema, |             cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema, | ||||||
|             cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3), |             cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range(min=0, max=3), | ||||||
|             cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7), |             cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7), | ||||||
|             cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"), |             cv.Optional(CONF_X_AXIS): mlx90393_axis_schema(), | ||||||
|             cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"), |             cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema(), | ||||||
|             cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"), |             cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema(), | ||||||
|  |             cv.Optional(CONF_TEMPERATURE_COMPENSATION, default=False): bool, | ||||||
|  |             cv.Optional(CONF_HALLCONF, default=0xC): cv.one_of(0xC, 0x0), | ||||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_CELSIUS, |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|                 accuracy_decimals=1, |                 accuracy_decimals=1, | ||||||
| @@ -96,7 +113,8 @@ CONFIG_SCHEMA = ( | |||||||
|         }, |         }, | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
|     .extend(i2c.i2c_device_schema(0x0C)) |     .extend(i2c.i2c_device_schema(0x0C)), | ||||||
|  |     _validate, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -111,6 +129,8 @@ async def to_code(config): | |||||||
|     cg.add(var.set_gain(GAIN[config[CONF_GAIN]])) |     cg.add(var.set_gain(GAIN[config[CONF_GAIN]])) | ||||||
|     cg.add(var.set_oversampling(config[CONF_OVERSAMPLING])) |     cg.add(var.set_oversampling(config[CONF_OVERSAMPLING])) | ||||||
|     cg.add(var.set_filter(config[CONF_FILTER])) |     cg.add(var.set_filter(config[CONF_FILTER])) | ||||||
|  |     cg.add(var.set_temperature_compensation(config[CONF_TEMPERATURE_COMPENSATION])) | ||||||
|  |     cg.add(var.set_hallconf(config[CONF_HALLCONF])) | ||||||
|  |  | ||||||
|     if CONF_X_AXIS in config: |     if CONF_X_AXIS in config: | ||||||
|         sens = await sensor.new_sensor(config[CONF_X_AXIS]) |         sens = await sensor.new_sensor(config[CONF_X_AXIS]) | ||||||
|   | |||||||
| @@ -43,6 +43,10 @@ void MLX90393Cls::setup() { | |||||||
|   this->mlx_.setDigitalFiltering(this->filter_); |   this->mlx_.setDigitalFiltering(this->filter_); | ||||||
|  |  | ||||||
|   this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_); |   this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_); | ||||||
|  |  | ||||||
|  |   this->mlx_.setTemperatureCompensation(this->temperature_compensation_); | ||||||
|  |  | ||||||
|  |   this->mlx_.setHallConf(this->hallconf_); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MLX90393Cls::dump_config() { | void MLX90393Cls::dump_config() { | ||||||
|   | |||||||
| @@ -29,7 +29,10 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90 | |||||||
|   void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; } |   void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; } | ||||||
|   void set_filter(uint8_t filter) { filter_ = filter; } |   void set_filter(uint8_t filter) { filter_ = filter; } | ||||||
|   void set_gain(uint8_t gain_sel) { gain_ = gain_sel; } |   void set_gain(uint8_t gain_sel) { gain_ = gain_sel; } | ||||||
|  |   void set_temperature_compensation(bool temperature_compensation) { | ||||||
|  |     temperature_compensation_ = temperature_compensation; | ||||||
|  |   } | ||||||
|  |   void set_hallconf(uint8_t hallconf) { hallconf_ = hallconf; } | ||||||
|   // overrides for MLX library |   // overrides for MLX library | ||||||
|  |  | ||||||
|   // disable lint because it keeps suggesting const uint8_t *response. |   // disable lint because it keeps suggesting const uint8_t *response. | ||||||
| @@ -49,9 +52,11 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90 | |||||||
|   sensor::Sensor *t_sensor_{nullptr}; |   sensor::Sensor *t_sensor_{nullptr}; | ||||||
|   uint8_t gain_; |   uint8_t gain_; | ||||||
|   uint8_t oversampling_; |   uint8_t oversampling_; | ||||||
|   uint8_t temperature_oversampling_ = 0; |   uint8_t temperature_oversampling_{0}; | ||||||
|   uint8_t filter_; |   uint8_t filter_; | ||||||
|   uint8_t resolutions_[3] = {0}; |   uint8_t resolutions_[3]{0}; | ||||||
|  |   bool temperature_compensation_{false}; | ||||||
|  |   uint8_t hallconf_{0xC}; | ||||||
|   GPIOPin *drdy_pin_{nullptr}; |   GPIOPin *drdy_pin_{nullptr}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,6 +36,7 @@ from esphome.const import ( | |||||||
|     CONF_PAYLOAD_AVAILABLE, |     CONF_PAYLOAD_AVAILABLE, | ||||||
|     CONF_PAYLOAD_NOT_AVAILABLE, |     CONF_PAYLOAD_NOT_AVAILABLE, | ||||||
|     CONF_PORT, |     CONF_PORT, | ||||||
|  |     CONF_PUBLISH_NAN_AS_NONE, | ||||||
|     CONF_QOS, |     CONF_QOS, | ||||||
|     CONF_REBOOT_TIMEOUT, |     CONF_REBOOT_TIMEOUT, | ||||||
|     CONF_RETAIN, |     CONF_RETAIN, | ||||||
| @@ -49,7 +50,6 @@ from esphome.const import ( | |||||||
|     CONF_USE_ABBREVIATIONS, |     CONF_USE_ABBREVIATIONS, | ||||||
|     CONF_USERNAME, |     CONF_USERNAME, | ||||||
|     CONF_WILL_MESSAGE, |     CONF_WILL_MESSAGE, | ||||||
|     CONF_PUBLISH_NAN_AS_NONE, |  | ||||||
|     PLATFORM_BK72XX, |     PLATFORM_BK72XX, | ||||||
|     PLATFORM_ESP32, |     PLATFORM_ESP32, | ||||||
|     PLATFORM_ESP8266, |     PLATFORM_ESP8266, | ||||||
| @@ -406,7 +406,7 @@ async def to_code(config): | |||||||
|     if CONF_SSL_FINGERPRINTS in config: |     if CONF_SSL_FINGERPRINTS in config: | ||||||
|         for fingerprint in config[CONF_SSL_FINGERPRINTS]: |         for fingerprint in config[CONF_SSL_FINGERPRINTS]: | ||||||
|             arr = [ |             arr = [ | ||||||
|                 cg.RawExpression(f"0x{fingerprint[i:i + 2]}") for i in range(0, 40, 2) |                 cg.RawExpression(f"0x{fingerprint[i : i + 2]}") for i in range(0, 40, 2) | ||||||
|             ] |             ] | ||||||
|             cg.add(var.add_ssl_fingerprint(arr)) |             cg.add(var.add_ssl_fingerprint(arr)) | ||||||
|         cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1") |         cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1") | ||||||
|   | |||||||
							
								
								
									
										189
									
								
								esphome/components/msa3xx/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								esphome/components/msa3xx/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | |||||||
|  | from esphome import automation | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_CALIBRATION, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_MIRROR_X, | ||||||
|  |     CONF_MIRROR_Y, | ||||||
|  |     CONF_OFFSET_X, | ||||||
|  |     CONF_OFFSET_Y, | ||||||
|  |     CONF_OFFSET_Z, | ||||||
|  |     CONF_RANGE, | ||||||
|  |     CONF_RESOLUTION, | ||||||
|  |     CONF_SWAP_XY, | ||||||
|  |     CONF_TRANSFORM, | ||||||
|  |     CONF_TYPE, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@latonita"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  | CONF_MSA3XX_ID = "msa3xx_id" | ||||||
|  |  | ||||||
|  | CONF_MIRROR_Z = "mirror_z" | ||||||
|  | CONF_ON_ACTIVE = "on_active" | ||||||
|  | CONF_ON_DOUBLE_TAP = "on_double_tap" | ||||||
|  | CONF_ON_FREEFALL = "on_freefall" | ||||||
|  | CONF_ON_ORIENTATION = "on_orientation" | ||||||
|  | CONF_ON_TAP = "on_tap" | ||||||
|  |  | ||||||
|  | MODEL_MSA301 = "MSA301" | ||||||
|  | MODEL_MSA311 = "MSA311" | ||||||
|  |  | ||||||
|  | msa3xx_ns = cg.esphome_ns.namespace("msa3xx") | ||||||
|  | MSA3xxComponent = msa3xx_ns.class_( | ||||||
|  |     "MSA3xxComponent", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | MSAModels = msa3xx_ns.enum("Model", True) | ||||||
|  | MSA_MODELS = { | ||||||
|  |     MODEL_MSA301: MSAModels.MSA301, | ||||||
|  |     MODEL_MSA311: MSAModels.MSA311, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MSARange = msa3xx_ns.enum("Range", True) | ||||||
|  | MSA_RANGES = { | ||||||
|  |     "2G": MSARange.RANGE_2G, | ||||||
|  |     "4G": MSARange.RANGE_4G, | ||||||
|  |     "8G": MSARange.RANGE_8G, | ||||||
|  |     "16G": MSARange.RANGE_16G, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MSAResolution = msa3xx_ns.enum("Resolution", True) | ||||||
|  | RESOLUTIONS_MSA301 = { | ||||||
|  |     14: MSAResolution.RES_14BIT, | ||||||
|  |     12: MSAResolution.RES_12BIT, | ||||||
|  |     10: MSAResolution.RES_10BIT, | ||||||
|  |     8: MSAResolution.RES_8BIT, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | RESOLUTIONS_MSA311 = { | ||||||
|  |     12: MSAResolution.RES_12BIT, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | _COMMON_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(MSA3xxComponent), | ||||||
|  |         cv.Optional(CONF_RANGE, default="2G"): cv.enum(MSA_RANGES, upper=True), | ||||||
|  |         cv.Optional(CONF_CALIBRATION): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_OFFSET_X, default=0): cv.float_range( | ||||||
|  |                     min=-4.5, max=4.5 | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_OFFSET_Y, default=0): cv.float_range( | ||||||
|  |                     min=-4.5, max=4.5 | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional(CONF_OFFSET_Z, default=0): cv.float_range( | ||||||
|  |                     min=-4.5, max=4.5 | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_TRANSFORM): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_MIRROR_Z, default=False): cv.boolean, | ||||||
|  |                 cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ON_ACTIVE): automation.validate_automation(single=True), | ||||||
|  |         cv.Optional(CONF_ON_TAP): automation.validate_automation(single=True), | ||||||
|  |         cv.Optional(CONF_ON_DOUBLE_TAP): automation.validate_automation(single=True), | ||||||
|  |         cv.Optional(CONF_ON_FREEFALL): automation.validate_automation(single=True), | ||||||
|  |         cv.Optional(CONF_ON_ORIENTATION): automation.validate_automation(single=True), | ||||||
|  |     } | ||||||
|  | ).extend(cv.polling_component_schema("10s")) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.typed_schema( | ||||||
|  |     { | ||||||
|  |         MODEL_MSA301: _COMMON_SCHEMA.extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_RESOLUTION, default=14): cv.enum(RESOLUTIONS_MSA301), | ||||||
|  |             } | ||||||
|  |         ).extend(i2c.i2c_device_schema(0x26)), | ||||||
|  |         MODEL_MSA311: _COMMON_SCHEMA.extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_RESOLUTION, default=12): cv.enum(RESOLUTIONS_MSA311), | ||||||
|  |             } | ||||||
|  |         ).extend(i2c.i2c_device_schema(0x62)), | ||||||
|  |     }, | ||||||
|  |     upper=True, | ||||||
|  |     enum=MSA_MODELS, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | MSA_SENSOR_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_MSA3XX_ID): cv.use_id(MSA3xxComponent), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_model(config[CONF_TYPE])) | ||||||
|  |     cg.add(var.set_range(MSA_RANGES[config[CONF_RANGE]])) | ||||||
|  |     cg.add(var.set_resolution(RESOLUTIONS_MSA301[config[CONF_RESOLUTION]])) | ||||||
|  |  | ||||||
|  |     if transform := config.get(CONF_TRANSFORM): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_transform( | ||||||
|  |                 transform[CONF_MIRROR_X], | ||||||
|  |                 transform[CONF_MIRROR_Y], | ||||||
|  |                 transform[CONF_MIRROR_Z], | ||||||
|  |                 transform[CONF_SWAP_XY], | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if calibration_config := config.get(CONF_CALIBRATION): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_offset( | ||||||
|  |                 calibration_config[CONF_OFFSET_X], | ||||||
|  |                 calibration_config[CONF_OFFSET_Y], | ||||||
|  |                 calibration_config[CONF_OFFSET_Z], | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     # Triggers secton | ||||||
|  |  | ||||||
|  |     if CONF_ON_ORIENTATION in config: | ||||||
|  |         await automation.build_automation( | ||||||
|  |             var.get_orientation_trigger(), | ||||||
|  |             [], | ||||||
|  |             config[CONF_ON_ORIENTATION], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if CONF_ON_TAP in config: | ||||||
|  |         await automation.build_automation( | ||||||
|  |             var.get_tap_trigger(), | ||||||
|  |             [], | ||||||
|  |             config[CONF_ON_TAP], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if CONF_ON_DOUBLE_TAP in config: | ||||||
|  |         await automation.build_automation( | ||||||
|  |             var.get_double_tap_trigger(), | ||||||
|  |             [], | ||||||
|  |             config[CONF_ON_DOUBLE_TAP], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if CONF_ON_ACTIVE in config: | ||||||
|  |         await automation.build_automation( | ||||||
|  |             var.get_active_trigger(), | ||||||
|  |             [], | ||||||
|  |             config[CONF_ON_ACTIVE], | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if CONF_ON_FREEFALL in config: | ||||||
|  |         await automation.build_automation( | ||||||
|  |             var.get_freefall_trigger(), | ||||||
|  |             [], | ||||||
|  |             config[CONF_ON_FREEFALL], | ||||||
|  |         ) | ||||||
							
								
								
									
										40
									
								
								esphome/components/msa3xx/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/msa3xx/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import binary_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ACTIVE, CONF_NAME, DEVICE_CLASS_VIBRATION, ICON_VIBRATE | ||||||
|  |  | ||||||
|  | from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@latonita"] | ||||||
|  | DEPENDENCIES = ["msa3xx"] | ||||||
|  |  | ||||||
|  | CONF_TAP = "tap" | ||||||
|  | CONF_DOUBLE_TAP = "double_tap" | ||||||
|  |  | ||||||
|  | ICON_TAP = "mdi:gesture-tap" | ||||||
|  | ICON_DOUBLE_TAP = "mdi:gesture-double-tap" | ||||||
|  |  | ||||||
|  | EVENT_SENSORS = (CONF_TAP, CONF_DOUBLE_TAP, CONF_ACTIVE) | ||||||
|  | ICONS = (ICON_TAP, ICON_DOUBLE_TAP, ICON_VIBRATE) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.Optional(event): cv.maybe_simple_value( | ||||||
|  |             binary_sensor.binary_sensor_schema( | ||||||
|  |                 device_class=DEVICE_CLASS_VIBRATION, | ||||||
|  |                 icon=icon, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ) | ||||||
|  |         for event, icon in zip(EVENT_SENSORS, ICONS) | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     hub = await cg.get_variable(config[CONF_MSA3XX_ID]) | ||||||
|  |  | ||||||
|  |     for sensor in EVENT_SENSORS: | ||||||
|  |         if sensor in config: | ||||||
|  |             sens = await binary_sensor.new_binary_sensor(config[sensor]) | ||||||
|  |             cg.add(getattr(hub, f"set_{sensor}_binary_sensor")(sens)) | ||||||
							
								
								
									
										417
									
								
								esphome/components/msa3xx/msa3xx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								esphome/components/msa3xx/msa3xx.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,417 @@ | |||||||
|  | #include "msa3xx.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace msa3xx { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "msa3xx"; | ||||||
|  |  | ||||||
|  | const uint8_t MSA_3XX_PART_ID = 0x13; | ||||||
|  |  | ||||||
|  | const float GRAVITY_EARTH = 9.80665f; | ||||||
|  | const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9);  // LSB to 1 LSB = 3.9mg = 0.0039g | ||||||
|  | const float G_OFFSET_MIN = -4.5f;  // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe | ||||||
|  | const float G_OFFSET_MAX = 4.5f;   // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe | ||||||
|  |  | ||||||
|  | const uint8_t RESOLUTION[] = {14, 12, 10, 8}; | ||||||
|  |  | ||||||
|  | const uint32_t TAP_COOLDOWN_MS = 500; | ||||||
|  | const uint32_t DOUBLE_TAP_COOLDOWN_MS = 500; | ||||||
|  | const uint32_t ACTIVITY_COOLDOWN_MS = 500; | ||||||
|  |  | ||||||
|  | const char *model_to_string(Model model) { | ||||||
|  |   switch (model) { | ||||||
|  |     case Model::MSA301: | ||||||
|  |       return "MSA301"; | ||||||
|  |     case Model::MSA311: | ||||||
|  |       return "MSA311"; | ||||||
|  |     default: | ||||||
|  |       return "Unknown"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *power_mode_to_string(PowerMode power_mode) { | ||||||
|  |   switch (power_mode) { | ||||||
|  |     case PowerMode::NORMAL: | ||||||
|  |       return "Normal"; | ||||||
|  |     case PowerMode::LOW_POWER: | ||||||
|  |       return "Low Power"; | ||||||
|  |     case PowerMode::SUSPEND: | ||||||
|  |       return "Suspend"; | ||||||
|  |     default: | ||||||
|  |       return "Unknown"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *res_to_string(Resolution resolution) { | ||||||
|  |   switch (resolution) { | ||||||
|  |     case Resolution::RES_14BIT: | ||||||
|  |       return "14-bit"; | ||||||
|  |     case Resolution::RES_12BIT: | ||||||
|  |       return "12-bit"; | ||||||
|  |     case Resolution::RES_10BIT: | ||||||
|  |       return "10-bit"; | ||||||
|  |     case Resolution::RES_8BIT: | ||||||
|  |       return "8-bit"; | ||||||
|  |     default: | ||||||
|  |       return "Unknown"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *range_to_string(Range range) { | ||||||
|  |   switch (range) { | ||||||
|  |     case Range::RANGE_2G: | ||||||
|  |       return "±2g"; | ||||||
|  |     case Range::RANGE_4G: | ||||||
|  |       return "±4g"; | ||||||
|  |     case Range::RANGE_8G: | ||||||
|  |       return "±8g"; | ||||||
|  |     case Range::RANGE_16G: | ||||||
|  |       return "±16g"; | ||||||
|  |     default: | ||||||
|  |       return "Unknown"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *bandwidth_to_string(Bandwidth bandwidth) { | ||||||
|  |   switch (bandwidth) { | ||||||
|  |     case Bandwidth::BW_1_95HZ: | ||||||
|  |       return "1.95 Hz"; | ||||||
|  |     case Bandwidth::BW_3_9HZ: | ||||||
|  |       return "3.9 Hz"; | ||||||
|  |     case Bandwidth::BW_7_81HZ: | ||||||
|  |       return "7.81 Hz"; | ||||||
|  |     case Bandwidth::BW_15_63HZ: | ||||||
|  |       return "15.63 Hz"; | ||||||
|  |     case Bandwidth::BW_31_25HZ: | ||||||
|  |       return "31.25 Hz"; | ||||||
|  |     case Bandwidth::BW_62_5HZ: | ||||||
|  |       return "62.5 Hz"; | ||||||
|  |     case Bandwidth::BW_125HZ: | ||||||
|  |       return "125 Hz"; | ||||||
|  |     case Bandwidth::BW_250HZ: | ||||||
|  |       return "250 Hz"; | ||||||
|  |     case Bandwidth::BW_500HZ: | ||||||
|  |       return "500 Hz"; | ||||||
|  |     default: | ||||||
|  |       return "Unknown"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *orientation_xy_to_string(OrientationXY orientation) { | ||||||
|  |   switch (orientation) { | ||||||
|  |     case OrientationXY::PORTRAIT_UPRIGHT: | ||||||
|  |       return "Portrait Upright"; | ||||||
|  |     case OrientationXY::PORTRAIT_UPSIDE_DOWN: | ||||||
|  |       return "Portrait Upside Down"; | ||||||
|  |     case OrientationXY::LANDSCAPE_LEFT: | ||||||
|  |       return "Landscape Left"; | ||||||
|  |     case OrientationXY::LANDSCAPE_RIGHT: | ||||||
|  |       return "Landscape Right"; | ||||||
|  |     default: | ||||||
|  |       return "Unknown"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *orientation_z_to_string(bool orientation) { return orientation ? "Downwards looking" : "Upwards looking"; } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up MSA3xx..."); | ||||||
|  |  | ||||||
|  |   uint8_t part_id{0xff}; | ||||||
|  |   if (!this->read_byte(static_cast<uint8_t>(RegisterMap::PART_ID), &part_id) || (part_id != MSA_3XX_PART_ID)) { | ||||||
|  |     ESP_LOGE(TAG, "Part ID is wrong or missing. Got 0x%02X", part_id); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Resolution LSB/g | ||||||
|  |   // Range : MSA301      : MSA311 | ||||||
|  |   // S2g   : 1024 (2^10) : 4096 (2^12) | ||||||
|  |   // S4g   : 512  (2^9)  : 2048 (2^11) | ||||||
|  |   // S8g   : 256  (2^8)  : 1024 (2^10) | ||||||
|  |   // S16g  : 128  (2^7)  : 512  (2^9) | ||||||
|  |   if (this->model_ == Model::MSA301) { | ||||||
|  |     this->device_params_.accel_data_width = 14; | ||||||
|  |     this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 12; | ||||||
|  |   } else if (this->model_ == Model::MSA311) { | ||||||
|  |     this->device_params_.accel_data_width = 12; | ||||||
|  |     this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 10; | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "Unknown model"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->setup_odr_(this->data_rate_); | ||||||
|  |   this->setup_power_mode_bandwidth_(this->power_mode_, this->bandwidth_); | ||||||
|  |   this->setup_range_resolution_(this->range_, this->resolution_);                       // 2g...16g, 14...8 bit | ||||||
|  |   this->setup_offset_(this->offset_x_, this->offset_y_, this->offset_z_);               // calibration offsets | ||||||
|  |   this->write_byte(static_cast<uint8_t>(RegisterMap::TAP_DURATION), 0b11000100);        // set tap duration 250ms | ||||||
|  |   this->write_byte(static_cast<uint8_t>(RegisterMap::SWAP_POLARITY), this->swap_.raw);  // set axes polarity | ||||||
|  |   this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_0), 0b01110111);           // enable all interrupts | ||||||
|  |   this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_1), 0b00011000);           // including orientation | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "MSA3xx:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with MSA3xx failed!"); | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Model: %s", model_to_string(this->model_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Power Mode: %s", power_mode_to_string(this->power_mode_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Bandwidth: %s", bandwidth_to_string(this->bandwidth_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Range: %s", range_to_string(this->range_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Resolution: %s", res_to_string(this->resolution_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Offsets: {%.3f m/s², %.3f m/s², %.3f m/s²}", this->offset_x_, this->offset_y_, this->offset_z_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Transform: {mirror_x=%s, mirror_y=%s, mirror_z=%s, swap_xy=%s}", YESNO(this->swap_.x_polarity), | ||||||
|  |                 YESNO(this->swap_.y_polarity), YESNO(this->swap_.z_polarity), YESNO(this->swap_.x_y_swap)); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |  | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   LOG_BINARY_SENSOR("  ", "Tap", this->tap_binary_sensor_); | ||||||
|  |   LOG_BINARY_SENSOR("  ", "Double Tap", this->double_tap_binary_sensor_); | ||||||
|  |   LOG_BINARY_SENSOR("  ", "Active", this->active_binary_sensor_); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   LOG_SENSOR("  ", "Acceleration X", this->acceleration_x_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Acceleration Y", this->acceleration_y_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Acceleration Z", this->acceleration_z_sensor_); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   LOG_TEXT_SENSOR("  ", "Orientation XY", this->orientation_xy_text_sensor_); | ||||||
|  |   LOG_TEXT_SENSOR("  ", "Orientation Z", this->orientation_z_text_sensor_); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MSA3xxComponent::read_data_() { | ||||||
|  |   uint8_t accel_data[6]; | ||||||
|  |   if (!this->read_bytes(static_cast<uint8_t>(RegisterMap::ACC_X_LSB), accel_data, 6)) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto raw_to_x_bit = [](uint16_t lsb, uint16_t msb, uint8_t data_bits) -> uint16_t { | ||||||
|  |     return ((msb << 8) | lsb) >> (16 - data_bits); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   auto lpf = [](float new_value, float old_value, float alpha = 0.5f) { | ||||||
|  |     return alpha * new_value + (1.0f - alpha) * old_value; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   this->data_.lsb_x = | ||||||
|  |       this->twos_complement_(raw_to_x_bit(accel_data[0], accel_data[1], this->device_params_.accel_data_width), | ||||||
|  |                              this->device_params_.accel_data_width); | ||||||
|  |   this->data_.lsb_y = | ||||||
|  |       this->twos_complement_(raw_to_x_bit(accel_data[2], accel_data[3], this->device_params_.accel_data_width), | ||||||
|  |                              this->device_params_.accel_data_width); | ||||||
|  |   this->data_.lsb_z = | ||||||
|  |       this->twos_complement_(raw_to_x_bit(accel_data[4], accel_data[5], this->device_params_.accel_data_width), | ||||||
|  |                              this->device_params_.accel_data_width); | ||||||
|  |  | ||||||
|  |   this->data_.x = lpf(ldexp(this->data_.lsb_x, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.x); | ||||||
|  |   this->data_.y = lpf(ldexp(this->data_.lsb_y, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.y); | ||||||
|  |   this->data_.z = lpf(ldexp(this->data_.lsb_z, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.z); | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MSA3xxComponent::read_motion_status_() { | ||||||
|  |   if (!this->read_byte(static_cast<uint8_t>(RegisterMap::MOTION_INTERRUPT), &this->status_.motion_int.raw)) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->read_byte(static_cast<uint8_t>(RegisterMap::ORIENTATION_STATUS), &this->status_.orientation.raw)) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::loop() { | ||||||
|  |   if (!this->is_ready()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   RegMotionInterrupt old_motion_int = this->status_.motion_int; | ||||||
|  |  | ||||||
|  |   if (!this->read_data_() || !this->read_motion_status_()) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->process_motions_(old_motion_int); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::update() { | ||||||
|  |   ESP_LOGV(TAG, "Updating MSA3xx..."); | ||||||
|  |  | ||||||
|  |   if (!this->is_ready()) { | ||||||
|  |     ESP_LOGV(TAG, "Component MSA3xx not ready for update"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   ESP_LOGV(TAG, "Acceleration: {x = %+1.3f m/s², y = %+1.3f m/s², z = %+1.3f m/s²}; ", this->data_.x, this->data_.y, | ||||||
|  |            this->data_.z); | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "Orientation: {XY = %s, Z = %s}", orientation_xy_to_string(this->status_.orientation.orient_xy), | ||||||
|  |            orientation_z_to_string(this->status_.orientation.orient_z)); | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   if (this->acceleration_x_sensor_ != nullptr) | ||||||
|  |     this->acceleration_x_sensor_->publish_state(this->data_.x); | ||||||
|  |   if (this->acceleration_y_sensor_ != nullptr) | ||||||
|  |     this->acceleration_y_sensor_->publish_state(this->data_.y); | ||||||
|  |   if (this->acceleration_z_sensor_ != nullptr) | ||||||
|  |     this->acceleration_z_sensor_->publish_state(this->data_.z); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   if (this->orientation_xy_text_sensor_ != nullptr && | ||||||
|  |       (this->status_.orientation.orient_xy != this->status_.orientation_old.orient_xy || | ||||||
|  |        this->status_.never_published)) { | ||||||
|  |     this->orientation_xy_text_sensor_->publish_state(orientation_xy_to_string(this->status_.orientation.orient_xy)); | ||||||
|  |   } | ||||||
|  |   if (this->orientation_z_text_sensor_ != nullptr && | ||||||
|  |       (this->status_.orientation.orient_z != this->status_.orientation_old.orient_z || this->status_.never_published)) { | ||||||
|  |     this->orientation_z_text_sensor_->publish_state(orientation_z_to_string(this->status_.orientation.orient_z)); | ||||||
|  |   } | ||||||
|  |   this->status_.orientation_old = this->status_.orientation; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   this->status_.never_published = false; | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  | float MSA3xxComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::set_offset(float offset_x, float offset_y, float offset_z) { | ||||||
|  |   this->offset_x_ = offset_x; | ||||||
|  |   this->offset_y_ = offset_y; | ||||||
|  |   this->offset_z_ = offset_z; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy) { | ||||||
|  |   this->swap_.x_polarity = mirror_x; | ||||||
|  |   this->swap_.y_polarity = mirror_y; | ||||||
|  |   this->swap_.z_polarity = mirror_z; | ||||||
|  |   this->swap_.x_y_swap = swap_xy; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::setup_odr_(DataRate rate) { | ||||||
|  |   RegOutputDataRate reg_odr; | ||||||
|  |   auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::ODR)); | ||||||
|  |   if (reg.has_value()) { | ||||||
|  |     reg_odr.raw = reg.value(); | ||||||
|  |   } else { | ||||||
|  |     reg_odr.raw = 0x0F;  // defaut from datasheet | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   reg_odr.x_axis_disable = false; | ||||||
|  |   reg_odr.y_axis_disable = false; | ||||||
|  |   reg_odr.z_axis_disable = false; | ||||||
|  |   reg_odr.odr = rate; | ||||||
|  |  | ||||||
|  |   this->write_byte(static_cast<uint8_t>(RegisterMap::ODR), reg_odr.raw); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth) { | ||||||
|  |   // 0x11 POWER_MODE_BANDWIDTH | ||||||
|  |   auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH)); | ||||||
|  |  | ||||||
|  |   RegPowerModeBandwidth power_mode_bandwidth; | ||||||
|  |   if (reg.has_value()) { | ||||||
|  |     power_mode_bandwidth.raw = reg.value(); | ||||||
|  |   } else { | ||||||
|  |     power_mode_bandwidth.raw = 0xde;  // defaut from datasheet | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   power_mode_bandwidth.power_mode = power_mode; | ||||||
|  |   power_mode_bandwidth.low_power_bandwidth = bandwidth; | ||||||
|  |  | ||||||
|  |   this->write_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH), power_mode_bandwidth.raw); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::setup_range_resolution_(Range range, Resolution resolution) { | ||||||
|  |   RegRangeResolution reg; | ||||||
|  |   reg.raw = this->read_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION)).value_or(0x00); | ||||||
|  |   reg.range = range; | ||||||
|  |   if (this->model_ == Model::MSA301) { | ||||||
|  |     reg.resolution = resolution; | ||||||
|  |   } | ||||||
|  |   this->write_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION), reg.raw); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::setup_offset_(float offset_x, float offset_y, float offset_z) { | ||||||
|  |   uint8_t offset[3]; | ||||||
|  |  | ||||||
|  |   auto offset_g_to_lsb = [](float accel) -> int8_t { | ||||||
|  |     float acccel_clamped = clamp(accel, G_OFFSET_MIN, G_OFFSET_MAX); | ||||||
|  |     return static_cast<int8_t>(acccel_clamped * LSB_COEFF); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   offset[0] = offset_g_to_lsb(offset_x); | ||||||
|  |   offset[1] = offset_g_to_lsb(offset_y); | ||||||
|  |   offset[2] = offset_g_to_lsb(offset_z); | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "Offset (%.3f, %.3f, %.3f)=>LSB(%d, %d, %d)", offset_x, offset_y, offset_z, offset[0], offset[1], | ||||||
|  |            offset[2]); | ||||||
|  |  | ||||||
|  |   this->write_bytes(static_cast<uint8_t>(RegisterMap::OFFSET_COMP_X), (uint8_t *) &offset, 3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int64_t MSA3xxComponent::twos_complement_(uint64_t value, uint8_t bits) { | ||||||
|  |   if (value > (1ULL << (bits - 1))) { | ||||||
|  |     return (int64_t) (value - (1ULL << bits)); | ||||||
|  |   } else { | ||||||
|  |     return (int64_t) value; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void binary_event_debounce(bool state, bool old_state, uint32_t now, uint32_t &last_ms, Trigger<> &trigger, | ||||||
|  |                            uint32_t cooldown_ms, void *bs, const char *desc) { | ||||||
|  |   if (state && now - last_ms > cooldown_ms) { | ||||||
|  |     ESP_LOGV(TAG, "%s detected", desc); | ||||||
|  |     trigger.trigger(); | ||||||
|  |     last_ms = now; | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |     if (bs != nullptr) { | ||||||
|  |       static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(true); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |   } else if (!state && now - last_ms > cooldown_ms && bs != nullptr) { | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |     static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(false); | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  | #define BS_OPTIONAL_PTR(x) ((void *) (x)) | ||||||
|  | #else | ||||||
|  | #define BS_OPTIONAL_PTR(x) (nullptr) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | void MSA3xxComponent::process_motions_(RegMotionInterrupt old) { | ||||||
|  |   uint32_t now = millis(); | ||||||
|  |  | ||||||
|  |   binary_event_debounce(this->status_.motion_int.single_tap_interrupt, old.single_tap_interrupt, now, | ||||||
|  |                         this->status_.last_tap_ms, this->tap_trigger_, TAP_COOLDOWN_MS, | ||||||
|  |                         BS_OPTIONAL_PTR(this->tap_binary_sensor_), "Tap"); | ||||||
|  |   binary_event_debounce(this->status_.motion_int.double_tap_interrupt, old.double_tap_interrupt, now, | ||||||
|  |                         this->status_.last_double_tap_ms, this->double_tap_trigger_, DOUBLE_TAP_COOLDOWN_MS, | ||||||
|  |                         BS_OPTIONAL_PTR(this->double_tap_binary_sensor_), "Double Tap"); | ||||||
|  |   binary_event_debounce(this->status_.motion_int.active_interrupt, old.active_interrupt, now, | ||||||
|  |                         this->status_.last_action_ms, this->active_trigger_, ACTIVITY_COOLDOWN_MS, | ||||||
|  |                         BS_OPTIONAL_PTR(this->active_binary_sensor_), "Activity"); | ||||||
|  |  | ||||||
|  |   if (this->status_.motion_int.orientation_interrupt) { | ||||||
|  |     ESP_LOGVV(TAG, "Orientation changed"); | ||||||
|  |     this->orientation_trigger_.trigger(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace msa3xx | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										311
									
								
								esphome/components/msa3xx/msa3xx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								esphome/components/msa3xx/msa3xx.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  | #include "esphome/components/text_sensor/text_sensor.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace msa3xx { | ||||||
|  |  | ||||||
|  | // Combined register map of MSA301 and MSA311 | ||||||
|  | // Differences | ||||||
|  | //  What             |  MSA301 | MSA11  | | ||||||
|  | //  - Resolution     |  14-bit | 12-bit | | ||||||
|  | // | ||||||
|  |  | ||||||
|  | // I2c address | ||||||
|  | enum class Model : uint8_t { | ||||||
|  |   MSA301 = 0x26, | ||||||
|  |   MSA311 = 0x62, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Combined MSA301 and MSA311 register map | ||||||
|  | enum class RegisterMap : uint8_t { | ||||||
|  |   SOFT_RESET = 0x00, | ||||||
|  |   PART_ID = 0x01, | ||||||
|  |   ACC_X_LSB = 0x02, | ||||||
|  |   ACC_X_MSB = 0x03, | ||||||
|  |   ACC_Y_LSB = 0x04, | ||||||
|  |   ACC_Y_MSB = 0x05, | ||||||
|  |   ACC_Z_LSB = 0x06, | ||||||
|  |   ACC_Z_MSB = 0x07, | ||||||
|  |   MOTION_INTERRUPT = 0x09, | ||||||
|  |   DATA_INTERRUPT = 0x0A, | ||||||
|  |   TAP_ACTIVE_STATUS = 0x0B, | ||||||
|  |   ORIENTATION_STATUS = 0x0C, | ||||||
|  |   RESOLUTION_RANGE_CONFIG = 0x0D, | ||||||
|  |   RANGE_RESOLUTION = 0x0F, | ||||||
|  |   ODR = 0x10, | ||||||
|  |   POWER_MODE_BANDWIDTH = 0x11, | ||||||
|  |   SWAP_POLARITY = 0x12, | ||||||
|  |   INT_SET_0 = 0x16, | ||||||
|  |   INT_SET_1 = 0x17, | ||||||
|  |   INT_MAP_0 = 0x19, | ||||||
|  |   INT_MAP_1 = 0x1A, | ||||||
|  |   INT_CONFIG = 0x20, | ||||||
|  |   INT_LATCH = 0x21, | ||||||
|  |   FREEFALL_DURATION = 0x22, | ||||||
|  |   FREEFALL_THRESHOLD = 0x23, | ||||||
|  |   FREEFALL_HYSTERESIS = 0x24, | ||||||
|  |   ACTIVE_DURATION = 0x27, | ||||||
|  |   ACTIVE_THRESHOLD = 0x28, | ||||||
|  |   TAP_DURATION = 0x2A, | ||||||
|  |   TAP_THRESHOLD = 0x2B, | ||||||
|  |   ORIENTATION_CONFIG = 0x2C, | ||||||
|  |   Z_BLOCK = 0x2D, | ||||||
|  |   OFFSET_COMP_X = 0x38, | ||||||
|  |   OFFSET_COMP_Y = 0x39, | ||||||
|  |   OFFSET_COMP_Z = 0x3A, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class Range : uint8_t { | ||||||
|  |   RANGE_2G = 0b00, | ||||||
|  |   RANGE_4G = 0b01, | ||||||
|  |   RANGE_8G = 0b10, | ||||||
|  |   RANGE_16G = 0b11, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class Resolution : uint8_t { | ||||||
|  |   RES_14BIT = 0b00, | ||||||
|  |   RES_12BIT = 0b01, | ||||||
|  |   RES_10BIT = 0b10, | ||||||
|  |   RES_8BIT = 0b11, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class PowerMode : uint8_t { | ||||||
|  |   NORMAL = 0b00, | ||||||
|  |   LOW_POWER = 0b01, | ||||||
|  |   SUSPEND = 0b11, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class Bandwidth : uint8_t { | ||||||
|  |   BW_1_95HZ = 0b0000, | ||||||
|  |   BW_3_9HZ = 0b0011, | ||||||
|  |   BW_7_81HZ = 0b0100, | ||||||
|  |   BW_15_63HZ = 0b0101, | ||||||
|  |   BW_31_25HZ = 0b0110, | ||||||
|  |   BW_62_5HZ = 0b0111, | ||||||
|  |   BW_125HZ = 0b1000, | ||||||
|  |   BW_250HZ = 0b1001, | ||||||
|  |   BW_500HZ = 0b1010, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class DataRate : uint8_t { | ||||||
|  |   ODR_1HZ = 0b0000,     // not available in normal mode | ||||||
|  |   ODR_1_95HZ = 0b0001,  // not available in normal mode | ||||||
|  |   ODR_3_9HZ = 0b0010, | ||||||
|  |   ODR_7_81HZ = 0b0011, | ||||||
|  |   ODR_15_63HZ = 0b0100, | ||||||
|  |   ODR_31_25HZ = 0b0101, | ||||||
|  |   ODR_62_5HZ = 0b0110, | ||||||
|  |   ODR_125HZ = 0b0111, | ||||||
|  |   ODR_250HZ = 0b1000, | ||||||
|  |   ODR_500HZ = 0b1001,   // not available in low power mode | ||||||
|  |   ODR_1000HZ = 0b1010,  // not available in low power mode | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class OrientationXY : uint8_t { | ||||||
|  |   PORTRAIT_UPRIGHT = 0b00, | ||||||
|  |   PORTRAIT_UPSIDE_DOWN = 0b01, | ||||||
|  |   LANDSCAPE_LEFT = 0b10, | ||||||
|  |   LANDSCAPE_RIGHT = 0b11, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | union Orientation { | ||||||
|  |   struct { | ||||||
|  |     OrientationXY xy : 2; | ||||||
|  |     bool z : 1; | ||||||
|  |     uint8_t reserved : 5; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 0x09 | ||||||
|  | union RegMotionInterrupt { | ||||||
|  |   struct { | ||||||
|  |     bool freefall_interrupt : 1; | ||||||
|  |     bool reserved_1 : 1; | ||||||
|  |     bool active_interrupt : 1; | ||||||
|  |     bool reserved_3 : 1; | ||||||
|  |     bool double_tap_interrupt : 1; | ||||||
|  |     bool single_tap_interrupt : 1; | ||||||
|  |     bool orientation_interrupt : 1; | ||||||
|  |     bool reserved_7 : 1; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 0x0C | ||||||
|  | union RegOrientationStatus { | ||||||
|  |   struct { | ||||||
|  |     uint8_t reserved_0_3 : 4; | ||||||
|  |     OrientationXY orient_xy : 2; | ||||||
|  |     bool orient_z : 1; | ||||||
|  |     uint8_t reserved_7 : 1; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw{0x00}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 0x0f | ||||||
|  | union RegRangeResolution { | ||||||
|  |   struct { | ||||||
|  |     Range range : 2; | ||||||
|  |     Resolution resolution : 2; | ||||||
|  |     uint8_t reserved_2 : 4; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw{0x00}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 0x10 | ||||||
|  | union RegOutputDataRate { | ||||||
|  |   struct { | ||||||
|  |     DataRate odr : 4; | ||||||
|  |     uint8_t reserved_4 : 1; | ||||||
|  |     bool z_axis_disable : 1; | ||||||
|  |     bool y_axis_disable : 1; | ||||||
|  |     bool x_axis_disable : 1; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw{0xde}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 0x11 | ||||||
|  | union RegPowerModeBandwidth { | ||||||
|  |   struct { | ||||||
|  |     uint8_t reserved_0 : 1; | ||||||
|  |     Bandwidth low_power_bandwidth : 4; | ||||||
|  |     uint8_t reserved_5 : 1; | ||||||
|  |     PowerMode power_mode : 2; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw{0xde}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 0x12 | ||||||
|  | union RegSwapPolarity { | ||||||
|  |   struct { | ||||||
|  |     bool x_y_swap : 1; | ||||||
|  |     bool z_polarity : 1; | ||||||
|  |     bool y_polarity : 1; | ||||||
|  |     bool x_polarity : 1; | ||||||
|  |     uint8_t reserved : 4; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw{0}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // 0x2a | ||||||
|  | union RegTapDuration { | ||||||
|  |   struct { | ||||||
|  |     uint8_t duration : 3; | ||||||
|  |     uint8_t reserved : 3; | ||||||
|  |     bool tap_shock : 1; | ||||||
|  |     bool tap_quiet : 1; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  |   uint8_t raw{0x04}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class MSA3xxComponent : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   void loop() override; | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   void set_model(Model model) { this->model_ = model; } | ||||||
|  |   void set_offset(float offset_x, float offset_y, float offset_z); | ||||||
|  |   void set_range(Range range) { this->range_ = range; } | ||||||
|  |   void set_bandwidth(Bandwidth bandwidth) { this->bandwidth_ = bandwidth; } | ||||||
|  |   void set_resolution(Resolution resolution) { this->resolution_ = resolution; } | ||||||
|  |   void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy); | ||||||
|  |  | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   SUB_BINARY_SENSOR(tap) | ||||||
|  |   SUB_BINARY_SENSOR(double_tap) | ||||||
|  |   SUB_BINARY_SENSOR(active) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   SUB_SENSOR(acceleration_x) | ||||||
|  |   SUB_SENSOR(acceleration_y) | ||||||
|  |   SUB_SENSOR(acceleration_z) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_TEXT_SENSOR | ||||||
|  |   SUB_TEXT_SENSOR(orientation_xy) | ||||||
|  |   SUB_TEXT_SENSOR(orientation_z) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   Trigger<> *get_tap_trigger() { return &this->tap_trigger_; } | ||||||
|  |   Trigger<> *get_double_tap_trigger() { return &this->double_tap_trigger_; } | ||||||
|  |   Trigger<> *get_orientation_trigger() { return &this->orientation_trigger_; } | ||||||
|  |   Trigger<> *get_freefall_trigger() { return &this->freefall_trigger_; } | ||||||
|  |   Trigger<> *get_active_trigger() { return &this->active_trigger_; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   Model model_{Model::MSA311}; | ||||||
|  |  | ||||||
|  |   PowerMode power_mode_{PowerMode::NORMAL}; | ||||||
|  |   DataRate data_rate_{DataRate::ODR_250HZ}; | ||||||
|  |   Bandwidth bandwidth_{Bandwidth::BW_250HZ}; | ||||||
|  |   Range range_{Range::RANGE_2G}; | ||||||
|  |   Resolution resolution_{Resolution::RES_14BIT}; | ||||||
|  |   float offset_x_, offset_y_, offset_z_;  // in m/s² | ||||||
|  |   RegSwapPolarity swap_; | ||||||
|  |  | ||||||
|  |   struct { | ||||||
|  |     int scale_factor_exp; | ||||||
|  |     uint8_t accel_data_width; | ||||||
|  |   } device_params_{}; | ||||||
|  |  | ||||||
|  |   struct { | ||||||
|  |     int16_t lsb_x, lsb_y, lsb_z; | ||||||
|  |     float x, y, z; | ||||||
|  |   } data_{}; | ||||||
|  |  | ||||||
|  |   struct { | ||||||
|  |     RegMotionInterrupt motion_int; | ||||||
|  |     RegOrientationStatus orientation; | ||||||
|  |     RegOrientationStatus orientation_old; | ||||||
|  |  | ||||||
|  |     uint32_t last_tap_ms{0}; | ||||||
|  |     uint32_t last_double_tap_ms{0}; | ||||||
|  |     uint32_t last_action_ms{0}; | ||||||
|  |  | ||||||
|  |     bool never_published{true}; | ||||||
|  |   } status_{}; | ||||||
|  |  | ||||||
|  |   void setup_odr_(DataRate rate); | ||||||
|  |   void setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth); | ||||||
|  |   void setup_range_resolution_(Range range, Resolution resolution); | ||||||
|  |   void setup_offset_(float offset_x, float offset_y, float offset_z); | ||||||
|  |  | ||||||
|  |   bool read_data_(); | ||||||
|  |   bool read_motion_status_(); | ||||||
|  |  | ||||||
|  |   int64_t twos_complement_(uint64_t value, uint8_t bits); | ||||||
|  |  | ||||||
|  |   // | ||||||
|  |   // Actons / Triggers | ||||||
|  |   // | ||||||
|  |   Trigger<> tap_trigger_; | ||||||
|  |   Trigger<> double_tap_trigger_; | ||||||
|  |   Trigger<> orientation_trigger_; | ||||||
|  |   Trigger<> freefall_trigger_; | ||||||
|  |   Trigger<> active_trigger_; | ||||||
|  |  | ||||||
|  |   void process_motions_(RegMotionInterrupt old); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace msa3xx | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										42
									
								
								esphome/components/msa3xx/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								esphome/components/msa3xx/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ACCELERATION_X, | ||||||
|  |     CONF_ACCELERATION_Y, | ||||||
|  |     CONF_ACCELERATION_Z, | ||||||
|  |     CONF_NAME, | ||||||
|  |     ICON_BRIEFCASE_DOWNLOAD, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_METER_PER_SECOND_SQUARED, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@latonita"] | ||||||
|  | DEPENDENCIES = ["msa3xx"] | ||||||
|  |  | ||||||
|  | ACCELERATION_SENSORS = (CONF_ACCELERATION_X, CONF_ACCELERATION_Y, CONF_ACCELERATION_Z) | ||||||
|  |  | ||||||
|  | accel_schema = cv.maybe_simple_value( | ||||||
|  |     sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, | ||||||
|  |         icon=ICON_BRIEFCASE_DOWNLOAD, | ||||||
|  |         accuracy_decimals=2, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |     ), | ||||||
|  |     key=CONF_NAME, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend( | ||||||
|  |     {cv.Optional(sensor): accel_schema for sensor in ACCELERATION_SENSORS} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     hub = await cg.get_variable(config[CONF_MSA3XX_ID]) | ||||||
|  |     for accel_key in ACCELERATION_SENSORS: | ||||||
|  |         if accel_key in config: | ||||||
|  |             sens = await sensor.new_sensor(config[accel_key]) | ||||||
|  |             cg.add(getattr(hub, f"set_{accel_key}_sensor")(sens)) | ||||||
							
								
								
									
										38
									
								
								esphome/components/msa3xx/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/msa3xx/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import text_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_NAME | ||||||
|  |  | ||||||
|  | from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@latonita"] | ||||||
|  | DEPENDENCIES = ["msa3xx"] | ||||||
|  |  | ||||||
|  | CONF_ORIENTATION_XY = "orientation_xy" | ||||||
|  | CONF_ORIENTATION_Z = "orientation_z" | ||||||
|  | ICON_SCREEN_ROTATION = "mdi:screen-rotation" | ||||||
|  |  | ||||||
|  | ORIENTATION_SENSORS = (CONF_ORIENTATION_XY, CONF_ORIENTATION_Z) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.Optional(sensor): cv.maybe_simple_value( | ||||||
|  |             text_sensor.text_sensor_schema(icon=ICON_SCREEN_ROTATION), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ) | ||||||
|  |         for sensor in ORIENTATION_SENSORS | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def setup_conf(config, key, hub): | ||||||
|  |     if sensor_config := config.get(key): | ||||||
|  |         var = await text_sensor.new_text_sensor(sensor_config) | ||||||
|  |         cg.add(getattr(hub, f"set_{key}_text_sensor")(var)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     hub = await cg.get_variable(config[CONF_MSA3XX_ID]) | ||||||
|  |  | ||||||
|  |     for key in ORIENTATION_SENSORS: | ||||||
|  |         await setup_conf(config, key, hub) | ||||||
| @@ -1,9 +1,10 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_UID | from esphome.const import CONF_UID | ||||||
| from esphome.core import HexInt | from esphome.core import HexInt | ||||||
| from .. import nfc_ns, Nfcc, NfcTagListener |  | ||||||
|  | from .. import Nfcc, NfcTagListener, nfc_ns | ||||||
|  |  | ||||||
| DEPENDENCIES = ["nfc"] | DEPENDENCIES = ["nfc"] | ||||||
|  |  | ||||||
| @@ -25,8 +26,7 @@ def validate_uid(value): | |||||||
|     for x in value.split("-"): |     for x in value.split("-"): | ||||||
|         if len(x) != 2: |         if len(x) != 2: | ||||||
|             raise cv.Invalid( |             raise cv.Invalid( | ||||||
|                 "Each part (separated by '-') of the UID must be two characters " |                 "Each part (separated by '-') of the UID must be two characters long." | ||||||
|                 "long." |  | ||||||
|             ) |             ) | ||||||
|         try: |         try: | ||||||
|             x = int(x, 16) |             x = int(x, 16) | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
| from .. import const, schema, validate, generate | import esphome.config_validation as cv | ||||||
|  |  | ||||||
|  | from .. import const, generate, schema, validate | ||||||
|  |  | ||||||
| DEPENDENCIES = [const.OPENTHERM] | DEPENDENCIES = [const.OPENTHERM] | ||||||
| COMPONENT_TYPE = const.BINARY_SENSOR | COMPONENT_TYPE = const.BINARY_SENSOR | ||||||
| @@ -11,8 +12,7 @@ COMPONENT_TYPE = const.BINARY_SENSOR | |||||||
| def get_entity_validation_schema(entity: schema.BinarySensorSchema) -> cv.Schema: | def get_entity_validation_schema(entity: schema.BinarySensorSchema) -> cv.Schema: | ||||||
|     return binary_sensor.binary_sensor_schema( |     return binary_sensor.binary_sensor_schema( | ||||||
|         device_class=( |         device_class=( | ||||||
|             entity.device_class |             entity.device_class or binary_sensor._UNDEF  # pylint: disable=protected-access | ||||||
|             or binary_sensor._UNDEF  # pylint: disable=protected-access |  | ||||||
|         ), |         ), | ||||||
|         icon=(entity.icon or binary_sensor._UNDEF),  # pylint: disable=protected-access |         icon=(entity.icon or binary_sensor._UNDEF),  # pylint: disable=protected-access | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -3,8 +3,9 @@ from typing import Any, Callable, Optional | |||||||
|  |  | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| from . import const | from . import const | ||||||
| from .schema import TSchema, SettingSchema | from .schema import SettingSchema, TSchema | ||||||
|  |  | ||||||
| opentherm_ns = cg.esphome_ns.namespace("opentherm") | opentherm_ns = cg.esphome_ns.namespace("opentherm") | ||||||
| OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) | OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) | ||||||
| @@ -112,8 +113,7 @@ def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]): | |||||||
|         msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") |         msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") | ||||||
|         if keep_updated: |         if keep_updated: | ||||||
|             cg.add(hub.add_repeating_message(msg_expr)) |             cg.add(hub.add_repeating_message(msg_expr)) | ||||||
|         else: |         elif order is not None: | ||||||
|             if order is not None: |  | ||||||
|             cg.add(hub.add_initial_message(msg_expr, order)) |             cg.add(hub.add_initial_message(msg_expr, order)) | ||||||
|         else: |         else: | ||||||
|             cg.add(hub.add_initial_message(msg_expr)) |             cg.add(hub.add_initial_message(msg_expr)) | ||||||
| @@ -128,7 +128,7 @@ Create = Callable[[dict[str, Any], str, cg.MockObj], Awaitable[cg.Pvariable]] | |||||||
|  |  | ||||||
|  |  | ||||||
| def create_only_conf( | def create_only_conf( | ||||||
|     create: Callable[[dict[str, Any]], Awaitable[cg.Pvariable]] |     create: Callable[[dict[str, Any]], Awaitable[cg.Pvariable]], | ||||||
| ) -> Create: | ) -> Create: | ||||||
|     return lambda conf, _key, _hub: create(conf) |     return lambda conf, _key, _hub: create(conf) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| from .. import const, schema, validate, generate | import esphome.config_validation as cv | ||||||
|  |  | ||||||
|  | from .. import const, generate, schema, validate | ||||||
|  |  | ||||||
| DEPENDENCIES = [const.OPENTHERM] | DEPENDENCIES = [const.OPENTHERM] | ||||||
| COMPONENT_TYPE = const.SENSOR | COMPONENT_TYPE = const.SENSOR | ||||||
| @@ -22,11 +23,9 @@ MSG_DATA_TYPES = { | |||||||
|  |  | ||||||
| def get_entity_validation_schema(entity: schema.SensorSchema) -> cv.Schema: | def get_entity_validation_schema(entity: schema.SensorSchema) -> cv.Schema: | ||||||
|     return sensor.sensor_schema( |     return sensor.sensor_schema( | ||||||
|         unit_of_measurement=entity.unit_of_measurement |         unit_of_measurement=entity.unit_of_measurement or sensor._UNDEF,  # pylint: disable=protected-access | ||||||
|         or sensor._UNDEF,  # pylint: disable=protected-access |  | ||||||
|         accuracy_decimals=entity.accuracy_decimals, |         accuracy_decimals=entity.accuracy_decimals, | ||||||
|         device_class=entity.device_class |         device_class=entity.device_class or sensor._UNDEF,  # pylint: disable=protected-access | ||||||
|         or sensor._UNDEF,  # pylint: disable=protected-access |  | ||||||
|         icon=entity.icon or sensor._UNDEF,  # pylint: disable=protected-access |         icon=entity.icon or sensor._UNDEF,  # pylint: disable=protected-access | ||||||
|         state_class=entity.state_class, |         state_class=entity.state_class, | ||||||
|     ).extend( |     ).extend( | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import git, yaml_util | from esphome import git, yaml_util | ||||||
| from esphome.config_helpers import merge_config | from esphome.config_helpers import merge_config | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ESPHOME, |     CONF_ESPHOME, | ||||||
|     CONF_FILE, |     CONF_FILE, | ||||||
| @@ -10,12 +10,14 @@ from esphome.const import ( | |||||||
|     CONF_MIN_VERSION, |     CONF_MIN_VERSION, | ||||||
|     CONF_PACKAGES, |     CONF_PACKAGES, | ||||||
|     CONF_PASSWORD, |     CONF_PASSWORD, | ||||||
|  |     CONF_PATH, | ||||||
|     CONF_REF, |     CONF_REF, | ||||||
|     CONF_REFRESH, |     CONF_REFRESH, | ||||||
|     CONF_URL, |     CONF_URL, | ||||||
|     CONF_USERNAME, |     CONF_USERNAME, | ||||||
|  |     CONF_VARS, | ||||||
|  |     __version__ as ESPHOME_VERSION, | ||||||
| ) | ) | ||||||
| from esphome.const import __version__ as ESPHOME_VERSION |  | ||||||
| from esphome.core import EsphomeError | from esphome.core import EsphomeError | ||||||
|  |  | ||||||
| DOMAIN = CONF_PACKAGES | DOMAIN = CONF_PACKAGES | ||||||
| @@ -74,7 +76,19 @@ BASE_SCHEMA = cv.All( | |||||||
|             cv.Optional(CONF_PASSWORD): cv.string, |             cv.Optional(CONF_PASSWORD): cv.string, | ||||||
|             cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, |             cv.Exclusive(CONF_FILE, "files"): validate_yaml_filename, | ||||||
|             cv.Exclusive(CONF_FILES, "files"): cv.All( |             cv.Exclusive(CONF_FILES, "files"): cv.All( | ||||||
|                 cv.ensure_list(validate_yaml_filename), |                 cv.ensure_list( | ||||||
|  |                     cv.Any( | ||||||
|  |                         validate_yaml_filename, | ||||||
|  |                         cv.Schema( | ||||||
|  |                             { | ||||||
|  |                                 cv.Required(CONF_PATH): validate_yaml_filename, | ||||||
|  |                                 cv.Optional(CONF_VARS, default={}): cv.Schema( | ||||||
|  |                                     {cv.string: cv.string} | ||||||
|  |                                 ), | ||||||
|  |                             } | ||||||
|  |                         ), | ||||||
|  |                     ) | ||||||
|  |                 ), | ||||||
|                 cv.Length(min=1), |                 cv.Length(min=1), | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_REF): cv.git_ref, |             cv.Optional(CONF_REF): cv.git_ref, | ||||||
| @@ -106,16 +120,25 @@ def _process_base_package(config: dict) -> dict: | |||||||
|         username=config.get(CONF_USERNAME), |         username=config.get(CONF_USERNAME), | ||||||
|         password=config.get(CONF_PASSWORD), |         password=config.get(CONF_PASSWORD), | ||||||
|     ) |     ) | ||||||
|     files: list[str] = config[CONF_FILES] |     files = [] | ||||||
|  |  | ||||||
|  |     for file in config[CONF_FILES]: | ||||||
|  |         if isinstance(file, str): | ||||||
|  |             files.append({CONF_PATH: file, CONF_VARS: {}}) | ||||||
|  |         else: | ||||||
|  |             files.append(file) | ||||||
|  |  | ||||||
|     def get_packages(files) -> dict: |     def get_packages(files) -> dict: | ||||||
|         packages = {} |         packages = {} | ||||||
|         for file in files: |         for idx, file in enumerate(files): | ||||||
|             yaml_file: Path = repo_dir / file |             filename = file[CONF_PATH] | ||||||
|  |             yaml_file: Path = repo_dir / filename | ||||||
|  |             vars = file.get(CONF_VARS, {}) | ||||||
|  |  | ||||||
|             if not yaml_file.is_file(): |             if not yaml_file.is_file(): | ||||||
|                 raise cv.Invalid( |                 raise cv.Invalid( | ||||||
|                     f"{file} does not exist in repository", path=[CONF_FILES] |                     f"{filename} does not exist in repository", | ||||||
|  |                     path=[CONF_FILES, idx, CONF_PATH], | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|             try: |             try: | ||||||
| @@ -131,11 +154,12 @@ def _process_base_package(config: dict) -> dict: | |||||||
|                         raise cv.Invalid( |                         raise cv.Invalid( | ||||||
|                             f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}" |                             f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}" | ||||||
|                         ) |                         ) | ||||||
|  |                 vars = {k: str(v) for k, v in vars.items()} | ||||||
|                 packages[file] = new_yaml |                 new_yaml = yaml_util.substitute_vars(new_yaml, vars) | ||||||
|  |                 packages[f"{filename}{idx}"] = new_yaml | ||||||
|             except EsphomeError as e: |             except EsphomeError as e: | ||||||
|                 raise cv.Invalid( |                 raise cv.Invalid( | ||||||
|                     f"{file} is not a valid YAML file. Please check the file contents.\n{e}" |                     f"{filename} is not a valid YAML file. Please check the file contents.\n{e}" | ||||||
|                 ) from e |                 ) from e | ||||||
|         return packages |         return packages | ||||||
|  |  | ||||||
| @@ -154,7 +178,7 @@ def _process_base_package(config: dict) -> dict: | |||||||
|             error = er |             error = er | ||||||
|  |  | ||||||
|     if packages is None: |     if packages is None: | ||||||
|         raise cv.Invalid(f"Failed to load packages. {error}") |         raise cv.Invalid(f"Failed to load packages. {error}", path=error.path) | ||||||
|  |  | ||||||
|     return {"packages": packages} |     return {"packages": packages} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_UID | from esphome.const import CONF_UID | ||||||
| from esphome.core import HexInt | from esphome.core import HexInt | ||||||
| from . import pn532_ns, PN532, CONF_PN532_ID |  | ||||||
|  | from . import CONF_PN532_ID, PN532, pn532_ns | ||||||
|  |  | ||||||
| DEPENDENCIES = ["pn532"] | DEPENDENCIES = ["pn532"] | ||||||
|  |  | ||||||
| @@ -13,8 +14,7 @@ def validate_uid(value): | |||||||
|     for x in value.split("-"): |     for x in value.split("-"): | ||||||
|         if len(x) != 2: |         if len(x) != 2: | ||||||
|             raise cv.Invalid( |             raise cv.Invalid( | ||||||
|                 "Each part (separated by '-') of the UID must be two characters " |                 "Each part (separated by '-') of the UID must be two characters long." | ||||||
|                 "long." |  | ||||||
|             ) |             ) | ||||||
|         try: |         try: | ||||||
|             x = int(x, 16) |             x = int(x, 16) | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_UID | from esphome.const import CONF_UID | ||||||
| from esphome.core import HexInt | from esphome.core import HexInt | ||||||
| from . import rc522_ns, RC522, CONF_RC522_ID |  | ||||||
|  | from . import CONF_RC522_ID, RC522, rc522_ns | ||||||
|  |  | ||||||
| DEPENDENCIES = ["rc522"] | DEPENDENCIES = ["rc522"] | ||||||
|  |  | ||||||
| @@ -13,8 +14,7 @@ def validate_uid(value): | |||||||
|     for x in value.split("-"): |     for x in value.split("-"): | ||||||
|         if len(x) != 2: |         if len(x) != 2: | ||||||
|             raise cv.Invalid( |             raise cv.Invalid( | ||||||
|                 "Each part (separated by '-') of the UID must be two characters " |                 "Each part (separated by '-') of the UID must be two characters long." | ||||||
|                 "long." |  | ||||||
|             ) |             ) | ||||||
|         try: |         try: | ||||||
|             x = int(x, 16) |             x = int(x, 16) | ||||||
|   | |||||||
| @@ -46,6 +46,7 @@ class BSDSocketImpl : public Socket { | |||||||
|       close();  // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) |       close();  // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   int connect(const struct sockaddr *addr, socklen_t addrlen) override { return ::connect(fd_, addr, addrlen); } | ||||||
|   std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override { |   std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override { | ||||||
|     int fd = ::accept(fd_, addr, addrlen); |     int fd = ::accept(fd_, addr, addrlen); | ||||||
|     if (fd == -1) |     if (fd == -1) | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ class LwIPSocketImpl : public Socket { | |||||||
|       close();  // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) |       close();  // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   int connect(const struct sockaddr *addr, socklen_t addrlen) override { return lwip_connect(fd_, addr, addrlen); } | ||||||
|   std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override { |   std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) override { | ||||||
|     int fd = lwip_accept(fd_, addr, addrlen); |     int fd = lwip_accept(fd_, addr, addrlen); | ||||||
|     if (fd == -1) |     if (fd == -1) | ||||||
|   | |||||||
| @@ -21,7 +21,9 @@ class Socket { | |||||||
|   virtual int close() = 0; |   virtual int close() = 0; | ||||||
|   // not supported yet: |   // not supported yet: | ||||||
|   // virtual int connect(const std::string &address) = 0; |   // virtual int connect(const std::string &address) = 0; | ||||||
|   // virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0; | #if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS) | ||||||
|  |   virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0; | ||||||
|  | #endif | ||||||
|   virtual int shutdown(int how) = 0; |   virtual int shutdown(int how) = 0; | ||||||
|  |  | ||||||
|   virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; |   virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0; | ||||||
|   | |||||||
| @@ -1,15 +1,17 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
|  | import esphome.codegen as cg | ||||||
| from esphome.components import display | from esphome.components import display | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_EXTERNAL_VCC, |  | ||||||
|     CONF_LAMBDA, |  | ||||||
|     CONF_MODEL, |  | ||||||
|     CONF_RESET_PIN, |  | ||||||
|     CONF_BRIGHTNESS, |     CONF_BRIGHTNESS, | ||||||
|     CONF_CONTRAST, |     CONF_CONTRAST, | ||||||
|  |     CONF_EXTERNAL_VCC, | ||||||
|     CONF_INVERT, |     CONF_INVERT, | ||||||
|  |     CONF_LAMBDA, | ||||||
|  |     CONF_MODEL, | ||||||
|  |     CONF_OFFSET_X, | ||||||
|  |     CONF_OFFSET_Y, | ||||||
|  |     CONF_RESET_PIN, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base") | ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base") | ||||||
| @@ -18,8 +20,6 @@ SSD1306Model = ssd1306_base_ns.enum("SSD1306Model") | |||||||
|  |  | ||||||
| CONF_FLIP_X = "flip_x" | CONF_FLIP_X = "flip_x" | ||||||
| CONF_FLIP_Y = "flip_y" | CONF_FLIP_Y = "flip_y" | ||||||
| CONF_OFFSET_X = "offset_x" |  | ||||||
| CONF_OFFSET_Y = "offset_y" |  | ||||||
|  |  | ||||||
| MODELS = { | MODELS = { | ||||||
|     "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32, |     "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32, | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user